background
Demand:
java technology stack, to access all the current projects to the log center, the requirements seem relatively simple
But in the process of implementation, there are various problems, such as different departments, different development frameworks and different levels of personnel competence,
-
Scheme selection:
-
Each project of scheme 1 is connected to logstash,
There are all kinds of log frameworks, log4j logback log4j2(apache.logging.log4j), which is very confusing. Moreover, developers need to introduce jars, which may conflict with the existing version of jars and have certain development costs
-
Scheme 2 is based on the existing file log, which is collected by filebeat, analyzed, sent to message queue, and then dumped to elastic search
But the log format is not uniform, kao
-
Scheme 3 is based on bytecode enhancement,
Rewrite the log printing method in log4j logback log4j 2 to intercept Add our own logic, record the log in a controllable form, put the message directly into mq, and then consume by the consumer (it can be logstash or a self-developed processor), and then integrate the program into the basic docker image, and set the path of Java agent with JAVA_OPTS parameter
-
- Implementation:
The choice of bytecode enhancement framework: ASM bytebuddy JVM sandbox, the first two are definitely not used, because: I don't believe my own code, rotten + lazy
So choose JVM sandbox github address
Another reason, class isolation, has no impact on the enhanced project
There is a demo in the project that is easy to understand. It intercepts exceptions, You can view it here
Code above, explain again (log4j)
new EventWatchBuilder(moduleEventWatcher) .onClass("org.apache.log4j.Category") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { //Define an ERROR level (default is consistent with ERROR int errorLevel = 40000; //Get event variable Object event = advice.getParameterArray()[0]; // Get the log level corresponding to event int level = invokeMethod(invokeMethod(event, "getLevel"), "toInt"); // Get the printing time of the log long timeStamp = invokeField(event, "timeStamp"); // Get the formatted string of the log String msg = invokeMethod(event, "getRenderedMessage"); // Get logger name String loggerName = invokeMethod(event, "getLoggerName"); // Get thread name String threadName = invokeMethod(event, "getThreadName"); // If less than the default error level if (level < errorLevel) { // Send log information to local queue and wait for (asynchronous) sending offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { // If it's an error level, define the throwable variable Throwable throwable = null; // Get ThrowableInformation Object throwProxy = invokeMethod(event, "getThrowableInformation"); if (throwProxy != null) { // Get real error information from throwable proxy class throwable = invokeMethod(throwProxy, "getThrowable"); } // Send message with error message to local message queue, to be sent offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); // Access the CAT of the comment, output the error information to the CAT disk for alarm Cat.logError("[ERROR] " + msg, throwable); // Set the current context to have error information for subsequent processing Cat.getManager().setHasError(true); } } catch (Exception ex) { //black hole } } });
The org.apache.log4j.Category.callAppenders method is the log4j framework. The method called before write message is to write the message that matches the set level to the appender. defined in the various configurations. We strengthen the code, which is equivalent to adding a custom appender and entering the data.
Note that reflection is used in it, which is the reflection of cache and the mechanism of JVM sandbox. Because the class loading strategy of classloader can only use reflection at present, after testing, there will be no obvious loss of performance. The following article will post the performance test
Next, logback is strengthened. It is similar to log4j basically
new EventWatchBuilder(moduleEventWatcher) .onClass("ch.qos.logback.classic.Logger") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { int errorLevel = 40000; Object event = advice.getParameterArray()[0]; int level = invokeMethod(invokeMethod(event, "getLevel"), "toInt"); long timeStamp = invokeMethod(event, "getTimeStamp"); String msg = invokeMethod(event, "getFormattedMessage"); String loggerName = invokeMethod(event, "getLoggerName"); String threadName = invokeMethod(event, "getThreadName"); if (level < errorLevel) { offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { Throwable throwable = null; Object throwProxy = invokeMethod(event, "getThrowableProxy"); if (throwProxy != null) { throwable = invokeMethod(throwProxy, "getThrowable"); } offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); Cat.logError("[ERROR] " + msg, throwable); Cat.getManager().setHasError(true); } } catch (Exception ex) { //black hole } } });
Don't explain. Next, log4j2 is similar. The difference is that the level value of log4j2 is different from logback log4j, which is the opposite, and the value is also different, so a conversion is made
new EventWatchBuilder(moduleEventWatcher) .onClass("org.apache.logging.log4j.core.config.LoggerConfig") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { int errorLevel = 40000; Object event = advice.getParameterArray()[0]; int level = invokeMethod(invokeMethod(event, "getLevel"), "intLevel"); if (level >= 500) { level = 10000; } else if (level >= 400) { level = 20000; } else if (level >= 300) { level = 30000; } else if (level >= 200) { level = 40000; } else if (level >= 100) { level = 40000; } else { level = 40000; } long timeStamp = invokeMethod(event, "getTimeMillis"); String msg = invokeMethod(invokeMethod(event, "getMessage"), "getFormattedMessage"); String loggerName = invokeMethod(event, "getLoggerName"); String threadName = invokeMethod(event, "getThreadName"); if (level < errorLevel) { offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { Throwable throwable = invokeMethod(event, "getThrown"); offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); Cat.logError("[ERROR] " + msg, throwable); Cat.getManager().setHasError(true); } } catch (Exception ex) { //black hole } } });
ok, the above is the core code of the third log collection scheme, which will be open source for reference before the completion of this series of articles
Brain hole: bytecode enhancement (2) dynamic log level
Brain hole: byte code enhancement (3) APM scheme buried point analysis
Brain hole: byte code enhancement (4) tomcat access log collection
Brain hole: byte code enhancement (5) solution to business problems
Brain hole: bytecode enhancement (6) performance test