This is my 15th day of the August Challenge
This series of code address: github.com/HashZhang/s…
The Log4j2 asynchronous logging core is implemented with a RingBuffer. If a large number of logs are generated at a certain time and the write speed is not timely enough to cause the RingBuffer to be full, the business code will block where the logging is called. So we need to monitor the RingBuffer. Log4j2 creates a separate RingBuffer for each AsyncLogger configuration, such as Log4j2:
<! <loggers> <! --default logger --> <Asyncroot level="info" includeLocation="true"> <appender-ref ref="console"/> </Asyncroot> <AsyncLogger name="RocketmqClient" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger> <AsyncLogger name="com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger> <AsyncLogger name="org.mybatis" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger> </loggers>Copy the code
This configuration contains four AsyncLogger AsyncLogger and creates a RingBuffer for each AsyncLogger. Log4j2 also allows for monitoring AsyncLogger, so it exposes the AsyncLogger’s monitoring as an MBean (JMX Managed Bean).
The relevant source code is as follows:
Server.java
private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {/ / obtain log4j2. Loggers in the XML configuration under the label of all configuration values final Map < String, LoggerConfig> map = ctx.getConfiguration().getLoggers(); For (final String name: map.keyset ()) {final LoggerConfig CFG = map.get(name); final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg); // Register a LoggerConfigAdmin register(MBS, MBean, mBean.getobjectName ()) for each Logger; // If asynchronous logging is configured, If (CFG instanceof AsyncLoggerConfig) {final AsyncLoggerConfig Async = (AsyncLoggerConfig) CFG; final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName()); register(mbs, rbmbean, rbmbean.getObjectName()); }}}Copy the code
Create MBean class source: ringBufferAdmin.java
public class RingBufferAdmin implements RingBufferAdminMBean { private final RingBuffer<? > ringBuffer; private final ObjectName objectName; / /... Omit the other code we don't care about the public static final String DOMAIN = "org. Apache. Logging. Log4j2"; String PATTERN_ASYNC_LOGGER_CONFIG = DOMAIN + ":type=%s,component=Loggers,name=%s,subtype=RingBuffer"; Public static RingBufferAdmin forAsyncLoggerConfig(final RingBuffer<? > ringBuffer, final String contextName, final String configName) { final String ctxName = Server.escape(contextName); // For RootLogger, cfgName is an empty String. Final String cfgName = server.escape (configName); final String name = String.format(PATTERN_ASYNC_LOGGER_CONFIG, ctxName, cfgName); return new RingBufferAdmin(ringBuffer, name); } @override public long getBufferSize() {return RingBuffer == null? 0 : ringBuffer.getBufferSize(); } @override public long getRemainingCapacity() {return RingBuffer == null? 0 : ringBuffer.remainingCapacity(); } public ObjectName getObjectName() { return objectName; }}Copy the code
We use Spring Boot in our microservices project and integrate Prometheus. We can expose Prometheus by using the Log4j2 RingBuffer size as an indicator, with the following code:
Corresponding source: log4j2Configuration.java
import io.micrometer.core.instrument.Gauge; import io.micrometer.prometheus.PrometheusMeterRegistry; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.jmx.RingBufferAdminMBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import javax.annotation.PostConstruct; import javax.management.ObjectName; import java.lang.management.ManagementFactory; @log4j2@configuration (proxyBeanMethods = false) // Load only when Prometheus is introduced and the INTERFACE that operates as Prometheus is exposed @ConditionalOnEnabledMetricsExport("prometheus") public class Log4j2Configuration { @Autowired private ObjectProvider<PrometheusMeterRegistry> meterRegistry; Private volatile Boolean isInitialized = false; // The log configuration is initialized before the ApplicationContext is loaded // But Prometheus' related beans are complicated to load. // ApplicationContext may refresh multiple times, such as calling /refresh /, when the ApplicationContext is used as the source of the actuator. // For simplicity, use a simple isInitialized to determine if the initialization is the first time. Ensure initialized only once @ EventListener (ContextRefreshedEvent. Class) public synchronized void init () {if (! // Get LoggerContext from LogManager, LoggerContext = (LoggerContext) logManager.getContext (false); org.apache.logging.log4j.core.config.Configuration configuration = loggerContext.getConfiguration(); // Get the name of the LoggerContext, because the name of the Mbean contains this String ctxName = loggerContext.getName(); Configuration.getloggers ().keyset ().foreach (k -> {try {// For RootLogger, its cfgName is an empty string. We named it root String cfgName = stringutils.isblank (k) in Prometheus? "" : k; String gaugeName = StringUtils.isBlank(k) ? "root" : k; Gauge.builder(gaugeName + "_logger_ring_buffer_remaining_capacity", () - > {try {return (Number) ManagementFactory. GetPlatformMBeanServer (). The getAttribute (new ObjectName (/ / in accordance with the Log4j2 Source of naming the assembly name String. The format (RingBufferAdminMBean PATTERN_ASYNC_LOGGER_CONFIG, ctxName, cfgName) / / get the rest of the size, Note that this is strictly case sensitive), "RemainingCapacity"); } catch (Exception e) { log.error("get {} ring buffer remaining size error", k, e); } return -1; }).register(meterRegistry.getIfAvailable()); } catch (Exception e) { log.error("Log4j2Configuration-init error: {}", e.getMessage(), e); }}); isInitialized = true; }}}Copy the code
Add this code, and after requesting /actuator/ Prometheus, you can see the corresponding return:
# HELP root_logger_ring_buffer_remaining_capacity # TYPE root_logger_ring_buffer_remaining_capacity Gauge Root_logger_ring_buffer_remaining_capacity 262144.0 # HELP org_mybatis_logger_ring_BUFFer_remaining_capacity # TYPE Org_mybatis_logger_ring_buffer_remaining_capacity Gauge ORG_mybatis_logger_ring_BUFFer_remaining_capacity 262144.0 # HELP com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity # TYPE com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity gauge Com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity # 262144.0 HELP RocketmqClient_logger_ring_buffer_remaining_capacity # TYPE RocketmqClient_logger_ring_buffer_remaining_capacity gauge RocketmqClient_logger_ring_buffer_remaining_capacity 262144.0Copy the code
Thus, when this value is 0 for a period of time (indicating that the RingBuffer is full and that log generation is faster than consuming appenders), we consider the application log load to be excessive.
Log4j2 exposes many JMX beans. For example, you can view and modify the JMX beans via JConsole:
However, because JMX contains too much information, and because our servers are all over the world, remote JMX is unstable, so we still operate using the HTTP interface that exposes the ACTUATOR.
First, configure the IP address of the ACTUATOR to expose the logging API via HTTP.
Management: endpoints: # Use JMX: exposure: exclude: '*' include: '*'Copy the code
Request interface GET/ACTUATOR/LoGGERS, and you can see the following return, which log levels are supported by the current logging framework, and the level configuration for each Logger.
{
"levels": [
"OFF",
"FATAL",
"ERROR",
"WARN",
"INFO",
"DEBUG",
"TRACE"
],
"loggers": {
"ROOT": {
"configuredLevel": "WARN",
"effectiveLevel": "WARN"
},
"org.mybatis": {
"configuredLevel": "ERROR",
"effectiveLevel": "ERROR"
}
},
"groups": {
}
}
Copy the code
If we want to add or change the configuration of a Logger, we can customize the Logger name with POST/ACTUATOR /loggers/ and the Body as follows:
{
"configuredLevel": "WARN"
}
Copy the code
This section examines the various logging configurations in our microservices framework in detail, including the basic configuration, link tracing implementation and configuration, and what to do if there is no link tracing, as well as some core configurations that affect performance. Then the RingBuffer monitoring of logs is customized, and the configuration of logs can be dynamically modified through viewing the ACTUATOR. In the next section we will begin to analyze the Web container Undertow for spring-MVC based synchronous microservices usage.
Wechat search “my programming cat” to follow the public account, every day, easy to improve technology, win a variety of offers: