Log4j日志存储到数据库——优化

MySQL入门教程:连接池的使用 数据库连接池入门&log日志框架了解 阅读详情

年前没有什么心情开发正常的需求,最近一直在做保监会存量数据提取,很头大,打算研究点新东西换换心情。
我工作的公司服务器日志必须跟DBA要,一直麻烦人家不太好,而且他如果有事,过了很长时间才给我日志,会影响我问题排查的效率。我就想把日志存储到数据库中,这样十分完美。
最开始想自己写一套Appender来实现,后来Google一下,发现Log4j默认就提供JDBCAppender来实现日志存储数据库的功能。我参考了 log4j日志存储到数据库这篇博客,但是有个问题没有解决,所以才有我这篇博客的诞生,即我优化的之处。

1. 创建日志存储表

 create table XXX_LOG
(
  loglevel VARCHAR2(5) not null,--日志登记
  maketime VARCHAR2(25) not null,--打印时间
  location VARCHAR2(300) not null,--输出位置
  message  VARCHAR2(4000) not null--日志内容
);

由于输出位置我是用的是%l,即输出 类全名.函数名(文件名:行号),所以输出位置长度需要比较长。

2. log4j配置文件的管理

由于项目中用的是log4j.properties,我主要对properties格式的配置文件做说明。

#使用下面定义的appender.DB做完日志的输出位置
log4j.rootLogger=DB
#创建名字为DB的Appender,用来输出日志到数据库
log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
#数据库的驱动
log4j.appender.DB.driver=oracle.jdbc.driver.OracleDriver
#数据库的路径
log4j.appender.DB.URL=jdbc:oracle:thin:@IP:1521:dbname
#数据库的用户名
log4j.appender.DB.user=xxx
#数据库的密码
log4j.appender.DB.password=xxx
#有多少条日志信息后才存入数据库,如果数量过小是会影响生产的效率
log4j.appender.DB.BufferSize=30
#插入数据库使用的SQL语句,用的跟PatternLayout是一样的格式化参数
log4j.appender.DB.sql=INSERT INTO Prip_Log VALUES('%p','%d','%l','%m')
#格式化参数使用的类,也是我跟我参考的那篇博客不一样的地方,下面具体说明
log4j.appender.DB.layout=xxx.DBPatternLayout

3. 待解决的问题说明

如果直接按照我参考博客干,会出一个问题,打印信息中包含”’”(单引号),系统会报“缺少逗号”的错误。为什么会出现这个问题能,如果你把相应内容直接替换到插入SQL中就会发现问题。例如下面的SQL:

insert into xxx_log (LOGLEVEL, MAKETIME, LOCATION, MESSAGE)
values ('INFO', '2017-01-23 09:13:54,092', 'xxx.Configs.<init>(Configs.java:37)', ''测试单引号'');

你会发现打印信息的单引号跟SQL中的字符串边界的单引号匹配了,“测试单引号”这句话就没有被单引号包裹,数据库就不认为它是字符串。才产生这个问题。
那么如何解决的这个问题呢,用转义字符,将单引号转义就行了,根据具体数据库有不同的转义字符,oracle是用单引号作为转义,例如如下改写后的SQL:

insert into xxx_log (LOGLEVEL, MAKETIME, LOCATION, MESSAGE)
values ('INFO', '2017-01-23 09:13:54,092', 'xxx.Configs.<init>(Configs.java:37)', '''测试单引号''');

三个单引号,第一个是字符串边界,第二个是转义字符,第三个才是要打印的单引号。
或者是MySql转义字符用的是”\”,如下SQL:

insert into xxx_log (LOGLEVEL, MAKETIME, LOCATION, MESSAGE)
values ('INFO', '2017-01-23 09:13:54,092', 'xxx.Configs.<init>(Configs.java:37)', '\'测试单引号\'');

4. 具体实施方案

