springboot 日志詳解

系統用戶操作日志(記錄用戶操作并定時保存到表中)

客戶需求: 要對幾個關鍵的業務功能進行操作日志記錄,即什么人在什么時間操作了哪個功能,操作前的數據報文是什么、操作后的數據報文是什么,必要的時候可以一鍵回退。

設計思路: ruoyi中使用Spring AOP來實現操作日志
1、定義業務操作日志注解,注解內可以定義一些屬性,如操作功能名稱、功能的描述等;
2、把業務操作日志注解標記在需要進行業務操作記錄的方法上(在實際業務中,一些簡單的業務查詢行為通常沒有必要記錄);
3、定義切入點,編寫切面:切入點就是標記了業務操作日志注解的目標方法;切面的主要邏輯就是保存業務操作日志信息;

Spring Aop 詳解 以及示例

ruoyi實現方案,直接上核心日志切面類

/*** 操作日志記錄處理   注意這里是 操作日志 而不是 輸出日志* * @author ruoyi*/
@Aspect
@Component
@Slf4j
public class LogAspect
{/** 排除敏感屬性字段 */public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };/** 計算操作消耗時間 */private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");/*** 處理請求前執行*/@Before(value = "@annotation(controllerLog)")public void boBefore(JoinPoint joinPoint, Log controllerLog){TIME_THREADLOCAL.set(System.currentTimeMillis());}/*** 處理完請求后執行** @param joinPoint 切點*/@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult){handleLog(joinPoint, controllerLog, null, jsonResult);}/*** 攔截異常操作* * @param joinPoint 切點* @param e 異常*/@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e){handleLog(joinPoint, controllerLog, e, null);}protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult){try{// 獲取當前的用戶
//            LoginUser loginUser = SecurityUtils.getLoginUser();// 暫時使用admin來代替操作人String user= "admin";// *========數據庫日志=========*//SysOperLog operLog = new SysOperLog();operLog.setStatus(BusinessStatus.SUCCESS.ordinal());// 請求的地址
//            String ip = IpUtils.getIpAddr();String ip = "127.0.0.1";operLog.setOperIp(ip);operLog.setOperUrl(substring(getRequest().getRequestURI(), 0, 255));
//            if (loginUser != null)
//            {
                operLog.setOperName(loginUser.getUsername());
