Brain hole: byte code enhanced log collection scheme

Posted by vahidi on Fri, 08 Nov 2019 09:32:26 +0100

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,

  1. 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
  2. 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

Topics: Java log4j Apache jvm