上面只是说明了一下问题的原因,及解决思路,具体的实施方法没有说明。Log4j既然是个开源库,肯定会有各种的扩展方法,我就开始研究JDBCAppender的实现方式。想怎么样可以将日志内容中的“’”替换成“””。这个就得研究源代码了。
1. 方案1
先说一个不是十分完美的方案。我发现%m是获取org.apache.log4j.spi.LoggingEvent.getRenderedMessage(),我最开始的想法是写个子类继承LoggingEvent,重写getRenderedMessage方法,然后将子类对象植入log4j的过程中。

public class DBLoggingEvent extends LoggingEvent
{
    private LoggingEvent mEvent = null;

    public DBLoggingEvent(LoggingEvent tEvent)
    {
        super(tEvent.getFQNOfLoggerClass(), tEvent.getLogger(), tEvent.getLevel(), tEvent.getMessage(), null);
        mEvent = tEvent;
    }
    ……
    @Override
    public String getRenderedMessage()
    {
        // 将字符串中的“'”替换成“''”
        return mEvent.getRenderedMessage().replace("'", "''");
    }
}
public class DBPatternLayout extends PatternLayout
{
    @Override
    public String format(LoggingEvent event)
    {
        // 将新的LoggingEvent对象植入log4j的过程中
        DBLoggingEvent tEvent = new DBLoggingEvent(event);
        return super.format(tEvent);
    }
}

在log4j.properties的配置文件中使用自定义的解析类,即

 log4j.appender.DB.layout=xxx.DBPatternLayout

但是这个方案有三个问题,1、LoggingEvent 没有默认的无参构造函数,必须使用有参构造函数,如果之后构造函数发送变化了,这个类也得跟着改,有耦合问题。就算修改LoggingEvent 的源代码也会有切换log4j版本产生的耦合问题。2、每次都得多建一个对象,消耗系统资源。3、违反log4j的设计模式,即LoggingEvent 只是个javaBean,用来传输数据的,不要增加多余的业务处理。
2. 方案2
那我继续研究代码,发现log4j是用PatternParser来获取解析格式化参数的解释器的,那我写个子类继承PatternParser,增加对单引号的处理,更能符合log4j的体系,即我第二种的方案的思路。
DBPatternParser相当于PatternParser的代理,增加了对保存到数据库时,打印信息中有单引号这个情况的特殊处理,其他情况还是调用父类的实现,我自己感觉用if能简便一些,但是我看父类代码,感觉switch更好一些,为了保持对将来的扩展能力,最后还是模仿父类的架构。

public class DBPatternParser extends PatternParser
{
    public DBPatternParser(String pattern)
    {
        super(pattern);
    }
    @Override
    protected void finalizeConverter(char c)
    {
        switch (c)
        {
            case 'm':
                // 如果是打印信息,调用DBPatternConverter来替换一个单引号成两个单引号
                addConverter(new DBPatternConverter(formattingInfo, c));
                currentLiteral.setLength(0);
                break;
            default:
                // 其他情况调用父类的实现
                super.finalizeConverter(c);
                break;
        }
    }
    private static class DBPatternConverter extends PatternConverter
    {
        char type;
        DBPatternConverter(FormattingInfo formattingInfo, char type)
        {
            super(formattingInfo);
            this.type = type;
        }
        public String convert(LoggingEvent event)
        {
            switch (type)
            {
                case 'm':
                    return event.getRenderedMessage().replace("'", "''");
                default:
                    return null;
            }
        }
    }
}

将新写的PatternParser子类植入log4j的体系中

public class DBPatternLayout extends PatternLayout
{
    @Override
    protected PatternParser createPatternParser(String pattern)
    {
        return new DBPatternParser(pattern);
    }
}

在log4j.properties的配置文件中使用自定义的解析类,跟方案1一样

 log4j.appender.DB.layout=xxx.DBPatternLayout

在这谈一下对zhangchu_63的感谢,即我参考的那篇博客的作者

5.进一步优化

由于

#有多少条日志信息后才存入数据库,如果数量过小是会影响生产的效率
log4j.appender.DB.BufferSize=30

