Influence of log4j2 log PatternLayout configuration on SOFAArk PluginClassLoader

background

"Log4j2-TF-7-AsyncLoggerConfig-8" Id=52 BLOCKED on sun.misc.URLClassPath@26eb1b56 owned by "Log4j2-TF-7-AsyncLoggerConfig-4" Id=17
    at sun.misc.URLClassPath.getNextLoader(URLClassPath.java:479)
    -  blocked on sun.misc.URLClassPath@26eb1b56
    at sun.misc.URLClassPath.getResource(URLClassPath.java:248)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:448)
    at com.alipay.sofa.ark.container.service.classloader.AbstractClasspathClassLoader.resolveLocalClass(AbstractClasspathClassLoader.java:302)
    at com.alipay.sofa.ark.container.service.classloader.PluginClassLoader.loadClassInternal(PluginClassLoader.java:102)
	  // Plug in loading  
		at com.alipay.sofa.ark.container.service.classloader.AbstractClasspathClassLoader.loadClass(AbstractClasspathClassLoader.java:71)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
    at org.apache.logging.log4j.core.impl.ThrowableProxy.loadClass(ThrowableProxy.java:563)
    at org.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace(ThrowableProxy.java:689)
    at org.apache.logging.log4j.core.impl.ThrowableProxy.<init>(ThrowableProxy.java:138)
    at org.apache.logging.log4j.core.impl.ThrowableProxy.<init>(ThrowableProxy.java:122)
    at org.apache.logging.log4j.core.impl.Log4jLogEvent.getThrownProxy(Log4jLogEvent.java:564)
    at org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter.format(ExtendedThrowablePatternConverter.java:63)
    at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
    at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:333)
    at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:232)
    at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:217)
    at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:177)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:170)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:161)
    at org.apache.logging.log4j.core.appender.RollingFileAppender.append(RollingFileAppender.java:268)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:448)
    at org.apache.logging.log4j.core.async.AsyncLoggerConfig.asyncCallAppenders(AsyncLoggerConfig.java:115)
    at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:112)
    at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:98)
    at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:128)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:627)
    at java.lang.Thread.run(Thread.java:861)
Copy code

Log4j2 related classes are exported in sofaark plug-in. Therefore, when log4j2 classes need to be loaded, they will be entrusted to PluginClassLoader for loading.

Configuring% throwable in PatternLayout has different effects on converters

SOFABoot starts loading log4j2 the configuration file

Parse the configuration file and create different PCs according to each configuration in PatternLayout

Rule mapping list

Find the corresponding Converter, create this object through reflection, and then place it in patternConverters.

Under what circumstances will an extended throwablepatternconverter be generated?

There are two conditions that will produce this Converter

Configure "xex", "xtrowable", "xexception"

If the PatternLayout in the configuration file of log4j2 is configured with "xex", "xtrowable" and "xexception", it can be known from the above analysis that when parsing, where the rules match the corresponding converterName, it will be created.

Created when% throwable is not configured

Why? Let's look at the next code:

// Create a corresponding PatternFormatter for PatternLayout to output the log according to the specified rules.
// PatternFormatter depends on Converter to execute format

