JDBCAppender记录日志失效问题详解,Log4j写入数据库

2019-11-26 16:35 来源:未知

官网:
图片 1

 log4j是一个优秀的开源日志记录项目,我们不仅可以对输出的日志的格式自定义,还可以自己定义日志输出的目的地,比如:屏幕,文本文件,数据库,甚至能通过socket输出。本节主要讲述如何将日志信息输入到数据库(可以插入任何数据库,在此主要以MSSQL为例进行详解)。
用log4j将日志写入数据库主要用到是log4j包下的JDBCAppender类,它提供了将日志信息异步写入数据的功能,我们可以直接使用这个类将我们的日志信息写入数据库;也可以扩展JDBCAppender类,就是将JDBCAppender类作为基类。下面将通过一个实例来讲解log4j是如何将日志信息写入数据库的。
我们的需求:我们在软件开发的过程中需要将调试信息、操作信息等记录下来,以便后面的审计,这些日志信息包括用户ID、用户姓名、操作类、路径、方法、操作时间、日志信息。
设计思想:我们采用JDBCAppender类直接将日志信息插入数据库,所有只需要在配置文件配置此类就可以;要获得用户信息需要用过滤器来实现;(假如不需要用户的信息,就不需要设计过滤器,其实大部分情况下都是需要这些用户信息,尤其是在web应用开发中)在日志信息中获得用户信息,就的通过过滤器的request或session对象,从session中拿到用户信息怎样传到log4j呢,log4j为我们提供了MDC(MDC是log4j种非常有用类,它们用于存储应用程序的上下文信息(context infomation),从而便于在log中使用这些上下文信息。MDC内部使用了类似map的机制来存储信息,上下文信息也是每个线程独立地储存,所不同的是信息都是以它们的key值存储在”map”中。相对应的方法,

 

MDC.put(key, value); MDC.remove(key); MDC.get(key);

事件:
最近在项目中使用log4j 1.x JDBCAppender记录管理员操作日志到数据库,在测试时发现系统启动后运行一段时间无法继续记录相关操作日志到数据库。
配置如下:
log4j.properties:

在配置PatternLayout的时候使用:%x{key}来输出对应的value)。有了MDC,我们可以在过滤器中先获得用户信息,再用MDC.Put(“key”)方法,log在执行sql语句时通过%x{key}来输出对应的value。

log4j.logger.oplog=INFO, oplog
log4j.appender.oplog=com.lenovo.moc.portal.dao.LogJDBCAppender
log4j.appender.oplog.driver=com.mysql.jdbc.Driver
log4j.appender.oplog.URL=jdbc:mysql://192.168.2.164:3306/oplog?characterEncoding=utf8
log4j.appender.oplog.user=xxx
log4j.appender.oplog.password=xxx
log4j.appender.oplog.sql=insert into operation_loginfo (staff_id, staff_name, user_role, op_type, op_alias, create_time, content, content_alias) values ('%x{login_staff_id}', '%x{login_staff_name}','%x{login_user_role}', '%x{op_type}', '%x{op_alias}', '%d{yyyy-mm-dd hh:mm:ss}','%m', '%x{content_alias}')
log4j.appender.oplog.layout=org.apache.log4j.PatternLayout

实现步骤:
1、在你的项目中要确保有log4j和commons-logging这两个jar文件;
2、设置要你要插入日志信息的表结构

java代码:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[WDZLOG]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[WDZLOG]
GO

CREATE TABLE [dbo].[WDZLOG] (
    [WDZLOGID] [int] IDENTITY (1, 1) NOT NULL ,
    [LogName] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,//用户ID
    [UserName] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,//用户姓名
    [Class] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,//类名
    [Mothod] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL //,方法名
    [CreateTime] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,//产生时间
    [LogLevel] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,//日志级别
    [MSG] [varchar] (555) COLLATE Chinese_PRC_CI_AS NULL //日志信息
) ON [PRIMARY]
GO
public class OperationLogService {
  private static final Logger logger = Logger.getLogger(OperationLogService.class);
  private static ExecutorService threadPool = Executors.newFixedThreadPool(3);;

  private static ExecutorService getThreadPool() {
    return threadPool;
  }

  /**
  * 记录操作日志
  * @param login_staff_id 员工id
  * @param login_staff_name 员工姓名
  * @param login_user_role 员工角色
  * @param op_type 操作类型
  * @param op_alias 操作别名
  * @param content_alias 操作内容
  * @param msg 附加信息
  */
  public static void log(String login_staff_id, String login_staff_name, String login_user_role, String op_type,
    String op_alias, String content_alias, final String msg) {
    getThreadPool().execute(new Runnable() {
      @Override
      public void run() {
        MDC.put("login_staff_id", login_staff_id);
        MDC.put("login_staff_name", login_staff_name);
        MDC.put("login_user_role", login_user_role);
        MDC.put("op_type", op_type);
        MDC.put("op_alias", op_alias);
        MDC.put("content_alias", content_alias);
       logger.info(msg);
      }
    });
  }

  public static void main(String[] args) {
    log("1", "zhangsan", "admin", "add_user", "添加用户", "zhangsan添加用户", "test msg");
  }
}