这个配置对性能的优化,往往会出现这个问题,如果这个你一直监控着系统,发现有问题了,马上去查看数据库的日志存储,发现最后的日志还没有刷新到数据库中,这个时候就比较尴尬了,我原先只能手动的多点几下,多输出点日志,然后让我想看到的日志刷新到数据库中。
那么有什么更好的方法去实现呢?还是得研究代码,我发现在JDBCAppender中调用flushBuffer方法来实现将缓存区中的日志刷新到数据库中的。

    if (buffer.size() >= bufferSize)
      flushBuffer();

这个问题就变成获取JDBCAppender,然后调用flushBuffer。
如何获取呢?从代码中发现Category类中有

  synchronized
  public
  Appender getAppender(String name) {
     if(aai == null || name == null)
      return null;

     return aai.getAppender(name);
  }

这个函数能实现我想要的功能。
理所当然的问题就变成了如何获取Category,然后调用getAppender。
我一看这个Category的子类,看着好面熟啊,Logger这个不就是我们输出日志的时候使用的类吗,如果我直接使用程序中获取的Logger能获取到Appender,我感觉不太可能啊,经过实际测试,也是不太可行的。那怎么办,继续看代码,终于我眼前一亮,我看到了如下的代码:

    Logger root = Logger.getRootLogger();
    root.addAppender(appender);

然后就有了如下类的诞生。由于Appender在log4j中是单例模式,那我也使用单例模式来获取它

public class FlushLogBuffer
{
    JDBCAppender mAppender = null;
    private static FlushLogBuffer BUFFER = null;
    private FlushLogBuffer()
    {
        Logger logger = Logger.getRootLogger();
        // 获取名字为DB的Appender
        mAppender = (JDBCAppender) logger.getAppender("DB");
    }
    public static FlushLogBuffer getInstance()
    {
        if (BUFFER == null)
        {
            synchronized (FlushLogBuffer.class)
            {
                if (BUFFER == null)
                {
                    BUFFER = new FlushLogBuffer();
                }
            }
        }
        return BUFFER;
    }
    public void flushBuffer()
    {
        mAppender.flushBuffer();
    }
}

只要写个页面,然后后台调用,就能将缓存区的日志刷新到数据库中

FlushLogBuffer tFlushLogBuffer = FlushLogBuffer.getInstance();
tFlushLogBuffer.flushBuffer();

至此,log4j日志存储到数据库的优化已经全部完毕了

SpringBoot 基于 MongoTemplate 的工具类 MongoDB基于分布式文件存储的数据库。由C++语言编写。MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种。他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 阅读详情

相关推荐

实战指南:在摩尔线程GPU上部署AI模型的完整流程

本文提供了一份详尽的实战指南,手把手教你如何在摩尔线程GPU上成功部署AI模型。内容涵盖从驱动安装、Docker环境配置,到核心的代码适配与模型运行全流程,并分享了性能调优技巧与常见避坑方法,旨在帮助开发者高效利用国产硬件进行AI应用开发。

weixin_29271263的博客 539

log4j日志优化:使用BufferedIO和BufferSize而不是ImmediateFlush

之前我们使用FileAppender的时候,我们配置是ImmediateFlush=true,一旦有新日志写入,立马将日志写入到磁盘的文件中。当日志很多,这种频繁操作文件显然性能很低下。可以发现:日志不能写入到文件中。这是因为:日志是停留在内存中的,虚拟机已经关闭,内存被是否了,log4j并没有将内存中的日志写入到文件中。怎么解决这个问题呢?这里使用了JVM关闭的回调,类似地在tomcat等web容器中,也有类似的关闭事件。我们需要捕获关闭事件,然后调用LogManager.shutdown()即可。

aty 1万+

STM32 使用IO口模拟I2C时序

上一篇《I2C协议详解》 我们了解了I2C的操作流程,这一篇,我们就使用I2C,来对EEPROM进行操作吧。 我们做两种选择: 1.时序由IO口模拟高低电平,需要了解协议并按照协议操作相应的IO口。 2.时序由硬件自行产生,不需要人工干预; 由硬件产生的I2C时序,我们借助Stm32Cube配置实现便可,我们这一篇,抛开Stm32Cube,手撕代码,根据I2C的时序,一步步地实现I2C对...

8937