// 1.alwaysWriteExceptions is true by default and can only be configured and modified in the configuration file.
public List<PatternFormatter> parse(final String pattern, final boolean alwaysWriteExceptions,
           final boolean disableAnsi, final boolean noConsoleNoAnsi) {
    final List<PatternFormatter> list = new ArrayList<>();
    final List<PatternConverter> converters = new ArrayList<>();
    final List<FormattingInfo> fields = new ArrayList<>();

    parse(pattern, converters, fields, disableAnsi, noConsoleNoAnsi, true);

    final Iterator<FormattingInfo> fieldIter = fields.iterator();
    boolean handlesThrowable = false;
	// The collection of all converters corresponding to the current Logger. The converters here are based on the above parse pattern 
    // Generated by the style of pattern,
    for (final PatternConverter converter : converters) {
        if (converter instanceof NanoTimePatternConverter) {
            // LOG4J2-1074 Switch to actual clock if nanosecond timestamps are required in config.
            // LOG4J2-1248 set config nanoclock
            if (config != null) {
                config.setNanoClock(new SystemNanoClock());
            }
        }
        LogEventPatternConverter pc;
        if (converter instanceof LogEventPatternConverter) { 
            pc = (LogEventPatternConverter) converter;
            // At present, the official convert is, and the handlesThrowable of Throwable returns true
            // Therefore, if there is no% throwable in the pattern, false will always be returned here
            handlesThrowable |= pc.handlesThrowable();
        } else {
            pc = new LiteralPatternConverter(config, Strings.EMPTY, true);
        }

        FormattingInfo field;
        if (fieldIter.hasNext()) {
            field = fieldIter.next();
        } else {
            field = FormattingInfo.getDefault();
        }
        list.add(new PatternFormatter(pc, field));
    }
	// alwaysWriteExceptions = true
    // handlesThrowable is true when configuring% throwable, and others are false
    // That is, when% throwable is not configured for the current Logger, an ExtendedThrowablePatternConverter will be created
    // As the default exception handling Converter
    if (alwaysWriteExceptions && !handlesThrowable) {
        final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(config, null);
        list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
    }
    return list;
}
Copy code

Conclusion: different types of patternconverters are generated according to different element types in the pattern. If% throwable is configured in the log file, a ThrowablePatternConverter will be generated correspondingly. If% throwable is not configured, the extended ThrowablePatternConverter is used by default.

converter difference analysis

Different patternconverters are used to format the corresponding data during log output, so the core method of corresponding PatternConverter is the format method. Let's take a look at the difference between the format methods of ThrowablePatternConverter and extended ThrowablePatternConverter.

ThrowablePatternConverter#format

@Override
public void format(final LogEvent event, final StringBuilder buffer) {
    final Throwable t = event.getThrown();

    if (isSubShortOption()) {
        formatSubShortOption(t, getSuffix(event), buffer);
    }
    else if (t != null && options.anyLines()) {
        formatOption(t, getSuffix(event), buffer);
    }
}
Copy code

The format option is to write the Throwable stackTracer to the buffer

ExtendedThrowablePatternConverter#format

@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
    // There is an extra getThrownProxy here
    final ThrowableProxy proxy = event.getThrownProxy();
    final Throwable throwable = event.getThrown();
    if ((throwable != null || proxy != null) && options.anyLines()) {
        if (proxy == null) {
            super.format(event, toAppendTo);
            return;
        }
        String suffix = getSuffix(event);
        final String extStackTrace = proxy.getExtendedStackTraceAsString(options.getIgnorePackages(), options.getTextRenderer(), suffix);
        final int len = toAppendTo.length();
        if (len > 0 && !Character.isWhitespace(toAppendTo.charAt(len - 1))) {
            toAppendTo.append(' ');
        }
        if (!options.allLines() || !Strings.LINE_SEPARATOR.equals(options.getSeparator())) {
            final StringBuilder sb = new StringBuilder();
            final String[] array = extStackTrace.split(Strings.LINE_SEPARATOR);
            final int limit = options.minLines(array.length) - 1;
            for (int i = 0; i <= limit; ++i) {
                sb.append(array[i]);
                if (i < limit) {
                    sb.append(options.getSeparator());
                }
            }
            toAppendTo.append(sb.toString());
        } else {
            toAppendTo.append(extStackTrace);
        }
    }
}
Copy code

Final ThrowableProxy = event. Getthrownproxy() combined with the uppermost exception stack, when building the ThrowableProxy object instance, it will serialize the data in the stack, build CacheEntry (toextended stacktrace), and trigger the class loading action.

A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other fields of the proxy like the message and stack trace.

Attachment 1: waiting strategy for Disruptor consumers

-DAsyncLoggerConfig.WaitStrategy=xxx

name

measures

scene

BlockingWaitStrategy

