提交 784e94b7 编写于 作者: 如梦技术's avatar 如梦技术 🐛

优化 mica-logging 组件。

上级 e9d28643
......@@ -23,6 +23,8 @@ import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.type.MapType;
import lombok.experimental.UtilityClass;
......@@ -694,6 +696,24 @@ public class JsonUtil {
}
}
/**
* 创建 ObjectNode
*
* @return ObjectNode
*/
public static ObjectNode createObjectNode() {
return getInstance().createObjectNode();
}
/**
* 创建 ArrayNode
*
* @return ArrayNode
*/
public static ArrayNode createArrayNode() {
return getInstance().createArrayNode();
}
/**
* 获取 ObjectMapper 实例
*
......
......@@ -2,6 +2,8 @@ package net.dreamlu.mica.core.utils;
import org.springframework.lang.Nullable;
import java.util.Objects;
/**
* 系统工具类
*
......@@ -69,9 +71,67 @@ public class SystemUtil {
/**
* 代码部署于 linux 上,工作默认为 mac 和 Windows
*
* @return boolean
*/
public static boolean isLocalDev() {
return !SystemUtil.isLinux();
}
/**
* 读取 System Property
*
* @param key key
* @return value
*/
@Nullable
public static String getProp(String key) {
return System.getProperty(key);
}
/**
* 读取 System Property
*
* @param key key
* @param defValue 默认值
* @return value
*/
public static String getProp(String key, String defValue) {
return System.getProperty(key, defValue);
}
/**
* 读取 System Property
*
* @param key key
* @param defaultValue defaultValue
* @return value
*/
public static int getPropToInt(String key, int defaultValue) {
return ObjectUtil.toInt(getProp(key), defaultValue);
}
/**
* 读取 System Property
*
* @param key key
* @param defaultValue defaultValue
* @return value
*/
public static boolean getPropToBool(String key, boolean defaultValue) {
return Objects.requireNonNull(ObjectUtil.toBoolean(getProp(key), defaultValue));
}
/**
* 读取 System Property 或者 Env
*
* @param key key
* @return value
*/
@Nullable
public static String getPropOrEnv(String key) {
String value = System.getProperty(key);
return value == null ? System.getenv(key) : value;
}
}
......@@ -2,8 +2,9 @@
## 功能
1. 默认日志配置。
2. logstash 日志收集。
3. 启动完成关闭控制台日志。
2. 打印 json 日志文件。
3. logstash 日志收集。
4. 启动完成关闭控制台日志。
## 依赖引用
### maven
......@@ -20,21 +21,6 @@
compile("net.dreamlu:mica-logging:${version}")
```
## 可选依赖
### maven
```xml
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${version}</version>
</dependency>
```
### gradle
```groovy
compile("net.logstash.logback:logstash-logback-encoder:${version}")
```
## 配置
**特别注意:** 需要配置`服务名``环境`,例如:
......@@ -48,12 +34,12 @@ spring:
| 配置项 | 默认值 | 说明 |
| ----- | ------ | ------ |
| mica.logging.console.enabled | true | 是否开启控制台日志,默认**开启**。设置为`关闭`时,启动完成后将自动关闭控制台日志 |
| mica.logging.logstash.enabled | false | 是否开启 logstash 日志收集 |
| mica.logging.use-json-format | false | 使用 json 格式化,设置后文件打印 json 日志,可用于 filebeat 收集日志文件 |
| mica.logging.console.close-after-start | false | 是否启动完成后将自动关闭控制台日志,默认**false****非开发环境**建议设置为 **true** |
| mica.logging.logstash.enabled | false | 是否开启 logstash 日志收集,直接收集到 logstash |
| mica.logging.logstash.host | localhost | logstash host |
| mica.logging.logstash.port | 5000 | logstash port |
| mica.logging.logstash.queue-size | 512 | logstash 队列大小 |
| mica.logging.use-json-format | false | 使用 json 格式化 |
## 参考
- [jhipster](https://github.com/jhipster/jhipster)
\ No newline at end of file
dependencies {
api project(":mica-core")
api "net.logstash.logback:logstash-logback-encoder:${logstashVersion}"
api "org.springframework.boot:spring-boot-starter-logging"
implementation "org.springframework.boot:spring-boot"
implementation "net.logstash.logback:logstash-logback-encoder:${logstashVersion}"
compileOnly "org.springframework.cloud:spring-cloud-context"
compileOnly "net.dreamlu:mica-auto:${micaAutoVersion}"
annotationProcessor "net.dreamlu:mica-auto:${micaAutoVersion}"
......
......@@ -34,7 +34,7 @@ public class LoggingInitializer implements ApplicationContextInitializer<Configu
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
// 读取系统配置的日志目录,默认为项目下 logs
String logBase = environment.getProperty("logging.file.path", "logs");
String logBase = environment.getProperty("logging.file.path", LoggingUtil.DEFAULT_LOG_DIR);
// 用于 spring boot admin 中展示日志
System.setProperty("logging.file.name", logBase + "/${spring.application.name}/all.log");
}
......
......@@ -17,7 +17,6 @@
package net.dreamlu.mica.logging.config;
import lombok.RequiredArgsConstructor;
import net.dreamlu.mica.core.utils.ObjectUtil;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
......@@ -36,15 +35,9 @@ public class LoggingStartedEventListener {
@Order
@EventListener(WebServerInitializedEvent.class)
public void afterStart() {
// 1. 如果开启 elk,关闭文件日志打印
MicaLoggingProperties.Logstash logStash = properties.getLogstash();
if (ObjectUtil.isTrue(logStash.isEnabled())) {
LoggingUtil.detachAppender(LoggingUtil.FILE_APPENDER_NAME);
LoggingUtil.detachAppender(LoggingUtil.FILE_ERROR_APPENDER_NAME);
}
// 2. 关闭控制台
// 1. 关闭控制台
MicaLoggingProperties.Console console = properties.getConsole();
if (ObjectUtil.isFalse(console.isEnabled())) {
if (console.isCloseAfterStart()) {
LoggingUtil.detachAppender(LoggingUtil.CONSOLE_APPENDER_NAME);
}
}
......
......@@ -22,12 +22,20 @@ package net.dreamlu.mica.logging.config;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.FileSize;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.SystemUtil;
import net.logstash.logback.appender.LogstashTcpSocketAppender;
import net.logstash.logback.composite.ContextJsonProvider;
import net.logstash.logback.composite.GlobalCustomFieldsJsonProvider;
......@@ -36,17 +44,24 @@ import net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder;
import net.logstash.logback.encoder.LogstashEncoder;
import net.logstash.logback.stacktrace.ShortenedThrowableConverter;
import org.slf4j.LoggerFactory;
import org.springframework.boot.logging.LoggingSystemProperties;
import org.springframework.boot.logging.logback.LogbackLoggingSystemProperties;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
/**
* 参考自 jhipster
*
* <p>
* Utility methods to add appenders to a {@link LoggerContext}.
*
* @author jhipster
* @author L.cm
*/
@Slf4j
@UtilityClass
public class LoggingUtil {
public static final String DEFAULT_LOG_DIR = "logs";
public static final String CONSOLE_APPENDER_NAME = "CONSOLE";
public static final String FILE_APPENDER_NAME = "FILE";
public static final String FILE_ERROR_APPENDER_NAME = "FILE_ERROR";
......@@ -68,7 +83,8 @@ public class LoggingUtil {
* @param context a {@link LoggerContext} object.
* @param customFields a {@link String} object.
*/
public static void addJsonConsoleAppender(LoggerContext context, String customFields) {
public static void addJsonConsoleAppender(LoggerContext context,
String customFields) {
log.info("Initializing Console loggingProperties");
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
......@@ -80,23 +96,79 @@ public class LoggingUtil {
context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(consoleAppender);
}
/**
* <p>addJsonConsoleAppender.</p>
*
* @param context a {@link LoggerContext} object.
* @param customFields a {@link String} object.
*/
public static void addAllFileAppender(LoggerContext context,
String logFile,
boolean useJsonFormat,
String customFields) {
log.info("Initializing {} file loggingProperties", logFile);
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
RollingFileAppender<ILoggingEvent> allFileAppender = new RollingFileAppender<>();
allFileAppender.setContext(context);
if (useJsonFormat) {
allFileAppender.setEncoder(compositeJsonEncoder(context, customFields));
} else {
allFileAppender.setEncoder(patternLayoutEncoder(context));
}
allFileAppender.setName(FILE_APPENDER_NAME);
allFileAppender.setFile(logFile);
allFileAppender.setRollingPolicy(rollingPolicy(context, logFile));
allFileAppender.start();
context.getLogger(Logger.ROOT_LOGGER_NAME).detachAppender(FILE_APPENDER_NAME);
context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(allFileAppender);
}
/**
* <p>addJsonConsoleAppender.</p>
*
* @param context a {@link LoggerContext} object.
* @param customFields a {@link String} object.
*/
public static void addErrorFileAppender(LoggerContext context,
String logFile,
boolean useJsonFormat,
String customFields) {
log.info("Initializing {} file loggingProperties", logFile);
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
RollingFileAppender<ILoggingEvent> errorFileAppender = new RollingFileAppender<>();
errorFileAppender.setContext(context);
errorFileAppender.addFilter(errorLevelFilter(context));
if (useJsonFormat) {
errorFileAppender.setEncoder(compositeJsonEncoder(context, customFields));
} else {
errorFileAppender.setEncoder(patternLayoutEncoder(context));
}
errorFileAppender.setName(FILE_ERROR_APPENDER_NAME);
errorFileAppender.setFile(logFile);
errorFileAppender.setRollingPolicy(rollingPolicy(context, logFile));
errorFileAppender.start();
context.getLogger(Logger.ROOT_LOGGER_NAME).detachAppender(FILE_ERROR_APPENDER_NAME);
context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(errorFileAppender);
}
/**
* <p>addLogstashTcpSocketAppender.</p>
*
* @param context a {@link LoggerContext} object.
* @param customFields a {@link String} object.
* @param logstashProperties a {@link net.dreamlu.mica.logging.config.MicaLoggingProperties.Logstash} object.
* @param logStashProperties a {@link net.dreamlu.mica.logging.config.MicaLoggingProperties.Logstash} object.
*/
public static void addLogstashTcpSocketAppender(LoggerContext context, String customFields,
MicaLoggingProperties.Logstash logstashProperties) {
log.info("Initializing Logstash loggingProperties");
public static void addLogStashTcpSocketAppender(LoggerContext context,
String customFields,
MicaLoggingProperties.Logstash logStashProperties) {
log.info("Initializing LogStash loggingProperties");
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
LogstashTcpSocketAppender logStashAppender = new LogstashTcpSocketAppender();
logStashAppender.addDestinations(new InetSocketAddress(logstashProperties.getHost(), logstashProperties.getPort()));
logStashAppender.addDestinations(new InetSocketAddress(logStashProperties.getHost(), logStashProperties.getPort()));
logStashAppender.setContext(context);
logStashAppender.setEncoder(logstashEncoder(customFields));
logStashAppender.setName(ASYNC_LOG_STASH_APPENDER_NAME);
logStashAppender.setQueueSize(logstashProperties.getQueueSize());
logStashAppender.setQueueSize(logStashProperties.getQueueSize());
logStashAppender.start();
context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(logStashAppender);
}
......@@ -108,13 +180,49 @@ public class LoggingUtil {
* @param customFields a {@link String} object.
* @param properties a {@link net.dreamlu.mica.logging.config.MicaLoggingProperties} object.
*/
public static void addContextListener(LoggerContext context, String customFields, MicaLoggingProperties properties) {
LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener(properties, customFields);
public static void addContextListener(LoggerContext context,
String logFile,
String logErrorFile,
String customFields,
MicaLoggingProperties properties) {
LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener(logFile, logErrorFile, customFields, properties);
loggerContextListener.setContext(context);
context.addListener(loggerContextListener);
}
private static LoggingEventCompositeJsonEncoder compositeJsonEncoder(LoggerContext context, String customFields) {
private static RollingPolicy rollingPolicy(LoggerContext context,
String logErrorFile) {
SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
rollingPolicy.setContext(context);
rollingPolicy.setCleanHistoryOnStart(SystemUtil.getPropToBool(LogbackLoggingSystemProperties.ROLLINGPOLICY_CLEAN_HISTORY_ON_START, false));
rollingPolicy.setFileNamePattern(logErrorFile + ".%d{yyyy-MM-dd}.%i.gz}");
rollingPolicy.setMaxFileSize(FileSize.valueOf(SystemUtil.getProp(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_FILE_SIZE, "10MB")));
rollingPolicy.setMaxHistory(SystemUtil.getPropToInt(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_HISTORY, 7));
rollingPolicy.setTotalSizeCap(FileSize.valueOf(SystemUtil.getProp(LogbackLoggingSystemProperties.ROLLINGPOLICY_TOTAL_SIZE_CAP, "0")));
rollingPolicy.start();
return rollingPolicy;
}
private static ThresholdFilter errorLevelFilter(LoggerContext context) {
final ThresholdFilter filter = new ThresholdFilter();
filter.setContext(context);
filter.setLevel(Level.ERROR.levelStr);
filter.start();
return filter;
}
private static Encoder<ILoggingEvent> patternLayoutEncoder(LoggerContext context) {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(context);
encoder.setPattern(context.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN));
String charsetName = SystemUtil.getProp(LogbackLoggingSystemProperties.FILE_LOG_CHARSET, "default");
encoder.setCharset(Charset.forName(charsetName));
encoder.start();
return encoder;
}
private static LoggingEventCompositeJsonEncoder compositeJsonEncoder(LoggerContext context,
String customFields) {
final LoggingEventCompositeJsonEncoder compositeJsonEncoder = new LoggingEventCompositeJsonEncoder();
compositeJsonEncoder.setContext(context);
compositeJsonEncoder.setProviders(jsonProviders(context, customFields));
......@@ -129,7 +237,8 @@ public class LoggingUtil {
return logstashEncoder;
}
private static LoggingEventJsonProviders jsonProviders(LoggerContext context, String customFields) {
private static LoggingEventJsonProviders jsonProviders(LoggerContext context,
String customFields) {
final LoggingEventJsonProviders jsonProviders = new LoggingEventJsonProviders();
jsonProviders.addArguments(new ArgumentsJsonProvider());
jsonProviders.addContext(new ContextJsonProvider<>());
......@@ -182,12 +291,19 @@ public class LoggingUtil {
* This listener ensures that the programmatic configuration is also re-applied after reset.
*/
private static class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener {
private final MicaLoggingProperties loggingProperties;
private final String logFile;
private final String logErrorFile;
private final String customFields;
private final MicaLoggingProperties loggingProperties;
private LogbackLoggerContextListener(MicaLoggingProperties loggingProperties, String customFields) {
private LogbackLoggerContextListener(String logFile,
String logErrorFile,
String customFields,
MicaLoggingProperties loggingProperties) {
this.loggingProperties = loggingProperties;
this.customFields = customFields;
this.logFile = logFile;
this.logErrorFile = logErrorFile;
}
@Override
......@@ -197,21 +313,23 @@ public class LoggingUtil {
@Override
public void onStart(LoggerContext context) {
if (this.loggingProperties.isUseJsonFormat()) {
addJsonConsoleAppender(context, customFields);
}
if (this.loggingProperties.getLogstash().isEnabled()) {
addLogstashTcpSocketAppender(context, customFields, loggingProperties.getLogstash());
addLogStashTcpSocketAppender(context, customFields, loggingProperties.getLogstash());
} else {
boolean useJsonFormat = loggingProperties.isUseJsonFormat();
addAllFileAppender(context, logFile, useJsonFormat, customFields);
addErrorFileAppender(context, logErrorFile, useJsonFormat, customFields);
}
}
@Override
public void onReset(LoggerContext context) {
if (this.loggingProperties.isUseJsonFormat()) {
addJsonConsoleAppender(context, customFields);
}
if (this.loggingProperties.getLogstash().isEnabled()) {
addLogstashTcpSocketAppender(context, customFields, loggingProperties.getLogstash());
addLogStashTcpSocketAppender(context, customFields, loggingProperties.getLogstash());
} else {
boolean useJsonFormat = loggingProperties.isUseJsonFormat();
addAllFileAppender(context, logFile, useJsonFormat, customFields);
addErrorFileAppender(context, logErrorFile, useJsonFormat, customFields);
}
}
......
......@@ -41,23 +41,32 @@ public class MicaLoggingConfiguration {
@Autowired
public MicaLoggingConfiguration(Environment environment,
MicaLoggingProperties loggingProperties) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
// 1. 服务名和环境和日志目录
String appName = environment.getRequiredProperty(MicaConstant.SPRING_APP_NAME_KEY);
String profile = environment.getRequiredProperty(MicaConstant.ACTIVE_PROFILES_PROPERTY);
Map<String, Object> map = new HashMap<>();
map.put("appName", appName);
map.put("profile", profile);
map.put("timestamp", "%date{\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\"}");
String customFields = JsonUtil.toJson(map);
// 2. 生成日志文件的文件
String logDir = environment.getProperty("logging.file.path", LoggingUtil.DEFAULT_LOG_DIR);
String logFile = logDir + '/' + appName + "all.log";
String logErrorFile = logDir + '/' + appName + "error.log";
// 3. logStash 配置
MicaLoggingProperties.Logstash logStashProperties = loggingProperties.getLogstash();
if (loggingProperties.isUseJsonFormat()) {
LoggingUtil.addJsonConsoleAppender(context, customFields);
}
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
// 4. json 自定义字段
Map<String, Object> customFields = new HashMap<>();
customFields.put("appName", appName);
customFields.put("profile", profile);
customFields.put("timestamp", "%date{\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\"}");
String customFieldsJson = JsonUtil.toJson(customFields);
// 是否采用 json 格式化
boolean useJsonFormat = loggingProperties.isUseJsonFormat();
if (logStashProperties.isEnabled()) {
LoggingUtil.addLogstashTcpSocketAppender(context, customFields, logStashProperties);
LoggingUtil.addLogStashTcpSocketAppender(context, customFieldsJson, logStashProperties);
} else {
LoggingUtil.addAllFileAppender(context, logFile, useJsonFormat, customFieldsJson);
LoggingUtil.addErrorFileAppender(context, logErrorFile, useJsonFormat, customFieldsJson);
}
if (loggingProperties.isUseJsonFormat() || logStashProperties.isEnabled()) {
LoggingUtil.addContextListener(context, customFields, loggingProperties);
if (useJsonFormat || logStashProperties.isEnabled()) {
LoggingUtil.addContextListener(context, logFile, logErrorFile, customFieldsJson, loggingProperties);
}
}
......
......@@ -43,9 +43,9 @@ public class MicaLoggingProperties {
@Setter
public static class Console {
/**
* 是否开启控制台日志
* 是否启动完成后关闭控制台日志,适用于,正式环境
*/
private boolean enabled = true;
private boolean closeAfterStart = false;
}
@Getter
......
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤 error -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_ERROR_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<cleanHistoryOnStart>${LOG_FILE_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<fileNamePattern>${LOG_ERROR_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<maxHistory>${LOG_FILE_MAX_HISTORY:-7}</maxHistory>
<totalSizeCap>${LOG_FILE_TOTAL_SIZE_CAP:-0}</totalSizeCap>
</rollingPolicy>
</appender>
</included>
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 服务名 -->
<springProperty scope="context" name="appName" source="spring.application.name"/>
<springProperty scope="context" name="logDir" source="logging.file.path" defaultValue="logs"/>
<!-- 日志级别 -->
<springProperty scope="context" name="loggingLevelRoot" source="logging.level.root" defaultValue="INFO"/>
<!-- 默认 jar 包同级目录 logs/appName/all.log, 外部可配置 LOG_PATH -->
<property name="LOG_FILE" value="${logDir:-logs}/${appName}/all.log"/>
<property name="LOG_ERROR_FILE" value="${logDir:-logs}/${appName}/error.log"/>
<!-- 控制台日志 -->
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<include resource="file-error.xml" />
<root level="${loggingLevelRoot}">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
<appender-ref ref="FILE_ERROR"/>
</root>
<!-- 减少部分debug日志 -->
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册