Log4jConfig.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.doris.common;
import org.apache.doris.httpv2.config.SpringLog4j2Config;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.io.IoBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
//
// don't use trace. use INFO, WARN, ERROR, FATAL
//
public class Log4jConfig extends XmlConfiguration {
private static final long serialVersionUID = 1L;
private static StringBuilder xmlConfTemplateBuilder = new StringBuilder();
private static void getXmlConfByStrategy(final String size, final String age) {
if (Config.log_rollover_strategy.equalsIgnoreCase("size")) {
xmlConfTemplateBuilder
.append(" <IfAny>\n")
.append(" <IfAccumulatedFileSize exceeds=\"${").append(size).append("}GB\"/>\n")
.append(" </IfAny>\n");
} else {
// default age
xmlConfTemplateBuilder
.append(" <IfLastModified age=\"${").append(age).append("}\" />\n");
}
}
// Placeholders
private static final String RUNTIME_LOG_FORMAT_PLACEHOLDER = "<!--REPLACED BY LOG FORMAT-->";
private static final String VERBOSE_MODULE_PLACEHOLDER = "<!--REPLACED BY AUDIT AND VERBOSE MODULE NAMES-->";
private static final String CONSOLE_APPENDER_PLACEHOLDER = "<!--REPLACED BY CONSOLE APPENDER-->";
private static final String RUNTIME_LOG_FILE_APPENDER_PLACEHOLDER = "<!--REPLACED BY LOG APPENDER-->";
private static final String RUNTIME_LOG_WARN_FILE_APPENDER_PLACEHOLDER = "<!--REPLACED BY WARN_LOG_APPENDER-->";
private static final String AUDIT_CONSOLE_LOGGER_PLACEHOLDER = "<!--REPLACED BY AUDIT CONSOLE LOGGER-->";
private static final String AUDIT_FILE_LOGGER_PLACEHOLDER = "<!--REPLACED BY AUDIT FILE LOGGER-->";
private static final String RUNTIME_LOG_MARKER_PLACEHOLDER = "<!--REPLACED BY RUNTIME LOG MARKER-->";
private static final String AUDIT_LOG_MARKER_PLACEHOLDER = "<!--REPLACED BY AUDIT LOG MARKER-->";
// Appender names
private static final String RUNTIME_LOG_CONSOLE_APPENDER = "Console";
private static final String RUNTIME_LOG_FILE_APPENDER = "Sys";
private static final String RUNTIME_LOG_WARN_FILE_APPENDER = "SysWF";
private static final String AUDIT_LOG_CONSOLE_APPENDER = "AuditConsole";
private static final String AUDIT_LOG_FILE_APPENDER = "AuditFile";
// Log patterns
private static final String RUNTIME_LOG_PATTERN
= RUNTIME_LOG_MARKER_PLACEHOLDER + "%d{yyyy-MM-dd HH:mm:ss,SSS} %p (%t|%tid)"
+ RUNTIME_LOG_FORMAT_PLACEHOLDER + "%m%n";
private static final String AUDIT_LOG_PATTERN
= AUDIT_LOG_MARKER_PLACEHOLDER + "%d{yyyy-MM-dd HH:mm:ss,SSS} [%c{1}] %m%n";
// Log markers
private static final String RUNTIME_LOG_MARKER = "RuntimeLogger ";
private static final String AUDIT_LOG_MARKER = "AuditLogger ";
// @formatter:off
static {
// CHECKSTYLE OFF
xmlConfTemplateBuilder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
.append("\n<!-- Auto Generated. DO NOT MODIFY IT! -->\n")
.append("<Configuration status=\"info\" packages=\"org.apache.doris.common\">\n")
.append(" <Appenders>\n")
.append(" <Console name=\"" + RUNTIME_LOG_CONSOLE_APPENDER + "\" target=\"SYSTEM_OUT\">\n")
.append(" <PatternLayout charset=\"UTF-8\">\n")
.append(" <Pattern>" + RUNTIME_LOG_PATTERN + "</Pattern>\n")
.append(" </PatternLayout>\n")
.append(" </Console>\n")
.append(" <Console name=\"" + AUDIT_LOG_CONSOLE_APPENDER + "\" target=\"SYSTEM_OUT\">\n")
.append(" <PatternLayout charset=\"UTF-8\">\n")
.append(" <Pattern>" + AUDIT_LOG_PATTERN + "</Pattern>\n")
.append(" </PatternLayout>\n")
.append(" </Console>\n")
.append(" <RollingFile name=\"" + RUNTIME_LOG_FILE_APPENDER + "\" fileName=\"${sys_log_dir}/fe.log\" filePattern=\"${sys_log_dir}/fe.log.${sys_file_pattern}-%i${sys_file_postfix}\" immediateFlush=\"${immediate_flush_flag}\">\n")
.append(" <PatternLayout charset=\"UTF-8\">\n")
.append(" <Pattern>" + RUNTIME_LOG_PATTERN + "</Pattern>\n")
.append(" </PatternLayout>\n")
.append(" <Policies>\n")
.append(" <TimeBasedTriggeringPolicy/>\n")
.append(" <SizeBasedTriggeringPolicy size=\"${sys_roll_maxsize}MB\"/>\n")
.append(" </Policies>\n")
.append(" <DefaultRolloverStrategy max=\"${sys_roll_num}\" fileIndex=\"max\">\n")
.append(" <Delete basePath=\"${sys_log_dir}/\" maxDepth=\"1\">\n")
.append(" <IfFileName glob=\"fe.log.*\" />\n");
getXmlConfByStrategy("info_sys_accumulated_file_size", "sys_log_delete_age");
xmlConfTemplateBuilder
.append(" </Delete>\n")
.append(" </DefaultRolloverStrategy>\n")
.append(" </RollingFile>\n")
.append(" <RollingFile name=\"" + RUNTIME_LOG_WARN_FILE_APPENDER + "\" fileName=\"${sys_log_dir}/fe.warn.log\" filePattern=\"${sys_log_dir}/fe.warn.log.${sys_file_pattern}-%i${sys_file_postfix}\" immediateFlush=\"${immediate_flush_flag}\">\n")
.append(" <PatternLayout charset=\"UTF-8\">\n")
.append(" <Pattern>" + RUNTIME_LOG_PATTERN + "</Pattern>\n")
.append(" </PatternLayout>\n")
.append(" <Policies>\n")
.append(" <TimeBasedTriggeringPolicy/>\n")
.append(" <SizeBasedTriggeringPolicy size=\"${sys_roll_maxsize}MB\"/>\n")
.append(" </Policies>\n")
.append(" <DefaultRolloverStrategy max=\"${sys_roll_num}\" fileIndex=\"max\">\n")
.append(" <Delete basePath=\"${sys_log_dir}/\" maxDepth=\"1\">\n")
.append(" <IfFileName glob=\"fe.warn.log.*\" />\n");
getXmlConfByStrategy("warn_sys_accumulated_file_size", "sys_log_delete_age");
xmlConfTemplateBuilder
.append(" </Delete>\n")
.append(" </DefaultRolloverStrategy>\n")
.append(" </RollingFile>\n")
.append(" <RollingFile name=\"" + AUDIT_LOG_FILE_APPENDER + "\" fileName=\"${audit_log_dir}/fe.audit.log\" filePattern=\"${audit_log_dir}/fe.audit.log.${audit_file_pattern}-%i${audit_file_postfix}\">\n")
.append(" <PatternLayout charset=\"UTF-8\">\n")
.append(" <Pattern>" + AUDIT_LOG_PATTERN + "</Pattern>\n")
.append(" </PatternLayout>\n")
.append(" <Policies>\n")
.append(" <TimeBasedTriggeringPolicy/>\n")
.append(" <SizeBasedTriggeringPolicy size=\"${audit_roll_maxsize}MB\"/>\n")
.append(" </Policies>\n")
.append(" <DefaultRolloverStrategy max=\"${audit_roll_num}\" fileIndex=\"max\">\n")
.append(" <Delete basePath=\"${audit_log_dir}/\" maxDepth=\"1\">\n")
.append(" <IfFileName glob=\"fe.audit.log.*\" />\n");
getXmlConfByStrategy("audit_sys_accumulated_file_size", "audit_log_delete_age");
xmlConfTemplateBuilder
.append(" </Delete>\n")
.append(" </DefaultRolloverStrategy>\n")
.append(" </RollingFile>\n")
.append(" </Appenders>\n")
.append(" <Loggers>\n")
.append(" <Root level=\"${sys_log_level}\" includeLocation=\"${include_location_flag}\">\n")
.append(" " + RUNTIME_LOG_FILE_APPENDER_PLACEHOLDER + "\n")
.append(" " + RUNTIME_LOG_WARN_FILE_APPENDER_PLACEHOLDER + "\n")
.append(" " + CONSOLE_APPENDER_PLACEHOLDER + "\n")
.append(" </Root>\n")
.append(" <Logger name=\"audit\" level=\"ERROR\" additivity=\"false\">\n")
.append(" " + AUDIT_FILE_LOGGER_PLACEHOLDER + "\n")
.append(" " + AUDIT_CONSOLE_LOGGER_PLACEHOLDER + "\n")
.append(" </Logger>\n")
.append(" " + VERBOSE_MODULE_PLACEHOLDER + "\n")
.append(" </Loggers>\n")
.append("</Configuration>");
// CHECKSTYLE ON
}
// @formatter:on
private static StrSubstitutor strSub;
private static String sysLogLevel;
private static String sysLogMode;
private static String[] verboseModules;
private static String[] auditModules;
// save the generated xml conf template
private static String logXmlConfTemplate;
// dir of fe.conf
public static String confDir;
// custom conf dir
public static String customConfDir;
// Doris uses both system.out and log4j to print log messages.
// This variable is used to check whether to add console appender to loggers.
// If doris is running under daemon mode, then this variable == false, and console logger will not be added.
// If doris is not running under daemon mode, then this variable == true, and console logger will be added to
// loggers, all logs will be printed to console.
public static boolean foreground = false;
private static void reconfig() throws IOException {
String newXmlConfTemplate = xmlConfTemplateBuilder.toString();
// sys log config
// ATTN, sys_log_dir is deprecated, use LOG_DIR instead
String sysLogDir = Strings.isNullOrEmpty(Config.sys_log_dir) ? System.getenv("LOG_DIR") :
Config.sys_log_dir;
String sysRollNum = String.valueOf(Config.sys_log_roll_num);
String sysDeleteAge = String.valueOf(Config.sys_log_delete_age);
boolean compressSysLog = Config.sys_log_enable_compress;
if (!(sysLogLevel.equalsIgnoreCase("INFO")
|| sysLogLevel.equalsIgnoreCase("WARN")
|| sysLogLevel.equalsIgnoreCase("ERROR")
|| sysLogLevel.equalsIgnoreCase("FATAL"))) {
throw new IOException("sys_log_level config error");
}
if (!(sysLogMode.equalsIgnoreCase("NORMAL")
|| sysLogMode.equalsIgnoreCase("BRIEF")
|| sysLogMode.equalsIgnoreCase("ASYNC"))) {
throw new IOException("sys_log_mode config error");
}
String sysLogRollPattern = "%d{yyyyMMdd}";
String sysRollMaxSize = String.valueOf(Config.log_roll_size_mb);
if (Config.sys_log_roll_interval.equals("HOUR")) {
sysLogRollPattern = "%d{yyyyMMddHH}";
} else if (Config.sys_log_roll_interval.equals("DAY")) {
sysLogRollPattern = "%d{yyyyMMdd}";
} else {
throw new IOException("sys_log_roll_interval config error: " + Config.sys_log_roll_interval);
}
// audit log config
String auditLogDir = Config.audit_log_dir;
String auditLogRollPattern = "%d{yyyyMMdd}";
String auditRollNum = String.valueOf(Config.audit_log_roll_num);
String auditRollMaxSize = String.valueOf(Config.log_roll_size_mb);
String auditDeleteAge = String.valueOf(Config.audit_log_delete_age);
boolean compressAuditLog = Config.audit_log_enable_compress;
if (Config.audit_log_roll_interval.equals("HOUR")) {
auditLogRollPattern = "%d{yyyyMMddHH}";
} else if (Config.audit_log_roll_interval.equals("DAY")) {
auditLogRollPattern = "%d{yyyyMMdd}";
} else {
throw new IOException("audit_log_roll_interval config error: " + Config.audit_log_roll_interval);
}
// verbose modules and audit log modules
StringBuilder sb = new StringBuilder();
for (String s : verboseModules) {
sb.append("<Logger name='" + s + "' level='DEBUG'/>");
}
for (String s : auditModules) {
sb.append("<Logger name='audit." + s + "' level='INFO'/>");
}
newXmlConfTemplate = newXmlConfTemplate.replaceAll(VERBOSE_MODULE_PLACEHOLDER, sb.toString());
// BRIEF: async, no location
// ASYNC: async, with location
// NORMAL: sync, with location
boolean includeLocation = !sysLogMode.equalsIgnoreCase("BRIEF");
boolean immediateFlush = sysLogMode.equalsIgnoreCase("NORMAL");
if (includeLocation) {
newXmlConfTemplate = newXmlConfTemplate.replaceAll(RUNTIME_LOG_FORMAT_PLACEHOLDER, " [%C{1}.%M():%L] ");
} else {
newXmlConfTemplate = newXmlConfTemplate.replaceAll(RUNTIME_LOG_FORMAT_PLACEHOLDER, " ");
}
if (!immediateFlush) {
newXmlConfTemplate = newXmlConfTemplate.replaceAll("Root", "AsyncRoot");
}
if (Config.enable_file_logger) {
newXmlConfTemplate = newXmlConfTemplate.replaceAll(RUNTIME_LOG_FILE_APPENDER_PLACEHOLDER,
"<AppenderRef ref=\"" + RUNTIME_LOG_FILE_APPENDER + "\"/>\n");
newXmlConfTemplate = newXmlConfTemplate.replaceAll(RUNTIME_LOG_WARN_FILE_APPENDER_PLACEHOLDER,
"<AppenderRef ref=\"" + RUNTIME_LOG_WARN_FILE_APPENDER + "\" level=\"WARN\"/>\n");
newXmlConfTemplate = newXmlConfTemplate.replaceAll(AUDIT_FILE_LOGGER_PLACEHOLDER,
"<AppenderRef ref=\"" + AUDIT_LOG_FILE_APPENDER + "\"/>\n");
}
if (foreground) {
newXmlConfTemplate = newXmlConfTemplate.replaceAll(RUNTIME_LOG_MARKER_PLACEHOLDER, RUNTIME_LOG_MARKER);
newXmlConfTemplate = newXmlConfTemplate.replaceAll(AUDIT_LOG_MARKER_PLACEHOLDER, AUDIT_LOG_MARKER);
newXmlConfTemplate = newXmlConfTemplate.replaceAll(CONSOLE_APPENDER_PLACEHOLDER,
"<AppenderRef ref=\"" + RUNTIME_LOG_CONSOLE_APPENDER + "\"/>\n");
newXmlConfTemplate = newXmlConfTemplate.replaceAll(AUDIT_CONSOLE_LOGGER_PLACEHOLDER,
"<AppenderRef ref=\"" + AUDIT_LOG_CONSOLE_APPENDER + "\"/>\n");
}
Map<String, String> properties = Maps.newHashMap();
properties.put("sys_log_dir", sysLogDir);
properties.put("sys_file_pattern", sysLogRollPattern);
properties.put("sys_roll_maxsize", sysRollMaxSize);
properties.put("sys_roll_num", sysRollNum);
properties.put("sys_log_delete_age", sysDeleteAge);
properties.put("sys_log_level", sysLogLevel);
properties.put("sys_file_postfix", compressSysLog ? ".gz" : "");
properties.put("audit_log_dir", auditLogDir);
properties.put("audit_file_pattern", auditLogRollPattern);
properties.put("audit_roll_maxsize", auditRollMaxSize);
properties.put("audit_roll_num", auditRollNum);
properties.put("audit_log_delete_age", auditDeleteAge);
properties.put("info_sys_accumulated_file_size", String.valueOf(Config.info_sys_accumulated_file_size));
properties.put("warn_sys_accumulated_file_size", String.valueOf(Config.warn_sys_accumulated_file_size));
properties.put("audit_sys_accumulated_file_size", String.valueOf(Config.audit_sys_accumulated_file_size));
properties.put("include_location_flag", Boolean.toString(includeLocation));
properties.put("immediate_flush_flag", Boolean.toString(immediateFlush));
properties.put("audit_file_postfix", compressAuditLog ? ".gz" : "");
strSub = new StrSubstitutor(new Interpolator(properties));
newXmlConfTemplate = strSub.replace(newXmlConfTemplate);
LogUtils.stdout("=====\n" + newXmlConfTemplate + "\n=====");
logXmlConfTemplate = newXmlConfTemplate;
SpringLog4j2Config.writeSpringLogConf(customConfDir);
// new SimpleLog4jConfiguration with xmlConfTemplate
if (newXmlConfTemplate == null || newXmlConfTemplate.isEmpty()) {
throw new IOException("The configuration template is empty!");
}
Log4jConfig config;
try (ByteArrayInputStream bis = new ByteArrayInputStream(newXmlConfTemplate.getBytes(StandardCharsets.UTF_8))) {
ConfigurationSource source = new ConfigurationSource(bis);
config = new Log4jConfig(source);
LoggerContext context = (LoggerContext) LogManager.getContext(LogManager.class.getClassLoader(), false);
context.start(config);
} catch (Exception e) {
throw new IOException("Error occurred while configuring Log4j", e);
}
redirectStd();
}
private static void redirectStd() {
PrintStream logPrintStream = IoBuilder.forLogger(LogManager.getLogger("system.out")).setLevel(Level.INFO)
.buildPrintStream();
System.setOut(logPrintStream);
PrintStream errorPrintStream = IoBuilder.forLogger(LogManager.getLogger("system.err")).setLevel(Level.ERROR)
.buildPrintStream();
System.setErr(errorPrintStream);
}
public static String getLogXmlConfTemplate() {
return logXmlConfTemplate;
}
public static class Tuple<X, Y, Z, U> {
public final X x;
public final Y y;
public final Z z;
public final U u;
public Tuple(X x, Y y, Z z, U u) {
this.x = x;
this.y = y;
this.z = z;
this.u = u;
}
}
@Override
public StrSubstitutor getStrSubstitutor() {
return strSub;
}
public Log4jConfig(final ConfigurationSource configSource) {
super(LoggerContext.getContext(), configSource);
}
public static synchronized void initLogging(String dorisConfDir) throws IOException {
sysLogLevel = Config.sys_log_level;
sysLogMode = Config.sys_log_mode;
verboseModules = Config.sys_log_verbose_modules;
auditModules = Config.audit_log_modules;
confDir = dorisConfDir;
customConfDir = Config.custom_config_dir;
reconfig();
}
public static synchronized Tuple<String, String, String[], String[]> updateLogging(
String level, String mode, String[] verboseNames, String[] auditNames) throws IOException {
boolean toReconfig = false;
if (level != null) {
sysLogLevel = level;
toReconfig = true;
}
if (mode != null) {
sysLogMode = mode;
toReconfig = true;
}
if (verboseNames != null) {
verboseModules = verboseNames;
toReconfig = true;
}
if (auditNames != null) {
auditModules = auditNames;
toReconfig = true;
}
if (toReconfig) {
reconfig();
}
return new Tuple<>(sysLogLevel, sysLogMode, verboseModules, auditModules);
}
}