Lock

Scenarios where CPU resources are scarce and throughput and latency are not important

BusySpinWaitStrategy

spin

By continuously retrying, the system calls caused by switching threads are reduced and the latency is reduced. It is recommended to use when the thread is bound to a fixed CPU

PhasedBackoffWaitStrategy

Spin + yield + custom policy

Scenarios where CPU resources are scarce and throughput and latency are not important

SleepingWaitStrategy

Spin + yield + sleep

There is a good tradeoff between performance and CPU resources. Uneven delay

TimeoutBlockingWaitStrategy

Lock with timeout limit

Scenarios where CPU resources are scarce and throughput and latency are not important

YieldingWaitStrategy

Spin + yield + spin

There is a good tradeoff between performance and CPU resources. The delay is relatively uniform

Dark shading is the default policy

Attachment 2: the length of the Disruptor queue and configure the queue blocking and discarding policy

  • -Dlog4j2.AsyncQueueFullPolicy=Default/Discard (default)
  • -Dlog4j2.DiscardThreshold=ERROR/INFO /... (default info)

1. DefaultAsyncQueueFullPolicy ---- wait queue, turn to synchronous operation policy (default)

public class DefaultAsyncQueueFullPolicy implements AsyncQueueFullPolicy {
    @Override
    public EventRoute getRoute(final long backgroundThreadId, final Level level) {

        // LOG4J2-1518: prevent deadlock when RingBuffer is full and object being logged calls
        // Logger.log in application thread
        // See also LOG4J2-471: prevent deadlock when RingBuffer is full and object
        // being logged calls Logger.log() from its toString() method in background thread
        return EventRoute.SYNCHRONOUS;
    }
}
Copy code

2. Discarding asyncqueuefullpolicy --- discards the log policy according to the log level

@Override
public EventRoute getRoute(final long backgroundThreadId, final Level level) {
    if (level.isLessSpecificThan(thresholdLevel)) {
        if (discardCount.getAndIncrement() == 0) {
            LOGGER.warn("Async queue is full, discarding event with level {}. " +
                        "This message will only appear once; future events from {} " +
                        "are silently discarded until queue capacity becomes available.",
                        level, thresholdLevel);
        }
        return EventRoute.DISCARD;
    }
    return super.getRoute(backgroundThreadId, level);
}
Copy code

Attachment 3: default queue size of Disruptor

  • -DAsyncLogger.RingBufferSize=xxx
// propertyName -> AsyncLoggerConfig.RingBufferSize
static int calculateRingBufferSize(final String propertyName) {
    // Constants.enable_threadlocations defaults to false
    // RINGBUFFER_NO_GC_DEFAULT_SIZE 4096
    // 256 * 1024=262144
    int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE;
    final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
                                                                                        String.valueOf(ringBufferSize));
    try {
        int size = Integer.parseInt(userPreferredRBSize);
        // RINGBUFFER_MIN_SIZE=128
        if (size < RINGBUFFER_MIN_SIZE) {
            size = RINGBUFFER_MIN_SIZE;
            LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize,
                        RINGBUFFER_MIN_SIZE);
        }
        ringBufferSize = size;
    } catch (final Exception ex) {
        LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
    }
    return Integers.ceilingNextPowerOfTwo(ringBufferSize);
}
Copy code

Constant.enable_threadlocations is divided into web and non web applications. For non web applications, it is true by default, and for web applications, it is false by default. The judgment basis is whether the classpath has a javax.servlet.Servlet class. It can be set manually through - Dlog4j2.is.webapp=true/false.

ps: -Dlog4j2.enable.threadlocals: This system property can be used to switch off the use of threadlocals, which will partly disable Log4j's garbage-free behaviour: to be fully garbage-free, Log4j stores objects in ThreadLocal fields to reuse them, otherwise new objects are created for each log event. Note that this property is not effective when Log4j detects it is running in a web application.

Posted by deljhp on Mon, 01 Nov 2021 23:01:45 -0700