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>