去年公司由于不断发展,内部自研系统越来越多,所以后来搭建了一个日志收集平台,并将日志收集功能以二方包形式引入各个自研系统,避免每个自研系统都要建立一套自己的日志模块,节约了开发时间,管理起来也更加容易。
这篇文章主要介绍如何编写二方包,并整合到各个系统中。
先介绍整个ELK日志平台的架构。其中xiaobawang-log就是今天的主角。
xiaobawang-log主要收集三种日志类型:
- 系统级别日志: 收集系统运行时产生的各个级别的日志(ERROR、INFO、WARN、DEBUG和TRACER),其中ERROR级别日志是我们最关心的。
- 用户请求日志: 主要用于controller层的请求,捕获用户请求信息和响应信息、以及来源ip等,便于分析用户行为。
- 自定义操作日志: 顾名思义,就是收集手动打的日志。比如定时器执行开始,都会习惯性写一个log.info("定时器执行开始!")的描述,这种就是属于自定义操作日志的类型。
二方包开发
先看目录结构
废话不多说,上代码。
1、首先创建一个springboot项目,引入如下包:- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>net.logstash.logback</groupId>
- <artifactId>logstash-logback-encoder</artifactId>
- <version>7.0.1</version>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-core</artifactId>
- <version>1.2.10</version>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.10</version>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-access</artifactId>
- <version>1.2.10</version>
- </dependency>
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.7.18</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- <version>1.18.26</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
复制代码 SysLog实体类
- public class SysLog {
- /**
- * 日志名称
- */
- private String logName;
- /**
- * ip地址
- */
- private String ip;
- /**
- * 请求参数
- */
- private String requestParams;
- /**
- * 请求地址
- */
- private String requestUrl;
- /**
- * 用户ua信息
- */
- private String userAgent;
- /**
- * 请求时间
- */
- private Long useTime;
- /**
- * 请求时间
- */
- private String exceptionInfo;
- /**
- * 响应信息
- */
- private String responseInfo;
- /**
- * 用户名称
- */
- private String username;
- /**
- * 请求方式
- */
- private String requestMethod;
- }
复制代码 LogAction
创建一个枚举类,包含三种日志类型。- public enum LogAction {
- USER_ACTION("用户日志", "user-action"),
- SYS_ACTION("系统日志", "sys-action"),
- CUSTON_ACTION("其他日志", "custom-action");
- private final String action;
- private final String actionName;
- LogAction(String action,String actionName) {
- this.action = action;
- this.actionName = actionName;
- }
- public String getAction() {
- return action;
- }
- public String getActionName() {
- return actionName;
- }
- }
复制代码 配置logstash
更改logstash配置文件,将index名称更改为log-%{[appname]}-%{+YYYY.MM.dd}-%{[action]},其中appname为系统名称,action为日志类型。
整个es索引名称是以“系统名称+日期+日志类型”的形式。比如“mySystem-2023.03.05-system-action”表示这个索引,是由mySystem在2023年3月5日产生的系统级别的日志。- # 输入端
- input {
- stdin { }
- #为logstash增加tcp输入口,后面springboot接入会用到
- tcp {
- mode => "server"
- host => "0.0.0.0"
- port => 5043
- codec => json_lines
- }
- }
-
- #输出端
- output {
- stdout {
- codec => rubydebug
- }
- elasticsearch {
- hosts => ["http://你的虚拟机ip地址:9200"]
- # 输出至elasticsearch中的自定义index名称
- index => "log-%{[appname]}-%{+YYYY.MM.dd}-%{[action]}"
- }
- stdout { codec => rubydebug }
- }
复制代码 AppenderBuilder
使用编程式配置logback,AppenderBuilder用于创建appender。
- 这里会创建两种appender。consoleAppender负责将日志打印到控制台,这对开发来说是十分有用的。而LogstashTcpSocketAppender则负责将日志保存到ELK中。
- setCustomFields中的参数,对应上面logstash配置文件的参数[appname]和[action]。
- @Component
- public class AppenderBuilder {
- public static final String SOCKET_ADDRESS = "你的虚拟机ip地址";
- public static final Integer PORT = 5043;//logstash tcp输入端口
- /**
- * logstash通信Appender
- * @param name
- * @param action
- * @param level
- * @return
- */
- public LogstashTcpSocketAppender logAppenderBuild(String name, String action, Level level) {
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- LogstashTcpSocketAppender appender = new LogstashTcpSocketAppender();
- appender.setContext(context);
- //设置logstash通信地址
- InetSocketAddress inetSocketAddress = new InetSocketAddress(SOCKET_ADDRESS, PORT);
- appender.addDestinations(inetSocketAddress);
- LogstashEncoder logstashEncoder = new LogstashEncoder();
- //对应前面logstash配置文件里的参数
- logstashEncoder.setCustomFields("{"appname":"" + name + "","action":"" + action + ""}");
- appender.setEncoder(logstashEncoder);
- //这里设置级别过滤器
- LevelFilter levelFilter = new LevelFilter();
- levelFilter.setLevel(level);
- levelFilter.setOnMatch(ACCEPT);
- levelFilter.setOnMismatch(DENY);
- levelFilter.start();
- appender.addFilter(levelFilter);
- appender.start();
- return appender;
- }
-
-
- /**
- * 控制打印Appender
- * @return
- */
- public ConsoleAppender consoleAppenderBuild() {
- ConsoleAppender consoleAppender = new ConsoleAppender();
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- PatternLayoutEncoder encoder = new PatternLayoutEncoder();
- encoder.setContext(context);
- //设置格式
- encoder.setPattern("%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger) - %cyan(%msg%n)");
- encoder.start();
- consoleAppender.setEncoder(encoder);
- consoleAppender.start();
- return consoleAppender;
- }
复制代码 LoggerBuilder
LoggerBuilder主要用于创建logger类。创建步骤如下:
- 获取logger上下文。
- 从上下文获取logger对象。创建过的logger会保存在LOGCONTAINER中,保证下次获取logger不会重复创建。这里使用ConcurrentHashMap防止出现并发问题。
- 创建appender,并将appender加入logger对象中。
- @Component
- public class LoggerBuilder {
- @Autowired
- AppenderBuilder appenderBuilder;
- @Value("${spring.application.name:unknow-system}")
- private String appName;
- private static final Map<String, Logger> LOGCONTAINER = new ConcurrentHashMap<>();
- public Logger getLogger(LogAction logAction) {
- Logger logger = LOGCONTAINER.get(logAction.getActionName() + "-" + appName);
- if (logger != null) {
- return logger;
- }
- logger = build(logAction);
- LOGCONTAINER.put(logAction.getActionName() + "-" + appName, logger);
- return logger;
- }
- public Logger getLogger() {
- return getLogger(LogAction.CUSTON_ACTION);
- }
- private Logger build(LogAction logAction) {
- //创建日志appender
- List<LogstashTcpSocketAppender> list = createAppender(appName, logAction.getActionName());
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- Logger logger = context.getLogger(logAction.getActionName() + "-" + appName);
- logger.setAdditive(false);
- //打印控制台appender
- ConsoleAppender consoleAppender = appenderBuilder.consoleAppenderBuild();
- logger.addAppender(consoleAppender);
- list.forEach(appender -> {
- logger.addAppender(appender);
- });
- return logger;
- }
- /**
- * LoggerContext上下文中的日志对象加入appender
- */
- public void addContextAppender() {
- //创建四种类型日志
- String action = LogAction.SYS_ACTION.getActionName();
- List<LogstashTcpSocketAppender> list = createAppender(appName, action);
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- //打印控制台
- ConsoleAppender consoleAppender = appenderBuilder.consoleAppenderBuild();
- context.getLoggerList().forEach(logger -> {
- logger.setAdditive(false);
- logger.addAppender(consoleAppender);
- list.forEach(appender -> {
- logger.addAppender(appender);
- });
- });
- }
- /**
- * 创建连接elk的appender,每一种级别日志创建一个appender
- *
- * @param name
- * @param action
- * @return
- */
- public List<LogstashTcpSocketAppender> createAppender(String name, String action) {
- List<LogstashTcpSocketAppender> list = new ArrayList<>();
- LogstashTcpSocketAppender errorAppender = appenderBuilder.logAppenderBuild(name, action, Level.ERROR);
- LogstashTcpSocketAppender infoAppender = appenderBuilder.logAppenderBuild(name, action, Level.INFO);
- LogstashTcpSocketAppender warnAppender = appenderBuilder.logAppenderBuild(name, action, Level.WARN);
- LogstashTcpSocketAppender debugAppender = appenderBuilder.logAppenderBuild(name, action, Level.DEBUG);
- LogstashTcpSocketAppender traceAppender = appenderBuilder.logAppenderBuild(name, action, Level.TRACE);
- list.add(errorAppender);
- list.add(infoAppender);
- list.add(warnAppender);
- list.add(debugAppender);
- list.add(traceAppender);
- return list;
- }
- }
复制代码 LogAspect
使用spring aop,实现拦截用户请求,记录用户日志。比如ip、请求参数、请求用户等信息,需要配合下面的XiaoBaWangLog注解使用。
这里拦截上面所说的第二种日志类型。XiaoBaWangLog
LoggerLoad主要是实现用户级别日志的收集功能。
这里定义了一个注解,在controller方法上加上@XiaoBaWangLog("操作内容"),即可拦截并生成请求日志。- @Target({ElementType.TYPE, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface XiaoBaWangLog {
- String value() default "";
- }
复制代码 LoggerLoad
LoggerLoad主要是实现系统级别日志的收集功能。
继承ApplicationRunner,可以在springboot执行后,自动创建系统级别日志logger对象。- @Component
- @Order(value = 1)
- @Slf4j
- public class LoggerLoad implements ApplicationRunner {
- @Autowired
- LoggerBuilder loggerBuilder;
- @Override
- public void run(ApplicationArguments args) throws Exception {
- loggerBuilder.addContextAppender();
- log.info("加载日志模块成功");
- }
- }
复制代码 LogConfig
LogConfig主要实现自定义级别日志的收集功能。
生成一个logger对象交给spring容器管理。后面直接从容器取就可以了。- @Configuration
- public class LogConfig {
- @Autowired
- LoggerBuilder loggerBuilder;
- @Bean
- public Logger loggerBean(){
- return loggerBuilder.getLogger();
- }
- }
复制代码 代码到现在已经全部完成,怎么将上述的所有Bean加入到spring呢?这个时候就需要用到spring.factories了。
spring.factories
在EnableAutoConfiguration中加入类的全路径名,在项目启动的时候,SpringFactoriesLoader会初始化spring.factories,包括pom中引入的jar包中的配置类。
注意,spring.factories在2.7开始已经不推荐使用,3.X版本的springBoot是不支持使用的。- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.xiaobawang.common.log.config.AppenderBuilder,\
- com.xiaobawang.common.log.config.LoggerBuilder,\
- com.xiaobawang.common.log.load.LoggerLoad,\
- com.xiaobawang.common.log.aspect.LogAspect,\
- com.xiaobawang.common.log.config.LogConfig
复制代码 测试
先将xiaobawang进行打包
新建一个springboot项目,引入打包好的xiaobawang-log.
运行springboot,出现“加载日志模块成功”表示日志模块启动成功。
接着新建一个controller请求
访问请求后,可以看到了三种不同类型的索引了
结束
还有很多需要优化的地方,比如ELK设置用户名密码登录等,对ELK比较了解的童鞋可以自己尝试优化!
如果这篇文章对你有帮助,记得一键三连~
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |