log4j2 日志保存至数据库

文章目录

  • 概述
  • 一、springmvc工程
    • 1.创建数据库日志表
    • 2.log4j2.xml引入JDBCAppender
    • 3.定义日志管理类
    • 4.编写日志输出代码
    • 5.运行结果
    • 6.完整代码
  • 二、springboot工程
    • 1. 创建数据库日志表
    • 2.log4j2.xml引入JDBCAppender
    • 3.定义日志管理类
    • 4. 遗留问题
    • 5. 解决办法
    • 6. 完整代码

概述

Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题。是目前最优秀的Java日志框架,没有之一。

官方Appenders提供了日志的多种输出方式实现。
在这里插入图片描述
下面我们以 JDBCAppender 为例来说明如何在项目中实现系统日志保存到数据库。

一、springmvc工程

1.创建数据库日志表

CREATE TABLE IF NOT EXISTS boot_log ( `id` bigint NOT NULL AUTO_INCREMENT,`event_id` varchar(50) ,`event_date` datetime ,`thread` varchar(255) ,`class` varchar(255) ,`function` varchar(255) ,`message` varchar(255) ,`exception` text,`level` varchar(255) ,`time` datetime,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.log4j2.xml引入JDBCAppender

	<?xml version="1.0" encoding="UTF-8"?>
<configuration status="off" monitorInterval="0"><properties><property name="LOG_HOME">../logs</property><property name="PROJECT">spring</property><property name="FORMAT">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</property></properties><appenders><console name="Console" target="system_out"><patternLayout pattern="${FORMAT}" /></console><JDBC name="databaseAppender" bufferSize="20" tableName="boot_log"><ConnectionFactory class="com.fly.core.log.LogPoolManager" method="getConnection" /><Column name="event_id" pattern="%X{id}" /><Column name="event_date" isEventTimestamp="true" /><Column name="thread" pattern="%t %x" /><Column name="class" pattern="%C" /><Column name="`function`" pattern="%M" /><Column name="message" pattern="%m" /><Column name="exception" pattern="%ex{full}" /><Column name="level" pattern="%level" /><Column name="time" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}" /></JDBC></appenders><loggers><logger name="org.springframework" level="INFO" /><root level="INFO"><appender-ref ref="Console" /><appender-ref ref="databaseAppender" /></root></loggers>
</configuration>

3.定义日志管理类

LogPoolManager.java

/*** * 日志数据库数据源* * @author 00fly* @version [版本号, 2023年3月27日]* @see [相关类/方法]* @since [产品/模块版本]*/
public final class LogPoolManager
{private LogPoolManager(){super();}/*** getConnection* * @return* @throws SQLException* @see [类、类#方法、类#成员]*/public static Connection getConnection()throws SQLException{// TODO: mvc工程下使用此写法,可行,boot工程不行DataSource dataSource = SpringContextUtils.getBean(DataSource.class);Assert.notNull(dataSource, "dataSource is null");return dataSource.getConnection();}
}

4.编写日志输出代码

@Slf4j
@Component
@Configuration
public class ScheduleJob
{@Value("${welcome.message:hello, 00fly in java!}")private String welcome;@Autowiredprivate JdbcTemplate jdbcTemplate;@Scheduled(cron = "0/10 * 7-20 * * ?")public void run(){log.info("---- {}", welcome);long count = jdbcTemplate.queryForObject("select count(*) from boot_log", Long.class);log.info("------------ boot_log count: {} ----------", count);if (count > 100){log.info("###### truncate table boot_log ######");jdbcTemplate.execute("truncate table boot_log");}}@Beanpublic ScheduledExecutorService scheduledExecutorService(){// return Executors.newScheduledThreadPool(5);return new ScheduledThreadPoolExecutor(5, new CustomizableThreadFactory("schedule-pool-"));}
}

5.运行结果

在这里插入图片描述
mysql 数据库日志数据如下在这里插入图片描述在log4j2.xml中设置了 bufferSize=“20”,这边日志容量达到20才执行一次批量保存。

6.完整代码

https://gitee.com/00fly/java-code-frame/tree/master/springmvc-dbutils

二、springboot工程

1. 创建数据库日志表

CREATE TABLE IF NOT EXISTS boot_log ( `id`  bigint NOT NULL AUTO_INCREMENT ,`event_id` varchar(50) ,`event_date` datetime ,`thread` varchar(255) ,`class` varchar(255) ,`function` varchar(255) ,`message` varchar(255) ,`exception` text,`level` varchar(255) ,`time` datetime,
PRIMARY KEY (id)
);

2.log4j2.xml引入JDBCAppender

