欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SLF4J与Log4j实现日志记录

程序员文章站 2022-03-16 10:34:26
...

1 Introduction

没什么介绍…只是记录一下自己的使用习惯

Maven

<slf4j.version>1.7.7</slf4j.version>

<dependency>
    <groupId>org.slf4j</groupId>        <artifactId>slf4j-api</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>${slf4j.version}</version>
    </dependency>
<!-- common-logging 实际调用slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<!-- java.util.logging 实际调用slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>${slf4j.version}</version>
</dependency>

2 Log4J的配置

其中tabook为项目的名字

# log4j.properties
# Output pattern : date thread priority [category] - message   FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7
log4j.rootLogger=WARN, Console, RollingFile, ErrorRollingFile

#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d %-5p [%c{5}] - %m%n

#RollingFile
log4j.appender.RollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingFile.File=../logs/tabook/tabook.log
log4j.appender.RollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#ErrorRollingFile
log4j.appender.ErrorRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ErrorRollingFile.Threshold=Error
log4j.appender.ErrorRollingFile.File=../logs/tabook/tabook_error.log
log4j.appender.ErrorRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ErrorRollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] -%m%n


log4j.logger.com.nevercome.tabook=DEBUG
log4j.logger.com.nevercome.tabook.common.security.shiro=WARN
log4j.logger.com.nevercome.tabook.common.utils.JedisUtils=WARN
log4j.logger.com.nevercome.tabook.modules.sys.web.LoginController=WARN
#log4j.logger.com.thinkgem.jeesite.modules.oa.dao.OaNotifyDao.findCount=WARN

3 测试使用

分别在log4j的配置文件中定义日志级别的包下进行测试。
观察控制台与两个日志文件的输出

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author: sun
 * @date: 2019/4/5
 */
public class LogTest {
    private Logger logger = LoggerFactory.getLogger(getClass());

    // 如果你直接运行了Test,可能会问
    // 为什么没有输出到配置的目录?
    // 其实它是输出到配置的目录了
    // 因为你以为配置的目录的相对路径是web容器启动之后的logs
    // 它不是没有输出,而是在别的地方
    @Test
    public void logTest() {
        logger.info("I'm {} message", "info");
        logger.debug("I'm {} message", "debug");
        logger.warn("I'm {} message", "warn");
        logger.error("I'm {} message", "error");
    }
}

4 拦截器Interceptor与日志

与日志记录说在一起的技术通常有切面AOP、过滤器Filter,拦截器Interceptor。每种技术当然各有特点…拦截也好过滤也罢在技术与应用细节上有许多不同,但其核心思想都是在一个或系列动作、事件的一个或某些时刻插入进去进行自己的操作。

代码出自JeeSite快速开发框架,请求拦截,进行日志记录。

import com.nevercome.tabook.common.mapper.JsonMapper;
import com.nevercome.tabook.common.service.BaseService;
import com.nevercome.tabook.common.utils.DateUtils;
import com.nevercome.tabook.modules.sys.utils.LogUtils;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Map;

/**
 * 日志拦截器
 */
public class LogInterceptor extends BaseService implements HandlerInterceptor {

    private static final ThreadLocal<Long> startTimeThreadLocal =
            new NamedThreadLocal<Long>("ThreadLocal StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        if (logger.isDebugEnabled()) {
            // 开始时间
            long beginTime = System.currentTimeMillis();
            // 线程绑定变量(该数据只有当前请求的线程可见)
            startTimeThreadLocal.set(beginTime);
            // sun 20190405 添加对请求参数的打印
            Map paramMap = request.getParameterMap();
            logger.debug("开始计时: {}  URI: {} Params: {}", new SimpleDateFormat("hh:mm:ss.SSS")
                    .format(beginTime), request.getRequestURI(), JsonMapper.toJsonString(paramMap));
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        if (modelAndView != null) {
            logger.info("ViewName: " + modelAndView.getViewName());
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {

        // 保存日志
        LogUtils.saveLog(request, handler, ex, null);

        // 打印JVM信息。
        if (logger.isDebugEnabled()) {
            long beginTime = startTimeThreadLocal.get(); // 得到线程绑定的局部变量(开始时间)
            long endTime = System.currentTimeMillis(); // 结束时间
            logger.debug("计时结束:{}  耗时:{}  URI: {}  最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
                    new SimpleDateFormat("hh:mm:ss.SSS").format(endTime), DateUtils.formatDateTime(endTime - beginTime),
                    request.getRequestURI(), Runtime.getRuntime().maxMemory() / 1024 / 1024, Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024,
                    (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
            // 删除线程变量中的数据,防止内存泄漏
            startTimeThreadLocal.remove();
        }
    }
}

这其中的BaseService是否继承并不十分重要。

配置MVC的拦截器

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="${adminPath}/**" />
        <mvc:exclude-mapping path="${adminPath}/"/>
        <mvc:exclude-mapping path="${adminPath}/login"/>
        <mvc:exclude-mapping path="${adminPath}/sys/menu/tree"/>
        <mvc:exclude-mapping path="${adminPath}/sys/menu/treeData"/>
        <mvc:exclude-mapping path="${adminPath}/oa/oaNotify/self/count"/>
        <bean class="com.nevercome.tabook.modules.sys.interceptor.LogInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

5 关于MyBatis

Log4j会自动按照配置打印Mybatis的Sql语句,如果你不想打印某一个模块的Sql语句,只需要控制这个dao层的package的打印级别为DEBUG以上就可以了。比如:

com.nevercome.tabook.modules.sys.dao=WARN