Log4j AsyncAppender的buffersize属性的作用

当用log4j记录日志时,如果记录的日志量非常大,日志文件迅速增长,IO负载提高,会导致log的输出函数( 如log.info())等响应较慢。此时,我们就想把log的输出放到单独的线程中,这样调用端就可以快速返回,实现异步化。   log4j提供了一个AsyncAppender来实现日志的异步输出,AsyncAppender有一个buffersize属性,用于控制AsyncAppender的...

Simple&Stupid 2065

Carla-UE4Editor导入RoadRunner地图文件(保姆级教程)

以Carla官网导入RoadRunner地图的标准文档为参考,里面增添了很多细节和注意事项,遇到问题的小伙伴过来看看吧。

DSK_981029的博客 1万+

在Ansys Mechanical中对磨损进行建模

在有限元分析中,磨损是通过移动接触表面上的节点来复制材料侵蚀来建模的。仿真迭代更新几何,并在每次更新后重新计算平衡,使过程成为非线性的。Ansys使用将压力、相对运动和材料阻力与表面损失联系起来的经验定律来处理连续介质尺度的磨损。要在Ansys Mechanical中激活磨损建模,必须使用TB,WEAR命令定义专用材料模型。该命令与 TBDATA 相结合,允许用户输入与磨损相关的材料常数,以控制磨损的计算方式以及在接触表面上的应用方式。

jkh920184196的博客 2628

log4j2配置文件log4j2.xml详解

配置全解 1.缺省默认配置文件 <?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <...

a677443612的博客 326

Python控制程控电源(USB)

随着智能电动汽车行业的持续发展,汽车电子或嵌入式设备在软硬件的测试中,都会使用程控电源供电,特别是自动化测试、压力测试场景必定使用到程控电源控制,控制电源上下电,监听电源电流、电压等,以下就以常见的艾德克斯程控电源为例,分享如何通过USB连接方式Python语言来控制程控电源。

测试领域技术分享 2865

Log4J输出日志会影响WEB应用响应时间

由于客户对系统性能要求很高,所有请求的相应时间必须在1秒内,所以我们在做开发的时候,特别注意页面的相应时间。

weback的专栏 2125

蓝鸟四轴飞控系统C程序V3.0(基础版本)深度解读

2. 解析遥控通道数据(油门 / 横滚 / 俯仰 / 偏航),转换为标准化值(-1000~+1000);3. 封装PID初始化(PID_Init,配置KP/KI/KD、输出限幅、积分限幅);,包含姿态角(Attitude_Data.Roll/Pitch/Yaw)、角速度(Gyro_Data.X/Y/Z)、遥控值(RC_Data[0~5])、电池电压(Bat_Volt)、电机PWM值(Motor_PWM[0~3])、系统状态(Sys_State),校验和通过Check_Sum()函数计算;

搏博的专栏 343

点云处理实操(四) -PCL中的点云三角化

点云处理实操(四) -PCL中的点云三角化

cangqiongxiaoye的博客 939

log4j配置详解

log4j配置详解及源码

Java_dragon95的博客 1万+

华为OD面试八股真题 - 操作系统 - 1

华为OD技术面,操作系统相关八股真题提供详细解答,这篇博客主要涉及于进程和线程相关知识点

qq_45776114的博客 1633

项目重构之数据源配置与优化:log4j 配置数据库连接池Druid,并实现日志存储到数据库

作者:泥沙砖瓦浆木匠 网站:http://blog.youkuaiyun.com/jeffli1993 个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。 交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_AonqzL0rpdQAjjqlHQQ 如果我的帮到了你,是否乐意捐助一下或请一杯啤酒也

程序员泥瓦匠 3209

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

项目重构之数据源配置与优化:log4j 配置数据库连接池Druid,并实现日志存储到数据库...

作者:泥沙砖瓦浆木匠网站:http://blog.youkuaiyun.com/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。 交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_AonqzL0rpdQAjjqlHQQ 如果我的帮到了你,是否乐意捐助一下或请一杯啤酒也好呢?有你支持,...

weixin_30849591的博客 103

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