3、配置文件(摘自我们的项目)后面将对此配置文件进行详细讲解,它也log4j的核心部分。

 

log4j.properties
log4j.rootLogger=INFO,stdout

log4j.logger.org.springframework.web.servlet=INFO,db

log4j.logger.org.springframework.beans.factory.xml=INFO
log4j.logger.com.neam.stum.user=INFO,db

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%c] - - <%m>%n

log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/exppower.log
log4j.appender.logfile.DatePattern=.yyyy-MM-dd
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] wang- <%m>%n

########################

# JDBC Appender

#######################


#log4j.logger.business=INFO,db
#log4j.appender.db=com.neam.commons.MyJDBCAppender
log4j.appender.db=JDBCExtAppender

log4j.appender.db.BufferSize=10

log4j.appender.db.sqlname=log

log4j.appender.db.driver=net.sourceforge.jtds.jdbc.Driver

log4j.appender.db.URL=jdbc:jtds:SqlServer://localhost:1433;DatabaseName=pubs

log4j.appender.db.user=sa

log4j.appender.db.password=sa

log4j.appender.db.sql=insert into WDZLOG (LogName,UserName,Class,Mothod,createTime,LogLevel,MSG) values ('%X{userId}','%X{userName}','%C','%M','%d{yyyy-MM-dd HH:mm:ss}','%p','%m')

log4j.appender.db.layout=org.apache.log4j.PatternLayout

解决办法:
通过查看log4j 1.x JDBCAppender源码发现,并没有对数据库连接的有效性进行判断。即:一旦数据库连接断开,就无法继续写入日志。
故而,通过扩展JDBCAppender的方式,进行数据库连接重连处理:

4、编写过滤器(ResFilter.java)

/**
* 自定义实现Log4j日志组件,将日志记录到数据库<br />.
* 解决问题: 原生组件在系统运行过程中可能会出现数据库连接断开,导致无法正常记录日志信息到数据库.
*
* @desc com.lenovo.moc.portal.dao.LogJDBCAppender
* @author chench9@lenovo.com
* @date 2017年3月15日
*/
public class LogJDBCAppender extends JDBCAppender {
  private static final Logger logger = Logger.getLogger(LogJDBCAppender.class);

  @Override
  protected Connection getConnection() throws SQLException {
    Connection connection = super.getConnection();
    if(connection == null || connection.isClosed()) {
      logger.warn(String.format("reconnect log jdbc appender connection"));
      connection = reconnect();
    }
    return connection;
  }

  /**
  * 重新创建数据库连接
  * @return
  * @throws SQLException
  */
  private Connection reconnect() throws SQLException {
    Connection connection = DriverManager.getConnection(databaseURL, databaseUser,databasePassword);
    return connection;
  }

  /**
  * 重载父类方法,打印错误信息到日志文件 <br />
  * 同时,处理数据库重连并在出错时重试记录日志信息.
  */
  @Override
  protected void execute(String sql) throws SQLException {
    try {
      super.execute(sql);
    } catch (Exception e) {
      logger.error(String.format("log jdbc appender execute sql eror: %s", getSql()), e);
      closeConnectionInterval();
      super.execute(sql);
    }
  }

  // 真正地关闭数据库连接
  private void closeConnectionInterval() {
    if(connection == null) {
      return;
    }

    try {
      connection.close();
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      connection = null;
    }
  }
}
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.apache.log4j.MDC;

import com.neam.domain.User;

public class ResFilter implements Filter{


    private final static double DEFAULT_USERID= Math.random()*100000.0;  

    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws IOException, ServletException {
       HttpServletRequest req=(HttpServletRequest)request;
        HttpSession session= req.getSession();
        if (session==null){
            MDC.put("userId",DEFAULT_USERID);  
        }
        else{
            User customer=(User)session.getAttribute("user");
            if (customer==null){
                MDC.put("userId",DEFAULT_USERID);
                MDC.put("userName",DEFAULT_USERID);
            }
            else
            {
                MDC.put("userId",customer.getName());
                MDC.put("userName",customer.getName());
            }
        }
        //logger.info("test for MDC.");

       chain.doFilter(request, response);
    }
    public void init(FilterConfig Config) throws ServletException {
//     this.filterConfig = Config;
//     String ccc = Config.getServletContext().getInitParameter("cherset");
//     this.targetEncoding = Config.getInitParameter("cherset");

    }
}

 

5、在需要写入日志的地方引入

log4j 1.x org.apache.log4j.jdbc.JDBCAppender类图:
图片 2

private Log logger = LogFactory.getLog(this.getClass());

在具体方法中就可以写入日志
logger.info("");
logger.debug("");
logger.warn("");
logger.error("");

org.apache.log4j.jdbc.JDBCAppender数据库连接实现:

配置文件详解: log4j.properties
log4j.properties
log4j.rootLogger=INFO,stdout

TAG标签:
版权声明:本文由金沙澳门官网4166发布于文物考古,转载请注明出处:JDBCAppender记录日志失效问题详解,Log4j写入数据库