//                operLog.setOperName(user);
//                SysUser currentUser = loginUser.getUser();
//                if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
//                {
//                    operLog.setDeptName(currentUser.getDept().getDeptName());
//                }
//            }if (e != null){operLog.setStatus(BusinessStatus.FAIL.ordinal());operLog.setErrorMsg(substring(e.getMessage(), 0, 2000));}// 設置方法名稱String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operLog.setMethod(className + "." + methodName + "()");// 設置請求方式operLog.setRequestMethod(getRequest().getMethod());// 處理設置注解上的參數getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);// 設置消耗時間operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
//            // 保存數據庫   這一步ruoyi 是放到定期執行任務線程池中
//            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));AsyncManager.me().execute(recordOper(operLog));}catch (Exception exp){// 記錄本地異常日志log.error("異常信息:{}", exp.getMessage());exp.printStackTrace();}finally{TIME_THREADLOCAL.remove();}}/*** 獲取注解中對方法的描述信息 用于Controller層注解* * @param log 日志* @param operLog 操作日志* @throws Exception*/public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception{// 設置action動作operLog.setBusinessType(log.businessType().ordinal());// 設置標題operLog.setTitle(log.title());// 設置操作人類別operLog.setOperatorType(log.operatorType().ordinal());// 是否需要保存request,參數和值if (log.isSaveRequestData()){// 獲取參數的信息,傳入到數據庫中。setRequestValue(joinPoint, operLog, log.excludeParamNames());}// 是否需要保存response,參數和值if (log.isSaveResponseData() && jsonResult!=null){operLog.setJsonResult(substring(JSON.toJSONString(jsonResult), 0, 2000));}}/*** 獲取請求的參數,放到log中* * @param operLog 操作日志* @throws Exception 異常*/private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception{Map<?, ?> paramsMap = getParamMap(getRequest());String requestMethod = operLog.getRequestMethod();if (paramsMap.isEmpty() && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))){String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);operLog.setOperParam(substring(params, 0, 2000));}else{
//            operLog.setOperParam(substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));}}/*** 參數拼裝*/private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames){StringBuilder params = new StringBuilder();if (paramsArray != null && paramsArray.length > 0){for (Object o : paramsArray){if (o!=null && !isFilterObject(o)){try{
//                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));String jsonObj = JSON.toJSONString(o);params.append(jsonObj).append(" ");}catch (Exception e){}}}}return params.toString().trim();}//    /**
//     * 忽略敏感屬性
//     */
//    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
//    {
//        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
//    }/*** 判斷是否需要過濾的對象。* * @param o 對象信息。* @return 如果是需要過濾的對象,則返回true;否則返回false。*/@SuppressWarnings("rawtypes")public boolean isFilterObject(final Object o){Class<?> clazz = o.getClass();if (clazz.isArray()){return clazz.getComponentType().isAssignableFrom(MultipartFile.class);}else if (Collection.class.isAssignableFrom(clazz)){Collection collection = (Collection) o;for (Object value : collection){return value instanceof MultipartFile;}}else if (Map.class.isAssignableFrom(clazz)){Map map = (Map) o;for (Object value : map.entrySet()){Map.Entry entry = (Map.Entry) value;return entry.getValue() instanceof MultipartFile;}}return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse|| o instanceof BindingResult;}/*** 截取字符串** @param str 字符串* @param start 開始* @param end 結束* @return 結果*/public static String substring(final String str, int start, int end){if (str == null){return "";}if (end < 0){end = str.length() + end;}if (start < 0){start = str.length() + start;}if (end > str.length()){end = str.length();}if (start > end){return "";}if (start < 0){start = 0;}if (end < 0){end = 0;}return str.substring(start, end);}/*** 獲取request*/public static HttpServletRequest getRequest(){return getRequestAttributes().getRequest();}public static ServletRequestAttributes getRequestAttributes(){RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes) attributes;}/*** 獲得所有請求參數** @param request 請求對象{@link ServletRequest}* @return Map*/public static Map<String, String> getParamMap(ServletRequest request){Map<String, String> params = new HashMap<>();for (Map.Entry<String, String[]> entry : getParams(request).entrySet()){params.put(entry.getKey(), join(entry.getValue(), ","));}return params;}/*** 獲得所有請求參數** @param request 請求對象{@link ServletRequest}* @return Map*/public static Map<String, String[]> getParams(ServletRequest request){final Map<String, String[]> map = request.getParameterMap();return Collections.unmodifiableMap(map);}public static String join(Object[] array, String delimiter) {return array == null ? null : join((Object[])array, delimiter, 0, array.length);}public static String join(Object[] array, String delimiter, int startIndex, int endIndex) {if (array == null) {return null;} else if (endIndex - startIndex <= 0) {return "";} else {StringJoiner joiner = new StringJoiner(toStringOrEmpty(delimiter));for(int i = startIndex; i < endIndex; ++i) {joiner.add(toStringOrEmpty(array[i]));}return joiner.toString();}}private static String toStringOrEmpty(Object obj) {return Objects.toString(obj, "");}/*** 操作日志記錄** @param operLog 操作日志信息* @return 任務task*/public static TimerTask recordOper(final SysOperLog operLog){return new TimerTask(){@Overridepublic void run(){// 遠程查詢操作地點
//                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));SpringUtils.getBean(ISysOperLogService.class).save(operLog);}};}}

這樣就可以將帶有Log注解的控制層的操作保存到數據庫中

系統日志

springboot默認提供logback日志來保存控制臺輸出日志。

在 src/main/resources/logback.xml 中定義相應的日志輸出格式即可
ruoyi 示例

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!-- 日志存放路徑   直接存在根目錄下--><property name="log.path" value="logs" /><!-- 日志輸出格式 --><property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /><!-- 控制臺輸出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${log.pattern}</pattern></encoder></appender><!-- 系統日志輸出 --><appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--	    <file>${log.path}/sys-info.log</file>--><!-- 循環政策:基于時間創建日志文件 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 日志文件名格式 --><fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern><!-- 日志最大的歷史 60天 --><maxHistory>60</maxHistory></rollingPolicy><encoder><pattern>${log.pattern}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 過濾的級別 --><level>INFO</level><!-- 匹配時的操作:接收(記錄) --><onMatch>ACCEPT</onMatch><!-- 不匹配時的操作:拒絕(不記錄) --><onMismatch>DENY</onMismatch></filter></appender><appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--	    <file>${log.path}/sys-error.log</file>--><!-- 循環政策:基于時間創建日志文件 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 日志文件名格式 --><fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern><!-- 日志最大的歷史 60天 --><maxHistory>60</maxHistory></rollingPolicy><encoder><pattern>${log.pattern}</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 過濾的級別 --><level>ERROR</level><!-- 匹配時的操作:接收(記錄) --><onMatch>ACCEPT</onMatch><!-- 不匹配時的操作:拒絕(不記錄) --><onMismatch>DENY</onMismatch></filter></appender><!-- 用戶訪問日志輸出  --><appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--		<file>${log.path}/sys-user.log</file>--><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 按天回滾 daily --><fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern><!-- 日志最大的歷史 60天 --><maxHistory>60</maxHistory></rollingPolicy><encoder><pattern>${log.pattern}</pattern></encoder></appender><!-- 系統模塊日志級別控制  --><logger name="com.example" level="info" /><!-- Spring日志級別控制  --><logger name="org.springframework" level="warn" /><root level="info"><appender-ref ref="console" /></root><!--系統操作日志--><root level="info"><appender-ref ref="file_info" /><appender-ref ref="file_error" /></root><!--系統用戶操作日志--><logger name="sys-user" level="info"><appender-ref ref="sys-user"/></logger>
</configuration> 