log4j写入数据到oracle,springboot+log4j将日志记录到oracle数据库

看到某个项目中的代码:...log.info("华夏***明细查询");dataRcdService.saveLog(HxStr,"华夏***明细查询");...log.error("农行用户" + abcXyNo + "详情查询异常", e);dataRcdService.saveLog(AbcStr,"农行用户" + abcXyNo + "详情查询异常:" + e.getMessage());...

weixin_39834678的博客 276

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

log4j mysql 详细日志文件_Log4j日志输出详细

日志论在应用程序中输出日志有有三个目的:(1)监视代码中变量的变化情况,把数据周期性地记录到文件中供其他应用进行统计分析工作。(2)跟踪代码运行进轨迹,作为日后审计的依据。(3)担当集成开发环境中的调试器,向文件或控制台打印代码的调试信息。Apache能用日志包(Commons Logging Package)是Apache的一个开放源代码项目,它提供了一组通用的日志接口,用户可以自由...

weixin_35769289的博客 578

Tomcat Log4j配置,生成日志到数据库!

由于最近正在扩展卫生局考务系统,由于上一次使用过一次该系统后,发现很多考生最后出现说已打印报名表但却在数据库中找不到她的报名记录; 因此为了以后有依据,将所有用户的操作日志文件写入数据供管理员查询成为了这次扩张项目的一个内容; 这里我决定使用的log4j日志文件,在多次的使用中感觉这个很不错; 首先我们要使用log4j日志文件时,我们需将两个必须的包放入lib目录下:log4j.XX.j...

eddysoft126的专栏 290

Log4j日志框架:从基础配置到企业级应用实践

日志系统是软件开发中不可或缺的基础组件,用于记录程序运行状态和异常信息。Log4j作为Java生态中最经典的日志框架,通过Logger、Appender和Layout三大核心组件实现灵活的日志管理。其分级日志控制机制(DEBUG/INFO/WARN/ERROR等)允许开发者根据不同环境调整日志详细程度,而多样化的输出方式(控制台、文件、数据库等)满足不同场景需求。在分布式系统和微服务架构中,结合ELK等工具可以实现日志的集中收集与分析。本文以Log4j 1.x为例,详细讲解配置技巧、性能优化方案以及常见问题

weixin_33851177的博客 404

LOG4J一些工作中的学习总结

首先声明:我这里使用的是apache的log4j日志框架,并没有采用slf4j的 因为工作需要,组长要求我对整个项目里面的日志过程进行优化,所有的日志不要全部输出到文件与控制台中去,这会影响实际使用过程中对项目出现问题时维护的困难程度。于是,我便乐在其中,开始在干活过程中对LOG4J实实在在地体验。PS:以前也用过,不过是简单地默认输出文件,而这次,我有了进一步地体会...

娃娃学JAVA 129

log4j日志存储到数据库

一、前提条件       系统必须是使用LOG4J进行日志管理,否则方法无效。       系统必须包含commons-logging-xxx.jar,log4j-xxx.jar这两个JAR包,XXX为版本号。   二、操作步骤       1、创建日志表            要把日志持久化,必须在数据库中创建一张用来存储日志信息的表,表内字段为日志      的一个主要属性包括:

技术学习与分享 3813

Java 把系统日志存储

在本博客中,我们介绍了如何使用Java实现高性能的系统日志存储方案。通过合理的日志管理架构设计、灵活的日志级别配置以及异步写入机制,我们可以有效地记录系统运行情况,定位问题并提高系统性能。同时,我们还演示了使用log4j框架和数据库Appender来实现日志存储,并给出了具体的Java代码示例。希望本博客对你理解系统日志管理有所帮助,如果你有任何问题或建议,请随时留言。

qq_31532979的贺公子之数据科学与艺术博客,致力于科技向善,拥抱开源,要用技术的影响力来领导团队,而不是威权和职位! 1752
上一篇: 序(我的自传)
下一篇: 优雅的解决SpringBoot集成RabbitMQ序列化和反序列化的思路1
明日之春
博客等级 码龄15年 3粉丝 8原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值