		<!-- bufferSize 没起作用,待排查 --><JDBC name="databaseAppender" bufferSize="20" tableName="boot_log"><ConnectionFactory class="com.fly.core.log.LogPoolManager" method="getConnection" /><Column name="event_id" pattern="%X{id}" /><Column name="event_date" isEventTimestamp="true" /><Column name="thread" pattern="%t %x" /><Column name="class" pattern="%C" /><Column name="`function`" pattern="%M" /><Column name="message" pattern="%m" /><Column name="exception" pattern="%ex{full}" /><Column name="level" pattern="%level" /><Column name="time" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}" /></JDBC>

3.定义日志管理类


/*** * 日志数据库数据源* * @author 00fly* @version [版本号, 2023年3月27日]* @see [相关类/方法]* @since [产品/模块版本]*/
public final class LogPoolManager
{private static DataSource dataSource;private LogPoolManager(){super();}/*** boot启动时指定的外部配置文件位置*/private static String configLocation;public static void setConfigLocation(String configLocation){LogPoolManager.configLocation = configLocation;}/*** 不能静态初始化 DataSource,否则无法加载外部配置文件*/public static synchronized void init(){try{// 加载外部配置文件if (StringUtils.isNotBlank(configLocation)){File file = new File(configLocation);String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8.toString());Properties props = YamlUtils.yamlToProperties(text);dataSource = DataSourceBuilder.create().type(DruidDataSource.class).url(props.getProperty("spring.datasource.url")).username(props.getProperty("spring.datasource.username")).password(props.getProperty("spring.datasource.password")).build();}else{// TODO: 数据源通过spring.profiles.active指定或docker-compose环境变量注入,怎么改写下面的逻辑?Resource resource = new ClassPathResource("application.yml");String text = IOUtils.toString(resource.getURL(), StandardCharsets.UTF_8.toString());boolean dev = StringUtils.contains(text, "dev");Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(dev ? "jdbc-h2.properties" : "jdbc-mysql.properties"));dataSource = DruidDataSourceFactory.createDataSource(properties);}}catch (Exception e){e.printStackTrace();}}/*** getConnection* * @return* @throws SQLException* @see [类、类#方法、类#成员]*/public static Connection getConnection()throws SQLException{if (dataSource == null){init();}Assert.notNull(dataSource, "dataSource can not be null");return dataSource.getConnection();}
}

4. 遗留问题

工程中log4j2组件的初始化一般早于springboot工程,这里采用log4j2.xml引入JDBCAppender,故LogPoolManager无法获取springboot管理的DataSource, 大家网上搜到的demo大部分采用写死数据库连接参数的形式,不利于维护。

上面采用的读取数据库配置文件的方式,在以下场景会导致无法读取正确的数据库配置,日志无法保存的问题:

  1. 数据源通过spring.profiles.active指定
  2. 数据源docker-compose环境变量注入

5. 解决办法

将第2部的log4j2.xml引入JDBCAppender改写为使用javaConfig方式。

Log4j2Configuration.java


@Component
public class Log4j2Configuration implements ApplicationListener<ContextRefreshedEvent>
{private final DataSource dataSource;public Log4j2Configuration(DataSource dataSource){this.dataSource = dataSource;}@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent){final LoggerContext ctx = LoggerContext.getContext(false);final ColumnConfig[] cc ={ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("event_id").setPattern("%X{id}").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("event_date").setEventTimestamp(true).setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("thread").setPattern("%t %x").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("class").setPattern("%C").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("`function`").setPattern("%M").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("message").setPattern("%m").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("exception").setPattern("%ex{full}").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("level").setPattern("%level").setUnicode(false).build(),ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("time").setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS}").setUnicode(false).build()};// 配置appenderfinal Appender appender = JdbcAppender.newBuilder().setName("databaseAppender").setIgnoreExceptions(false).setConnectionSource(new ConnectionFactory(dataSource)).setTableName("boot_log").setColumnConfigs(cc).setColumnMappings(new ColumnMapping[0]).build();appender.start();ctx.getConfiguration().addAppender(appender);// 指定哪些logger输出的日志保存在mysql中ctx.getConfiguration().getLoggerConfig("com.fly.core.log.job").addAppender(appender, Level.INFO, null);ctx.updateLoggers();}
}

ConnectionFactory.java


public class ConnectionFactory extends AbstractConnectionSource
{private final DataSource dataSource;public ConnectionFactory(DataSource dataSource){Assert.notNull(dataSource, "dataSource can not be null");this.dataSource = dataSource;}@Overridepublic Connection getConnection()throws SQLException{return dataSource.getConnection();}
}

6. 完整代码

改造前代码:
https://gitee.com/00fly/effict-side/tree/master/springboot-hello

改造后javaConfig代码:
https://gitee.com/00fly/effict-side/tree/master/springboot-hello-swagger2

有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/110109.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识 Linux 文件系统

初识 Linux 文件系统 如果是刚接触 Linux 系统&#xff0c;可能就很难搞清楚 Linux 如何引用文件和目录。对于对已经习惯 使用 Windows 操作系统的人来说&#xff0c;难度更大。所以要想学习 Linux 系统&#xff0c;就必须先了解 Linux 文件系统 文章目录 初识 Linux 文件系统…

icmp报文及用go实现

目录 一、概述 二、ICMP报文格式详解 2.1 什么是ICMP 2.2 ICMP报文格式 2.3 ICMP报文类型 2.4 实际报文举例 三、使用go实现icmp请求以及接收响应内容 一、概述 本文主要旨在学习icmp报文格式&#xff0c;以及通过go语言来实现ICMP发包。 二、ICMP报文格式详解 2.1 什…

SpringMVC之JSR303和拦截器

认识JSR303 JSR303是一项Java标准规范&#xff0c;也叫做Bean Validation规范&#xff0c;提供了一种JavaBean数据验证的规范方式。在SpringMVC中&#xff0c;可以通过引入JSR303相关的依赖&#xff0c;来实现数据的校验。 在使用JSR303进行校验时&#xff0c;需要在需要校验的…

ETL与ELT理解

ETL ETL&#xff08; Extract-Transform-Load&#xff09;&#xff0c;用来描述将数据从来源端经过抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、加载&#xff08;Load&#xff09;至目的端的过程。ETL模式适用于小数据量集。如果在转换过程…

回溯算法解决分割回文串

回溯算法解分割回文串 力扣131 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 示例 1&#xff1a; 输入&#xff1a;s "aab" 输出&#xff1…

安防监控视频系统EasyCVR+AI算法智能分析网关助力智慧校园建设

学生是祖国的未来&#xff0c;学校就是培育学生的地方。随着校园信息化建设的不断发展&#xff0c;信息服务在校园管理中的作用也越来越强。在保障学生安全与校园高效管理上&#xff0c;人工智能做出了极大贡献&#xff0c;旭帆科技安防监控系统/视频汇聚/云存储/AI智能视频分析…

mysql的索引分类

索引分类 在 MySQL 数据库&#xff0c;将索引的具体类型主要分为以下几类&#xff1a;主键索引、唯一索引、常规索引、全文索引。 分类 含义 特点 关键字 主键 索引 针对于表中主键创建的索引 默认自动创建 , 只能 有一个 PRIMARY 唯一 索引 避免同一个表中某数据列中…

多线程|多进程|高并发网络编程

一.多进程并发服务器 多进程并发服务器是一种经典的服务器架构&#xff0c;它通过创建多个子进程来处理客户端连接&#xff0c;从而实现并发处理多个客户端请求的能力。 概念&#xff1a; 服务器启动时&#xff0c;创建主进程&#xff0c;并绑定监听端口。当有客户端连接请求…

国家网络安全周2023时间是什么时候?有什么特点?谁举办的?

国家网络安全周2023时间是什么时候&#xff1f; 2023年国家网络安全宣传周将于9月11日至17日在全国范围内统一开展。其中开幕式等重要活动将在福建省福州市举行。今年网安周期间&#xff0c;除开幕式外&#xff0c;还将举行网络安全博览会、网络安全技术高峰论坛、网络安全微视…

JavaEE初阶(5)多线程案例(定时器、标准库中的定时器、实现定时器、线程池、标准库中的线程池、实现线程池)

接上次博客&#xff1a;JavaEE初阶&#xff08;4&#xff09;&#xff08;线程的状态、线程安全、synchronized、volatile、wait 和 notify、多线程的代码案例&#xff1a;单例模式——饿汉懒汉、阻塞队列&#xff09;_di-Dora的博客-CSDN博客 目录 多线程案例 定时器 标准…

后端/DFT/ATPG/PCB/SignOff设计常用工具/操作/流程及一些文件类型

目录 1.PD/DFT常用工具及流程 1.1 FC和ICC2 1.2 LC (Library compiler) 1.3 PrimeTime 1.4 Redhawk与PA 1.5 Calibre和物理验证PV 1.6 芯片设计流程 2.后端、DFT、ATPG的一些常见文件 2.1 LEF和DEF 2.2 ATPG的CTL和STIL 2.3 BSDL 2.4 IPXACT 2.5 CDL netlist 3.…

【去除若依首页】有些小项目不需要首页,去除方法

第一步 // // // // // // // // // // // // // // // // // // 修改登录页 Login.vue 中 大概144行 &#xff0c;注释掉原有跳转。替换为自己的跳转路径 // // // // // // // // // // // // // this.$router.push({ path: this.redirect || …