Logging best practices
<properties> <slf4j.version>1.7.30</slf4j.version> <log4j.version>2.13.3</log4j.version> </properties> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies>logback 日志配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 日志文件目录 --> <property name="log.location" value="/export/Logs/《《APP_NAME》》"/> <!-- 日志样式 --> <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80}[%L] - %msg%n"/> <!-- 将 JUL 的日志级别映射为 logback 的日志级别 --> <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>${log.pattern}</pattern> </encoder> </appender> <!-- 如果使用 Spring Boot 发布应用,则需要配置该 Appender。 --> <!-- 如果使用 Tomcat 发布应用,则不需要配置该 Appender。 --> <appender name="CATALINA" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 如果配置了该 Appender,则在 digger 中只需要添加该日志文件路径即可。 --> <file>${log.location}/catalina.log</file> <append>true</append> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.location}/catalina.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>100</maxHistory> </rollingPolicy> <encoder charset="UTF-8"> <pattern>${log.pattern}</pattern> </encoder> </appender> <appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.location}/app.log</file> <append>true</append> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${log.location}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>30GB</totalSizeCap> </rollingPolicy> <encoder charset="UTF-8"> <pattern>${log.pattern}</pattern> </encoder> </appender> <logger name="com.xxx" level="WARN"/> <root level="INFO"> <!-- 堆代码 duidaima.com --> <!-- 下面三选一即可。 --> <!-- 如果使用 Tomcat 发布,如果想要 Tomcat 的日志,则使用则留下 CONSOLE。 --> <!-- 如果使用 Tomcat 发布,如果不想要 Tomcat 的日志,则使用则留下 APP。 --> <!-- 如果使用 Spring Boot 发布,则留下 CATALINA; --> <appender-ref ref="CONSOLE"/> <appender-ref ref="APP"/> <appender-ref ref="CATALINA"/> </root> </configuration>
<properties> <slf4j.version>1.7.30</slf4j.version> <log4j.version>2.13.3</log4j.version> </properties> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jul</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j.version}</version> </dependency> </dependencies>logback 日志配置
// TODO
// 错误示例:无论日志是否打印出来,都会执行 toJson 操作,但是 toJson 耗时很高。 log.info("响应结果: {}", toJson(proceed)); // 正确示例 if (log.isInfoEnabled()) { log.info("响应结果:{}", toJson(proceed)); } // 错误实例:手动提前异常消息 log.error("error param: {}, result: {}, exception: {}", toJson(param), toJson(result), exception.getMessage()); // 正确实例 log.error("error param: {}, result: {}", toJson(paramDto), toJson(result), exception); // 错误示例:无意义前缀 log.info("##### 昵称: {}", name)); // 正确示例 log.info("昵称: {}", name));
package org.slf4j.helpers; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; final public class MessageFormatter { static final char DELIM_START = '{'; static final char DELIM_STOP = '}'; static final String DELIM_STR = "{}"; private static final char ESCAPE_CHAR = '\\'; // 堆代码 duidaima.com // 日志参数拼接的最终实现 final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; // use string builder for better multicore performance StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int L; for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented sbuf.append(messagePattern, i, j - 1); sbuf.append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash sbuf.append(messagePattern, i, j - 1); deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); i = j + 2; } } else { // normal case sbuf.append(messagePattern, i, j); // 判断需要拼接,这调用工具方法,进行参数拼接 deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); i = j + 2; } } } // append the characters following the last {} pair. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } // special treatment of array values was suggested by 'lizongbo' private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) { if (o == null) { sbuf.append("null"); return; } if (!o.getClass().isArray()) { // 简单对象直接拼接 safeObjectAppend(sbuf, o); } else { // check for primitive array types because they // unfortunately cannot be cast to Object[] if (o instanceof boolean[]) { booleanArrayAppend(sbuf, (boolean[]) o); } else if (o instanceof byte[]) { byteArrayAppend(sbuf, (byte[]) o); } else if (o instanceof char[]) { charArrayAppend(sbuf, (char[]) o); } else if (o instanceof short[]) { shortArrayAppend(sbuf, (short[]) o); } else if (o instanceof int[]) { intArrayAppend(sbuf, (int[]) o); } else if (o instanceof long[]) { longArrayAppend(sbuf, (long[]) o); } else if (o instanceof float[]) { floatArrayAppend(sbuf, (float[]) o); } else if (o instanceof double[]) { doubleArrayAppend(sbuf, (double[]) o); } else { objectArrayAppend(sbuf, (Object[]) o, seenMap); } } } // 基于 append 来实现日志参数拼接 private static void safeObjectAppend(StringBuilder sbuf, Object o) { try { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { Util.report("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t); sbuf.append("[FAILED toString()]"); } } }
public class TraceMessageConverter extends MessageConverter { @Override public String convert(ILoggingEvent event) { // 获取 span long traceId = span.getTraceId(); long spanId = span.getSpanId(); String msg = super.convert(event); return "traceId=" + traceId + ", spanId=" + spanId + ", " + msg; } }这样的话,如果能打通分布式追踪系统和日志系统的关联,就可以将分布式追踪和日志关联起来了,方便排查问题。