关于日志(slf4j的使用心得)

2024-06-23 16:32
文章标签 使用 日志 心得 slf4j

本文主要是介绍关于日志(slf4j的使用心得),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

没有调试过线上bug的人学不会打log

1. Object… arguments

从slf4j-1.6.0开始,public void error(String format, Object... arguments);中arguments的最后一个参数如果是throwable对象,将会被作为异常信息进行打印。
slf4j-1.6.0以前,只能通过public void error(String msg, Throwable t);打印异常信息,缺点是必须通过拼接字符串的形式把arguments组装为msg。

2. MDC的使用

本节内容摘取自:Slf4j MDC 使用和 基于 Logback 的实现分析(感谢原作者)
有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。
MDC ( Mapped Diagnostic Contexts ),顾名思义,其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
在日志模板中,使用 %X{ }来占位,替换到对应的 MDC 中 key 的值。

看一个MDC使用的简单示例:

public class LogTest {private static final Logger logger = LoggerFactory.getLogger(LogTest.class);public static void main(String[] args) {MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId()));logger.info("纯字符串信息的info级别日志");}
}

logback的输出模板配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration><property name="log.base" value="${catalina.base}/logs" /><contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"><resetJUL>true</resetJUL></contextListener><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder charset="UTF-8"><pattern>[%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5p) %logger.%M\(%F:%L\)] %X{THREAD_ID} %msg%n</pattern></encoder></appender><root level="INFO"><appender-ref ref="console" /></root>
</configuration>

于是,就有了输出:

[2015-04-30 15:34:35 INFO  io.github.ketao1989.log4j.LogTest.main(LogTest.java:29)] 1 纯字符串信息的info级别日志

3. 日志文件的分类

STDOUT:控制台输出日志
RollingFile:完整的日志文件
ErrorFile:保存系统报错
MainFile:保存系统一些关键日志,便于搜索

4. 常用配置

工具类:

package com.example;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;/*** 日志工具类* * @author frcoder*/
public class LogUtil {/*** 记录用户行为*/public static Logger userLog = LoggerFactory.getLogger("@USER");public static void newUserLog(String somethings) {MDC.put("USER", "");MDC.put("DO", somethings);userLog.debug("...");}public static void newUserLog(Object userId, String somethings) {MDC.put("USER", userId.toString());MDC.put("DO", somethings);userLog.debug("...");}public static void newUserLog(Object userId, Object role, String somethings) {MDC.put("USER", UserRole.toRoleString((Integer) role) + ":" + userId.toString());MDC.put("DO", somethings);userLog.debug("...");}public static void userQuit() {MDC.clear();}
}

配置:

Configuration:# Internal Log4j events levelstatus: warn# Automatic Reconfiguration, unit: secondmonitorInterval: 300dest: errname: YAMLConfigproperties:property:-name: projectNamevalue: me-name: logHomevalue: /tmp/logsthresholdFilter:level: debugappenders:## Console appenderConsole:name: STDOUTPatternLayout:Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"## RollingFile appenderRollingRandomAccessFile:-name: RollingFilefilename: "${logHome}/${projectName}.log"filePattern: "${logHome}/${projectName}.%d{yyyy-MM-dd}-%i.log.gz"PatternLayout:Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"Policies:SizeBasedTriggeringPolicy:size: 20MBDefaultRollOverStrategy:max: 5Delete:basePath: "/tmp/logs"maxDepth: 1IfFileName:glob: "epg*.log.*"IfLastModified:age: 5d-name: ErrorFilefilename: "${logHome}/${projectName}-error.log"filePattern: "${logHome}/${projectName}-error.%d{yyyy-MM-dd}-%i.log.gz"PatternLayout:Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"Policies:SizeBasedTriggeringPolicy:size: 20MBDefaultRollOverStrategy:max: 5-name: MainFilefilename: "${logHome}/${projectName}-main.log"filePattern: "${logHome}/${projectName}-main.%d{yyyy-MM-dd}-%i.log.gz"thresholdFilter:level: debugPatternLayout:Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"Policies:SizeBasedTriggeringPolicy:size: 20MBDefaultRollOverStrategy:max: 5Loggers:logger:-name: com.examplelevel: debugadditivity: falseAppenderRef:- ref: MainFile- ref: STDOUT-name: "@USER"level: debugadditivity: falseAppenderRef:- ref: MainFile- ref: STDOUT-name: org.hibernate.SQLlevel: warnRoot:level: infoAppenderRef:- ref: STDOUTlevel: error- ref: RollingFilelevel: info- ref: ErrorFilelevel: error

注意:上面代码中的additivity属性(false:只在本logger中输出,不要传递给上级logger;true:不仅在本logger中输出,也会传递给上级,如果本logger和上级logger都指向同一个日志文件,则日志可能会在该文件中打印2次。)

特别提示:各个level的优先级
thresholdFilter:总开关,低于这个级别的日志都不会显示
logger下:logger.level和logger.AppenderRef.level的级别取最低值

5. 日志与行为

一般在行为完成之后才打日志,看到日志就表示该行为已完成。

6. Response模板类

