RoketMQ日志原理

RoketMQ日志代码所在的子工程为rocketmq-logging,该子工程完成以下几个功能

  • rocketmq实现了一套简易的内部日志接口,通过org.apache.rocketmq.logging.InnerLoggerFactory获取自己内部实现的日志类;
  • org.apache.rocketmq.logging.Slf4jLoggerFactory获取以SLF4j做为门面的日志类(主要包括log4j1, log4j2和logback等),rocketmq默认采用logback作为日志框架,与log4j1, log4j2不一样,logback不需要指定配置文件照样可以打印出日志;
  • org.apache.rocketmq.logging.InternalLoggerFactory主要的功能是根据其内部变量InternalLoggerFactory#loggerType来判断是采用内部的日志还是外部日志,默认使用外部日志框架Log4j.
  • 日志的获取主要是在InternalLoggerFactory这个类中。
public abstract class InternalLoggerFactory {
    public static final String LOGGER_SLF4J = "slf4j";
    public static final String LOGGER_INNER = "inner";
    public static final String DEFAULT_LOGGER = LOGGER_SLF4J;
    private static String loggerType = null;
    private static ConcurrentHashMap<String, InternalLoggerFactory> loggerFactoryCache = new ConcurrentHashMap<String, InternalLoggerFactory>();

    private static InternalLoggerFactory getLoggerFactory() {
        InternalLoggerFactory internalLoggerFactory = null;
        if (loggerType != null) {
            internalLoggerFactory = loggerFactoryCache.get(loggerType);
        }
        if (internalLoggerFactory == null) {
            internalLoggerFactory = loggerFactoryCache.get(DEFAULT_LOGGER);
        }
        if (internalLoggerFactory == null) {
            internalLoggerFactory = loggerFactoryCache.get(LOGGER_INNER);
        }
        if (internalLoggerFactory == null) {
            throw new RuntimeException("[RocketMQ] Logger init failed, please check logger");
        }
        return internalLoggerFactory;
    }

    public static void setCurrentLoggerType(String type) {
        loggerType = type;
    }
    ...
    ...
    ...
}

服务端日志框架使用

这里所谓的服务端包括broker和nameserver,在broker的启动类org.apache.rocketmq.broker.BrokerStartup可以看到下面代码:

        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(lc);
        lc.reset();
        configurator.doConfigure(brokerConfig.getRocketmqHome() + "/conf/logback_broker.xml");
        ...
        ...
        InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);

整个org.apache.rocketmq.broker.BrokerStartup并没有对InternalLoggerFactory#loggerType设置值,可以看到服务端默认是采用了三方日志框架,日志的配置文件在"$ROCKET_HOME/conf"这个目录中。

客户端日志框架的使用

以producer为例进行说明,下面是一个简单的Producer代码;

    public static void main(String[] args) throws MQClientException, InterruptedException {
        producer.start();

        for (int i = 0; i < 10; i++) {
            try {
                Message msg = new Message("TopicTest" /* Topic */,
                        "TagA" /* Tag */,
                        ("Hello GJMZ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                SendResult sendResult = producer.send(msg);

                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }

运行上述代码,日志主要是打印在~/logs/rocketmqlogs/rocketmq_client.log这个文件中。客户端的日志类是在org.apache.rocketmq.client.producer.DefaultMQProducer这个类中获得,

        private final InternalLogger log = ClientLogger.getLog();

ClientLogger部分代码如下,可以看到客户端是以rocketmq.client.logUseSlf4j这个系统变量为开关,来确定日志框架的使用,rocketmq.client.logUseSlf4j = false则使用内部日志框架,默认使用内部日志框架,而内部日志框架打印的日志目录为~/logs/rocketmqlogs/rocketmq_client.log

    public class ClientLogger {
        public static final String CLIENT_LOG_USESLF4J = "rocketmq.client.logUseSlf4j";
        private static final InternalLogger CLIENT_LOGGER;
        private static final boolean CLIENT_USE_SLF4J;

        static {
            CLIENT_USE_SLF4J = Boolean.parseBoolean(System.getProperty(CLIENT_LOG_USESLF4J, "false"));
            if (!CLIENT_USE_SLF4J) {
                InternalLoggerFactory.setCurrentLoggerType(InnerLoggerFactory.LOGGER_INNER);
                CLIENT_LOGGER = createLogger(LoggerName.CLIENT_LOGGER_NAME);
                createLogger(LoggerName.COMMON_LOGGER_NAME);
                createLogger(RemotingHelper.ROCKETMQ_REMOTING);
            } else {
                CLIENT_LOGGER = InternalLoggerFactory.getLogger(LoggerName.CLIENT_LOGGER_NAME);
        }
        
        public static InternalLogger getLog() {
            return CLIENT_LOGGER;
        }   

        private static synchronized Appender createClientAppender() {
            String clientLogRoot = System.getProperty(CLIENT_LOG_ROOT, System.getProperty("user.home") + "/logs/rocketmqlogs");
            String clientLogMaxIndex = System.getProperty(CLIENT_LOG_MAXINDEX, "10");
            String clientLogFileName = System.getProperty(CLIENT_LOG_FILENAME, "rocketmq_client.log");
            String maxFileSize = System.getProperty(CLIENT_LOG_FILESIZE, "1073741824");
            String asyncQueueSize = System.getProperty(CLIENT_LOG_ASYNC_QUEUESIZE, "1024");

            String logFileName = clientLogRoot + "/" + clientLogFileName;

            int maxFileIndex = Integer.parseInt(clientLogMaxIndex);
            int queueSize = Integer.parseInt(asyncQueueSize);
    
            Layout layout = LoggingBuilder.newLayoutBuilder().withDefaultLayout().build();
    
            Appender rocketmqClientAppender = LoggingBuilder.newAppenderBuilder()
                .withRollingFileAppender(logFileName, maxFileSize, maxFileIndex)
                .withAsync(false, queueSize).withName(ROCKETMQ_CLIENT_APPENDER_NAME).withLayout(layout).build();

            Logger.getRootLogger().addAppender(rocketmqClientAppender);
            return rocketmqClientAppender;
        }       
    }

默认使用内部日志的时候,控制台也打印出了少许的日志,如下。这个主要是Netty日志框架所打印出的相关信息,第一行是Netty内部发现pom所引入的jar包中有SLF4J,采用SLF4J作为Netty的日志输出框架。但是rocketmq对Netty的日志框架做了一个适配,主要逻辑是在org.apache.rocketmq.remoting.netty.NettyLogger这个类中。当采用内部日志框架的时候,rocketmq对Netty的适配并没有做的很好,并没有在初始化Netty Log的时候指定一个Appender,所以会打印出2,3行的警告,进而Netty内部中所有的日志都不会打印出来。

        16:58:11.391 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging framework
        RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
        RocketMQLog:WARN Please initialize the logger system properly.

在自己的客户端maven工程中引入logback的pom,并且将rocketmq.client.logUseSlf4j设置为true,这样控制台就会打印出所有的日志包括Netty内部的日志(logback不需要指定配置文件即可正常运行)

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容