赞
踩
基于slf4j中打印日志的方法比较好用,提取出来用于别处
不管是单体应用还是海量应用,都是从小应用开始慢慢演进。当应用逐渐变大,业务逻辑变得更加复杂之后,系统开始变得不稳定,这时候你需要将系统拆分,或者重构。但改造的成本是相对高昂的,这时候你需要考虑的是人力、物力、时间等等。如果系统还没有到无可救药的地步(还能跑),简单的查错打印日志是再好不过的了。打印日志伴随着字符串拼接的效率问题,一行代码打印System.out.print()
对系统来说轻而易举,但十万到百万甚至千万的字符替换就需要考虑效率更高的替换方式。
如果实际写过企业级应用的人都知道,打印日志都是为了方便我们排查问题。从简单的log
各种级别的output
,到海量日志中通过requestId
或traceId
去跟踪调用链路。
这些日志的拼接方式,都要通过字符串截取、拼接、再组合来实现。
而Java
语言中,常用的日志系统有log4j
、logback
、jul
等。其中为了达到日志系统实现与应用快速替换的方式,采用门面模式和桥接模式实现的slf4j
,都需要面对快速的字符串处理。
该篇文章主要简述slf4j
与log4j2
结合在处理带{}
字符串的替换过程。
这里是基于Maven
项目来演示,首先我们需要写一串简单的代码,并将log4j.properties
文件写入resource
中。pom.xml
文件的dependencies
:
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.7.30</version>
- </dependency>
简单打印:
- import lombok.extern.slf4j.Slf4j;
-
- @Slf4j
- public class Slf4jTest {
- public static void main(String[] args) {
- // 这里使用 lombok 插件的 slf4j 注解
- // log 源码在 org.slf4j.Logger
- // 实际调用为 org.slf4j.impl.Log4jLoggerAdapter ,它实现了一个 wrapper,去包装实现 log4j org.apache.log4j.Logger
- log.info("qqq{}ppp", 123);
- // 打印结果:
- [INFO ] 2020-06-10 22:56:50,872 method:com.test.slf4j.Slf4jTest.main(Slf4jTest.java:9)qqq123ppp
- }
- }
log4j.properties
的配置
- log4j.rootLogger=info,stdout # info 级别的日志,stdout 标志符的打印追加器(appender)生效
-
- log4j.appender.stdout= org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.Target= System.out
- log4j.appender.stdout.layout= org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern= [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%m%n
我们如果debug
代码的话,会发现在调用slf4j
代码的时候,程序已经拼接好了字符串,它的调用链路是
- org.slf4j.impl.Log4jLoggerAdapter#info(java.lang.String, java.lang.Object)
- -> org.slf4j.helpers.MessageFormatter#format(java.lang.String, java.lang.Object)
- -> org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[])
- -> org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[], java.lang.Throwable)
在这里最关键的代码是MessageFormatter
,它实现了一套基于StringBuilder
和字符串动态索引的算法,代码如下:
- int i = 0;
- int j;
- // use string builder for better multicore performance
- // StringBuilder 来提高多核性能,无锁,非线程安全,并提前预支了 50 个字符位
- StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
-
- int L;
- // argArray 是输入替换的 Object 数组,这里是 new Object[]{ 123 }
- for (L = 0; L < argArray.length; L++) {
-
- // DELIM_STR == {} 字符
- 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);
- // 其中附带数组写入,如 boolean[],char[]等,argArray[L] 传入的即使是基本类型,也可以通过自动装配,实现对象类型的转换
- deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
- i = j + 2;
- }
- }
- }
- // append the characters following the last {} pair.
- sbuf.append(messagePattern, i, messagePattern.length());
如果要在项目中对大量字符进行替换拆分,其实可以复用MessageFormatter
这个类。经过了无数次考验,稳定性和效率那必定是很高的了。
所以基于MessageFormatter
重新复写了一个简版的MessageFormatter
只返回字符串:
-
- public class MessageFormatter {
-
- private static final char DELIM_START = '{';
- private static final String DELIM_STR = "{}";
- private static final char ESCAPE_CHAR = '\\';
-
- public static String format(final String messagePattern, final Object... argArray) {
-
- 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 messagePattern;
- } else { // add the tail string which contains no variables and return
- // the result.
- sbuf.append(messagePattern, i, messagePattern.length());
- return sbuf.toString();
- }
- } 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<>());
- i = j + 2;
- }
- } else {
- // normal case
- sbuf.append(messagePattern, i, j);
- deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());
- i = j + 2;
- }
- }
- }
- // append the characters following the last {} pair.
- sbuf.append(messagePattern, i, messagePattern.length());
- return sbuf.toString();
- }
-
- private static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
- return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
- }
-
- private static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
- if (delimeterStartIndex == 0) {
- return false;
- }
- char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
- return potentialEscape == ESCAPE_CHAR;
- }
-
- // 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);
- }
- }
- }
-
-
- 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()]");
- }
-
- }
-
- private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
- sbuf.append('[');
- if (!seenMap.containsKey(a)) {
- seenMap.put(a, null);
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- deeplyAppendParameter(sbuf, a[i], seenMap);
- if (i != len - 1)
- sbuf.append(", ");
- }
- // allow repeats in siblings
- seenMap.remove(a);
- } else {
- sbuf.append("...");
- }
- sbuf.append(']');
- }
-
-
- private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void charArrayAppend(StringBuilder sbuf, char[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void intArrayAppend(StringBuilder sbuf, int[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void longArrayAppend(StringBuilder sbuf, long[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
- sbuf.append('[');
- final int len = a.length;
- for (int i = 0; i < len; i++) {
- sbuf.append(a[i]);
- if (i != len - 1)
- sbuf.append(", ");
- }
- sbuf.append(']');
- }
-
- }
测试结果:
- System.out.println(MessageFormatter.format("qqq{}ppp{}end", 123, 321));
- 打印结果:qqq123ppp321end
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。