以上配置會在項目根目錄的同級目錄下生成 log/{日期}.log 的日志文件, 若打成 .jar包運行, 則日志文件會生成在 . jar 文件的同級目錄下。

# log
logging:level:# 你自己的包名稱com.example: debugorg.springframework: warn

在 application.yml中配置日志

Logback日志路徑保存配置
配置1
<property name="LOG_HOME" value="log" />

若項目未打成.jar文件, 運行項目, 日志文件會保存在項目的根目錄下
若項目打成.jar文件, 運行.jar文件, 日志文件會保存在.jar文件同級目錄下

此時 在根目錄的文件夾下面會生成相應的logs文件夾并且生成帶有日期的日志文件

按天保存系統日志到文件

操作日志持久化(保存到表中)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/9977.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/9977.shtml
英文地址,請注明出處:http://en.pswp.cn/web/9977.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

共享旅游革命:千益暢行卡的優勢揭秘

在共享經濟的快速發展中&#xff0c;共享旅游創業已成為許多創新者和投資者關注的重點。特別是千益暢行&#xff0c;作為共享旅游行業的新秀&#xff0c;其商業模型和經營策略引起了市場的高度討論。然而&#xff0c;對于這個公司是否僅僅是新一輪的市場“收割者”&#xff0c;…

Kylin Server V10下FTP服務器安全加固

一、查看操作系統信息 [root@localhost ~]# cat /etc/.kyinfo [dist] name=Kylin milestone=Server-V10-GFB-Release-ZF9_01-2204-Build03 arch=arm64 beta=False time=2023-01-09 11:04:36 dist_id=Kylin-Server-V10-GFB-Release-ZF9_01-2204-Build03-arm64-2023-01-09 11:04…

大模型prompt實例:知識庫信息質量校驗模塊

大模型相關目錄 大模型&#xff0c;包括部署微調prompt/Agent應用開發、知識庫增強、數據庫增強、知識圖譜增強、自然語言處理、多模態等大模型應用開發內容 從0起步&#xff0c;揚帆起航。 大模型應用向開發路徑&#xff1a;AI代理工作流大模型應用開發實用開源項目匯總大模…

基于FPGA的數字信號處理(11)--定點數的舍入模式(2)向最臨近值取整nearest

前言 在之前的文章介紹了定點數為什么需要舍入和幾種常見的舍入模式。今天我們再來看看另外一種舍入模式&#xff1a;向最臨近值取整nearest。 10進制數的nearest nearest&#xff1a; 向最臨近值方向取整。它的舍入方式和四舍五入非常類似&#xff0c;都是舍入到最近的整數…

【Unity AR開發系列】介紹如何使用這個支持熱更的AR開發插件,快速地開發AR應用

預告 Unity開發AR系列 本專欄將介紹如何使用這個支持熱更的AR開發插件&#xff0c;快速地開發AR應用。 更新 二、使用插件一鍵安裝HybridCLR和ARCore 三、配置帶HybridCLR的ARCore開發環境

計算機視覺與深度學習實戰之以Python為工具:基于主成分分析的人臉二維碼識別

注意:本文的下載教程,與以下文章的思路有相同點,也有不同點,最終目標只是讓讀者從多維度去熟練掌握本知識點。 下載教程:計算機視覺與深度學習實戰-以MATLAB和Python為工具_基于主成分分析的人臉二維碼識別_項目開發案例教程.pdf 一、引言 隨著科技的快速發展,計算機視覺…

單鏈表經典oj題(2)

前言 這次將要把剩下的oj題將以圖解和自己的理解把它講解完&#xff0c;希望對大家有所幫助&#xff0c;這次的講解也是干貨 第一題 21. 合并兩個有序鏈表 - 力扣&#xff08;LeetCode&#xff09; ok這次就簡單點&#xff0c;大家自己去看題目了 將兩個升序鏈表合并為一個…

帶有-i選項的sed命令在Linux上執行成功,但在MacOS上失敗了

問題&#xff1a; 我已經成功地使用以下 sed 命令在Linux中搜索/替換文本&#xff1a; sed -i s/old_string/new_string/g /path/to/file然而&#xff0c;當我在Mac OS X上嘗試時&#xff0c;我得到&#xff1a; command i expects \ followed by text我以為我的Mac運行的是…

未授權訪問:Memcached 未授權訪問漏洞

