log4j2 日志保存至数据库

news2024/11/17 15:41:29

文章目录

  • 概述
  • 一、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;
    
    @Autowired
    private 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");
        }
    }
    
    @Bean
    public 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指定环境注入
    如:
       java -jar -Dspring.profiles.active=dev springboot-hello.jar --spring.config.location=./application-other.yml
       java -jar springboot-hello.jar --spring.profiles.active=dev --spring.config.location=./application-other.yml
  1. 数据源通过docker-compose编排文件环境变量注入
    如:
services:
  hello:
    image: registry.cn-shanghai.aliyuncs.com/00fly/springboot-hello-swagger2:1.0.0
    container_name: hello-random
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 200M
        reservations:
          memory: 180M
    ports:
    - 8080:8082
    entrypoint: 'sh wait-for.sh 172.88.88.11:3306 -- java -jar /app.jar'
    environment:
      JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandom
      SPRING_DATASOURCE_URL: jdbc:mysql://172.88.88.11:3306/hello?useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
      SPRING_DATASOURCE_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root123
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'

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;
    }
    
    @Override
    public 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()};
        
        // 配置appender
        final 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;
    }
    
    @Override
    public 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.coloradmin.cn/o/1025563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

求臻医学:结直肠癌患者必看的就诊指南及基因检测意义

结直肠癌是常见的消化道肿瘤之一&#xff0c;已跃居我国高发恶性肿瘤第2位&#xff0c;且其发病率、死亡率逐年上升。数据显示&#xff0c;2020年新发病例 55.5 万&#xff0c;死亡病例 28.6 万。本文系统归纳总结了结直肠癌患者应该选择哪些诊科室、相关检查、治疗方式、预后预…

数字IC设计系列----单端口RAM、双端口RAM、同步FIFO、异步FIFO

一、单端口RAM原理及实现 1.1、概念/原理 在内存空间中开辟出一段固定大小的内存用于存储数据&#xff0c;每一个数据所占的bit位称之为位宽&#xff0c;这段内存空间中数据的总数称之为深度。例如reg [7:0] mem [255:0]&#xff0c;这段内存空间中每一个数据的位宽为8bit&am…

VS2019中使用printf函数报错处理方法

VS2019中使用printf函数报错处理方法 在使用vs2019学习OpenCV的过程中&#xff0c;使用简单的printf函数&#xff0c;竟然编译不过去&#xff0c;VS2019报错&#xff1b; 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4996 ‘sprintf’: This function or variable may…

基础概念回顾:云原生应用交付

原文链接&#xff1a;基础概念回顾&#xff1a;云原生应用交付 转载来源&#xff1a;NGINX 开源社区 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn 尽管云原生应用开发诞生于 21 世纪初&#xff0c;但是在术语使用方面还是非常混乱。本文将带您了解常见的术语和问题。…

图像处理领域之►边缘检测大合集◄【应该是全网仅有的了吧】