package com.example;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;/*** Response模板类* * @author frcoder*/
@ApiModel(value = "Response", description = "接口响应对象")
public class Response<T> {@ApiModelProperty(value = "编码")@JsonProperty("code")private int code;@ApiModelProperty(value = "消息")@JsonProperty("message")private String message;@ApiModelProperty(value = "数据")@JsonProperty("data")private T data;@JsonIgnoreprivate Exception exception;public static <T> Response<T> ok() {return ok(null, "success");}public static <T> Response<T> ok(T data) {return ok(data, "success");}public static <T> Response<T> ok(T data, String message) {return ok(0, data, message);}public static <T> Response<T> ok(Integer code, T data, String message) {return new Response(code, message, data);}public static <T> Response<T> fail(String message) {return fail(99, message);}public static <T> Response<T> fail(Integer code, String message) {return new Response(code, message);}public static <T> Response<T> failParam(String message) {return fail(400, message);}public static <T> Response<T> error(String message, Exception e) {return error(99, message, e);}public static <T> Response<T> error(Integer code, String message, Exception e) {return new Response(code, message).exception(e);}public Response() {}public Response(int code) {this.code = code;}public Response(int code, String message) {this.code = code;this.message = message;}public Response(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public Response code(int code) {this.code = code;return this;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public Response message(String message) {this.message = message;return this;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Response data(T data) {this.data = data;return this;}public Exception getException() {return exception;}public void setException(Exception exception) {this.exception = exception;}public Response exception(Exception exception) {this.exception = exception;return this;}
}

7. 注解与切面在日志中的应用

1. 在gradle中引入jar包

compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"

2. 编写注解类

package com.example;import java.lang.annotation.*;/*** @Log注解类* * @author frcoder*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {String value() default "";
}
package com.example;import com.example.Response;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import static com.example.LogUtil.userLog;/*** LogAop日志切面类* * @author frcoder*/
@Aspect
@Component
public class LogAop {private static Logger logger = LoggerFactory.getLogger(LogAop.class);/*** api.impl包下的函数,如果参数列表是以userId, role开头的会被记录用户日志*/@Pointcut("execution(public * com..api.impl..*.*(..)) || @annotation(Log))")public void log() {}@Before(value = "log()")public void doBeforeLog(JoinPoint joinPoint) {try {String methodName = joinPoint.getSignature().getName();Map args = AOPUtil.getArgs(joinPoint);if (args.containsKey("userId")) {if (args.containsKey("role")) {LogUtil.newUserLog(args.get("userId"), args.get("role"), methodName);} else {LogUtil.newUserLog(args.get("userId"), methodName);}} else {LogUtil.newUserLog(methodName);}} catch (Exception e) {logger.debug("LogAop doBefore new Log is wrong", e);}}@AfterReturning(value = "log()", returning = "ret")public void doAfterReturningLog(Object ret) {try {Response response = (Response) ret;userLog.info("[{}]: {}", response.getCode(), response.getMessage());if (response.getData() != null) {userLog.debug(StringUtil.Obj2JsonStr(response.getData()));}if (response.getException() != null) {userLog.error(response.getException().toString(), response.getException());}} catch (Exception e) {logger.debug("LogAop doAfterReturning is wrong", e);} finally {LogUtil.userQuit();}}}
package com.example;import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;/*** AOP工具类* * @author frcoder*/
public class AOPUtil {private static Logger logger = LoggerFactory.getLogger(AOPUtil.class);/*** 用于提取切入点参数*/public static Map getArgs(JoinPoint joinPoint) {try {String classType = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();// 获取参数值Object[] args = joinPoint.getArgs();Class<?>[] classes = new Class[args.length];for (int k = 0; k < args.length; k++) {if (!args[k].getClass().isPrimitive()) {// 获取的是封装类型而不是基础类型String result = args[k].getClass().getName();Class s = map.get(result);classes[k] = s == null ? args[k].getClass() : s;}}// 获取方法(第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型)Method method = Class.forName(classType).getMethod(methodName, classes);// 获取参数名ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();String[] parameterNames = pnd.getParameterNames(method);// 通过map封装参数名和参数值HashMap<String, Object> paramMap = new HashMap();for (int i = 0; i < parameterNames.length; i++) {paramMap.put(parameterNames[i], args[i]);}return paramMap;} catch (Exception e) {logger.error("提取切入点参数出错", e);}return Collections.EMPTY_MAP;}private static HashMap<String, Class> map = new HashMap<String, Class>() {{put("java.lang.Integer", Integer.class);put("java.lang.Double", Double.class);put("java.lang.Float", Float.class);put("java.lang.Long", Long.class);put("java.lang.Short", Short.class);put("java.lang.Boolean", Boolean.class);put("java.lang.Char", Character.class);}};}

这篇关于关于日志(slf4j的使用心得)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087684

相关文章

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Qt spdlog日志模块的使用详解

《Qtspdlog日志模块的使用详解》在Qt应用程序开发中,良好的日志系统至关重要,本文将介绍如何使用spdlog1.5.0创建满足以下要求的日志系统,感兴趣的朋友一起看看吧... 目录版本摘要例子logmanager.cpp文件main.cpp文件版本spdlog版本:1.5.0采用1.5.0版本主要

Java中使用Hutool进行AES加密解密的方法举例

《Java中使用Hutool进行AES加密解密的方法举例》AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,下面:本文主要介绍Java中使用Hutool进行AES加密解密的相关资料... 目录前言一、Hutool简介与引入1.1 Hutool简介1.2 引入Hutool二、AES加密解密基础