目錄 1、漏洞原理 2、環境搭建 3、未授權訪問 防御手段 今天繼續學習各種未授權訪問的知識和相關的實操實驗&#xff0c;一共有好多篇&#xff0c;內容主要是參考先知社區的一位大佬的關于未授權訪問的好文章&#xff0c;還有其他大佬總結好的文章&#xff1a; 這里附上大…

如何在OpenWrt軟路由中增加一個新功能

為了在OpenWrt中增加一個新的功能&#xff0c;并使其支持 UCI 配置&#xff0c;我們可以創建一個簡單的C語言服務&#xff0c;例如一個簡單的日志服務。此服務將記錄到日志文件中&#xff0c;并支持通過 UCI 配置啟用或禁用日志功能。以下是詳細的步驟和代碼示例。 1 創建服務…

K8S三 K8S部署微服務應用

一 用k8s部署微服務應用 以我們之前用docker部署過的eureka應用為例&#xff0c;首先添加配置文件eureka-app-deployment.yaml用于創建Deployment apiVersion: apps/v1 kind: Deployment metadata:name: eureka-app-deployment # deployment名字labels:app: eureka-app spec:…

【C++】CentOS環境搭建-升級CMAKE

【C】CentOS環境搭建-升級CMAKE CMAKE報錯CMake 3.12 or higher is required. You are running version 2.8.12.2升級步驟1.移除當前的cmake2.安裝必要的構建工具和庫3.下載最新的cmake源碼并解壓5.編譯和安裝6.驗證安裝 CMAKE報錯CMake 3.12 or higher is required. You are r…

oraclesql中刪除表中重復行的方法

在Oracle SQL中&#xff0c;刪除表中的重復行有幾種常見的方法。以下是其中的三種&#xff1a; 使用ROWID: 通過比較ROWID&#xff0c;你可以找到并刪除重復的行。這是因為ROWID是Oracle數據庫為每一行數據分配的唯一標識符。 sql DELETE FROM persons p1 WHERE ROWID NOT…

MySQL存儲引擎詳解

存儲引擎 MySQL體系結構 連接層&#xff1a;與客戶端連接&#xff0c;權限校驗、連接池服務層&#xff1a;SQL接口和解析、查詢優化、緩存、函數引擎層&#xff1a;索引、存儲引擎存儲層&#xff1a;系統文件、日志&#xff08;Redo、Undo等&#xff09; 存儲引擎介紹 不同的…

SSH:安全遠程訪問的基石

SSH&#xff1a;安全遠程訪問的基石 一、引言 在當今這個數字化、網絡化的時代&#xff0c;遠程訪問和管理計算機資源已成為日常工作的重要組成部分。然而&#xff0c;如何在不安全的網絡環境中確保數據傳輸的機密性、完整性和可靠性&#xff0c;成為了一個亟待解決的問題。S…

前端測試策略與實踐:單元測試、E2E測試與可訪問性審計

前端測試策略是確保Web應用程序質量、性能和用戶體驗的關鍵組成部分。有效的測試策略通常包括單元測試、端到端&#xff08;E2E&#xff09;測試以及可訪問性審計等多個層面。以下是關于這三類測試的策略與實踐建議&#xff1a; 單元測試 定義與目的&#xff1a; 單元測試是針…

P2622 關燈問題

小小注解&#xff1a; 1. vis&#xff1a;表示到達該狀態的步數&#xff08;min&#xff09;1&#xff0c; 因為我們是從開始狀態 窮舉&#xff0c;所以每次到一個新狀態&#xff08;之前沒有到過的狀態&#xff09;就是最小步數。 如何判斷是否是一個新狀態呢&#xff0c…

axios常用配置

Axios 是一個基于 promise 的 HTTP 庫&#xff0c;廣泛用于瀏覽器和 node.js 中。以下是一些 Axios 常用的配置選項&#xff1a; url: 字符串&#xff0c;請求的服務器URL&#xff0c;是必填項。method: 請求方法&#xff0c;如 ‘get’, ‘post’, ‘put’, ‘delete’ 等&am…

免費遠程控制軟件哪個好用

免費遠程控制軟件哪個好用 在現今高度信息化的社會&#xff0c;遠程控制軟件已成為許多用戶進行遠程辦公、技術支持和教育培訓的重要工具。市面上有許多免費的遠程控制軟件&#xff0c;但哪款才是最好用的呢&#xff1f;本文將為您介紹幾款熱門的免費遠程控制軟件&#xff0c;…

Tab菜單與下拉式菜單

Tab菜單 利用CSS隱藏或顯示欄目中的部分內容&#xff0c;實際Tab面板包含的全部內容都已下載到客戶端瀏覽器當中。一般Tab面板僅顯示一個Tab菜單項&#xff0c;當用戶點選對應的菜單選項之后&#xff0c;才會顯示對應的內容。 <!DOCTYPE html> <html><head>…