图像处理领域之►边缘检测‧大合集◄ 概述 {\color{Brown}概述} 概述 数据集 {\color{Purple}数据集} 数据集 实践 {\color{Red}实践} 实践 深度学习方法 {\color{Blue} 深度学习方法} 深度学习方法 机器学习方法 {\color{Blue} 机器学习方法} 机器学习方法 基于传统方法 {\col…

如何隐藏或修改Docker容器中的Nginx响应头中的Server

背景介绍 现在大部分项目通过Nginx作为反向代理&#xff0c;实际由于安全审计要求需要隐藏或修改响应头的Server信息&#xff0c;传统的项目直接部署在nginx服务器中&#xff0c;只需要在nginx服务器安装ngx_http_headers_more_filter_module插件&#xff0c;然后通过修改ngin…

Linux服务器占用处理手记

磁盘占用定位处理 查看磁盘占用情况&#xff1a; df -h 查看每个目录的占用情况&#xff1a; du -h -x --max-depth1 查找大文件和目录 du -sh /* du -sh /home/* 可参考&#xff1a; Linux垃圾清理指北_linux 清理垃圾_智商二五零_的博客-CSDN博客 查看CPU和内存占用情…

SpringBoot底层原理----配置优先级/Bean管理/springboot原理

配置优先级 最终得到以下配置优先级:(从低到高) Bean管理 获取bean bean作用域 第三方bean Springboot原理 起步依赖 原理就是依赖传递-通过引入web依赖将所有必要的都同时引入 自动配置--面试高频题目 即:在pom文件中引入依赖后,他是如何自动配置给IOC容器的 springboot采…

Xilinx SDK编译完成自动生成SREC文件(适用于ISE、Vivado、Vitis)

把elf转换成srec格式的常规方式&#xff0c;是打开Program Flash Memory界面&#xff0c;选择elf文件&#xff0c;点击Convert ELF to SREC 会在hardware目录下的cache文件夹下生产srec文件。 可以通过配置编译后执行命令&#xff0c;在每次编译完成自动生产srec文件。 会在…

安卓备份基带分区 备份字库 步骤解析 以免误檫除分区或者“格机” 后悔莫及

玩机搞机---安卓机型mtk和高通芯片查看分区 导出分区 备份分区的一些工具分析 修复基带 改串码 基带qcn 改相关参数 格机危害 手机基带的重要性前面几期博文我都有相关的说明。他区别于别的分区。而且目前手机的安全性越来越高。基带分区基本都是专机专用。而不像早期机型一…

小皮面板配置Xdebug,调用单个php文件

小皮面板配置Xdebug 首先下载phpstrom&#xff0c;和小皮面板 打开小皮面板&#xff0c;选中好要使用的php版本 然后点击【管理】> 【php扩展】> 【xdebug】 然后打开选中好版本的php位置 D:\Program_Files\phpstudy_pro\Extensions\php\php7.4.3nts打开php.ini文件…

【数据结构】—从直接插入排序升级到希尔排序究极详解(含C语言实现)

食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f525;这就不得不推荐此专栏了&#xff1a;C语言 ♈️今日夜电波&#xff1a;透明で透き通って何にでもなれそうで—HaKU 2:05 ━━━━━━️&#x1f49f;──────── 5:38 …

干货分享 | 关于同星硬件接口卡及TSMaster软件常见问题QA指南

TSMaster是同星智能开发的一款国产汽车总线工具链软件平台&#xff0c;是全方位汽车总线设计、仿真、分析、诊断和标定的专业工具&#xff0c;支持从需求分析到系统实现的整个系统开发过程。同星智能硬件接口卡可以提供CAN, CAN FD, LIN, FlexRay, 与Ethernet等网络的开发、仿真…

李宏毅-hw7-利用Bert完成QA

一、查漏补缺、熟能生巧&#xff1a; 只有熬过不熟练的时期&#xff0c;反复琢磨&#xff0c;才会有熟练之后&#xff0c;藐视众生的时刻 1.关于transformers中的tokenizer的用法的简单介绍&#xff1a; from transformers import BertTokenizerFast# 加载预训练的BERT模型to…

uniapp Echart X轴Y轴文字被遮挡怎么办,或未能铺满整个容器

有时候布局太小&#xff0c;使用echarts&#xff0c;x轴y轴文字容易被遮挡&#xff0c;怎么解决这个问题呢&#xff0c;或者是未能铺满整个容器。 方法1&#xff1a; 直接设置 containLabel 字段 options: { grid: { containLabel: true, },} 方法2: 间接设置&#xff0c;但是…

微信小程序页面栈超出导致页面卡死

微信小程序页面栈不能超出10个 超出10个之后无法进行点击选择跳转 解决方法&#xff1a; 跳转的时候&#xff0c;判断之前页面栈里是否存在要跳转的页面&#xff0c; 如果存在之前页面&#xff0c;就navigateBack返回之前页面&#xff0c; 如果不存在之前页面&#xff0c;判断…

RocketMQ 核心编程模型以及生产环境最佳实践

文章目录 1、RocketMQ的消息模型2、深入理解RocketMQ的消息模型2.1、RocketMQ客户端基本流程2.2、消息确认机制2.2.1、 发送消息的方式第一种称为单向发送第二种称为同步发送第三种称为异步发送 2.2.2、状态确认机制2.2.3、消费者也可以自行指定起始消费位点 2.3、广播消息2.4、…

nginx 报错[emerg]: unknown directive “锘? in E:\nginx-1.18.0/conf/nginx.conf:3

报错&#xff1a;nginx 报错[emerg] 32408#14080: unknown directive "锘? in E:\nginx-1.18.0/conf/nginx.conf:3 原因&#xff1a;使用nginx服务时&#xff0c;用txt记事本打开编辑了nginx.conf文件&#xff0c;类似WINDOWS自带的记事本等软件&#xff0c;在保存一个以…

mysql触发器triggers

文章目录 1、创建触发器2、 查看触发器3、删除触发器 1、创建触发器 语法&#xff1a; CREATE TRIGGER 触发器名称 BEFORE/AFTER INSERT/UPDATE/DELETE ON 表名 FOR EACH ROW BEGIN SQL逻辑 END;BEFORE 或 AFTER 表示之前 还是 之后触发 INSERT 或 UPDATE 或 DELETE 表示监听…

PostgreSQL 查询某个属性相同内容出现的次数

查询某个数据库表属性 name 相同内容出现出现的次数&#xff0c;并按次数从大到小排序 SELECT name, COUNT(*) AS count FROM your_table GROUP BY name ORDER BY count DESC;示例 select project_id, COUNT(*) AS count from app_ads_positions group by project_id order b…