IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> slf4j、logback、log4j打印出的日志行号不正确,如何获取正确的行号(调用者类里的行号) -> 正文阅读

[Java知识库]slf4j、logback、log4j打印出的日志行号不正确,如何获取正确的行号(调用者类里的行号)

目录

环境信息

说明

配置信息

问题描述

原因分析

解决方案

解决思路

LocationAwareLogger.java

ch.qos.logback.classic.Logger.java

解决方法

Log.java


环境信息

SpringBoot?2.1.15.RELEASE

slf4j:1.7.25

logback:1.2.3

说明

????????系统使用的是slf4j+logback日志组合,而且为了系统的个性化需求,封装了自己的日志操作类Log.java,不是直接使用slf4j的API:

private static Logger logger = LoggerFactory.getLogger(xxx.class);

配置信息

?logback配置:

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<property resource="configs-xml-logback.properties" />
	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %msg 【%t】【%logger{0}:%L】%n</pattern>
		</layout>
	</appender>

	<appender name="bizLog"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${log.root.path}/${app.name}/${app.name}.log</file>
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${log.root.path}/${app.name}/${app.name}.%d{yyyy-MM-dd}.log
			</fileNamePattern>
			<maxHistory>3</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %msg 【%t】【%logger{0}:%L】%n</pattern>
		</encoder>
	</appender>

	<!-- 异步输出 -->
	<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
		<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
		<discardingThreshold>0</discardingThreshold>
		<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
		<queueSize>256</queueSize>
		<!-- 添加附加的appender,最多只能添加一个 -->
		<appender-ref ref="bizLog" />
	</appender>

	<logger name="java.sql.Connection" level="${sql.log.level}" />
	<logger name="java.sql.Statement" level="${sql.log.level}" />
	<logger name="java.sql.PreparedStatement" level="${sql.log.level}" />
	
	<logger name="com.eternalinfo" level="${log.dev.level}" />
	
	<root level="${log.level}">
		<appender-ref ref="stdout" />
		<appender-ref ref="ASYNC" />
	</root>
	<jmxConfigurator />
</configuration>  

configs-xml-logback.properties

app.name=myApp
log.root.path=/home/myApp-logs
log.level=INFO
log.dev.level=DEBUG
sql.log.level=INFO

问题描述

logback的配置是生效的,%L是打印行号,在日志里也能显示出行号出来。

问题在于有很多行号的值不对,而且都是些重复的行数,比如51、117、149。

2021-09-22 14:06:49.238 INFO ?加载第7个子配置文件:file [D:\xxx.xml] 【main】【ResourceLookuper:117】
2021-09-22 14:06:49.248 INFO ?加载第8个子配置文件:file [D:\yyy.xml] 【main】【ResourceLookuper:117】
2021-09-22 14:06:49.256 DEBUG 总共加载了9配置文件 【main】【ResourceLookuper:51】
2021-09-22 14:06:49.256 DEBUG 开始处理已加载的第0个子配置文件:xxx.xml 【main】【XmlConfigDeserializer:51】
2021-09-22 14:38:27.517 WARN ?获取不到HttpServletRequest对象 【pool-14-thread-1】【HttpHolderUtil:149】

原因分析

????????问题出现以后,最开始是猜想这些行号是代理类里的行号,不过很快排除了:有些类并没有使用代理,打印出的日志行号还是不对。

? ? ? ? 仔细看了下日志的行号,发现了一个规律:

DEBUG级别的日志的行号是51

INFO级别的日志的行号是117

WARN级别的日志的行号是149

所以问题很可能出在日志操作类Log.java上,这些行号是日志操作类Log里的行号。打开Log类看了下,发现确实是这样:

Log.java? 有省略代码

import org.slf4j.Logger;

import ch.qos.logback.classic.LoggerContext;

public class Log
{
	public static final String LOG_LEVEL_KEY="log.level";
	public static final String OUT_LOG_CLASS_REGEX_KEY="out.log.class.regex";
	
	private Logger logger;
	private static final String PLACE_HODER_STR="\\{\\}";
	
	Log(Class<?> clz,LoggerContext loggerContext)
	{
//		logger=LoggerFactory.getLogger(clz); 
//		if(this.loggerContext==null) {
//			loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//		}
		logger=loggerContext.getLogger(clz);
	}
	
	private Logger getCurrentLog()
	{
		return logger;
	}
	
	private String getLogId(Object message) {
		String logId = LogIdUtil.getLogId()==null ?"":LogIdUtil.getLogId()+"||";
		return logId+message;
	}
	
	/**
	 * debug记录
	 * @param message
	 * @param e
	 */
	public void debug(Object message,Throwable e)
	{
		message = getLogId(message);
		getCurrentLog().debug(((String)message).replace("{}", ""),e);
	}
	
	
	/**
	 * message记录
	 * @param message
	 * @param e
	 */
	public void info(Object message,Throwable e)
	{
		message = getLogId(message);
		getCurrentLog().info(((String)message).replace("{}", ""),e);
	}
	
	
	
	public void warn(Object message,Throwable e)
	{
		message = getLogId(message);
		getCurrentLog().warn(((String)message).replace("{}", ""),e);
	}

...
	
}

原因在于打印日志是使用的日志操作类Log,真正调用slf4j的debug、info、warn的地方是Log类,所以打印出的行号是Log类里的行号,而不是调用Log类的地方(以下称为调用者类)的行号

getCurrentLog().debug(((String)message).replace("{}", ""),e);

解决方案

解决思路

? ? ? ? 在使用slf4j的时候,默认引用的是slf4j的org.slf4j.Logger接口(实现类看真正的日志框架是什么,比如这里是logback的ch.qos.logback.classic.Logger),而这个Logger接口还有一个子接口org.slf4j.spi.LocationAwareLogger,logback的ch.qos.logback.classic.Logger实现类也实现了这个接口:

LocationAwareLogger.java

package org.slf4j.spi;

import org.slf4j.Logger;
import org.slf4j.Marker;

/**
 * An <b>optional</b> interface helping integration with logging systems capable of 
 * extracting location information. This interface is mainly used by SLF4J bridges 
 * such as jcl-over-slf4j, jul-to-slf4j and log4j-over-slf4j or {@link Logger} wrappers
 * which need to provide hints so that the underlying logging system can extract
 * the correct location information (method name, line number).
 *
 * @author Ceki Gulcu
 * @since 1.3
 */
public interface LocationAwareLogger extends Logger {

    // these constants should be in EventContants. However, in order to preserve binary backward compatibility
    // we keep these constants here
    final public int TRACE_INT = 00;
    final public int DEBUG_INT = 10;
    final public int INFO_INT = 20;
    final public int WARN_INT = 30;
    final public int ERROR_INT = 40;

    /**
     * Printing method with support for location information. 
     * 
     * @param marker The marker to be used for this event, may be null.
     * @param fqcn The fully qualified class name of the <b>logger instance</b>,
     * typically the logger class, logger bridge or a logger wrapper.
     * @param level One of the level integers defined in this interface
     * @param message The message for the log event
     * @param t Throwable associated with the log event, may be null.
     */
    public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t);

}

ch.qos.logback.classic.Logger.java

public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {

    private static final long serialVersionUID = 5454405123156820674L; // 8745934908040027998L;

    /**
     * The fully qualified name of this class. Used in gathering caller
     * information.
     */
    public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();

    public void log(Marker marker, String fqcn, int levelInt, String message, Object[] argArray, Throwable t) {
        Level level = Level.fromLocationAwareLoggerInteger(levelInt);
        filterAndLog_0_Or3Plus(fqcn, marker, level, message, argArray, t);
    }
...
}

LocationAwareLogger这个接口的说明里写了:

这个可选接口可以帮助日志系统来输出正确的引用位置信息(方法名、行号),可以用于slf4j的桥连,比如jcl-over-slf4j, jul-to-slf4j and log4j-over-slf4j or Logger wrappers (或者Logger的装饰模式)

在这里,日志操作类Log相当于Logger的装饰器。

解决方法

修改日志操作类Log里的debug、info、warn等方法,改成LocationAwareLogger里的log方法

可以参考jcl-over-slf4j包里的org.apache.commons.logging.impl.SLF4JLocationAwareLog

Log.java

import org.slf4j.Logger;

import ch.qos.logback.classic.LoggerContext;

public class Log
{
	public static final String LOG_LEVEL_KEY="log.level";
	public static final String OUT_LOG_CLASS_REGEX_KEY="out.log.class.regex";
	
	private Logger logger;
	private static final String PLACE_HODER_STR="\\{\\}";
	
	Log(Class<?> clz,LoggerContext loggerContext)
	{
//		logger=LoggerFactory.getLogger(clz); 
//		if(this.loggerContext==null) {
//			loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//		}
		logger=loggerContext.getLogger(clz);
	}
	
	private Logger getCurrentLog()
	{
		return logger;
	}
	
	private String getLogId(Object message) {
		String logId = LogIdUtil.getLogId()==null ?"":LogIdUtil.getLogId()+"||";
		return logId+message;
	}
	
	/**
	 * debug记录
	 * @param message
	 * @param e
	 */
    public void debug(Object message, Throwable e) {
        message = getLogId(message);
        // 做判断是以防更改了日志实现框架之后,logger未实现LocationAwareLogger接口。如果只是logback使用,不需要
        if (this.logger instanceof LocationAwareLogger) {
            ((LocationAwareLogger) this.logger).log(null, FQCN, LocationAwareLogger.DEBUG_INT, (String) message, null, e);
            return;
        }
        this.logger.debug(((String) message).replace("{}", ""), e);
    }
	
	
	/**
	 * message记录
	 * @param message
	 * @param e
	 */
    public void info(Object message, Throwable e) {
        message = getLogId(message);
        if (this.logger instanceof LocationAwareLogger) {
            ((LocationAwareLogger) this.logger).log(null, FQCN, LocationAwareLogger.INFO_INT, (String) message, null, e);
            return;
        }
        getCurrentLog().info(((String) message).replace("{}", ""), e);
    }
	
	
    public void warn(Object message, Throwable e) {
        message = getLogId(message);
        if (this.logger instanceof LocationAwareLogger) {
            ((LocationAwareLogger) this.logger).log(null, FQCN, LocationAwareLogger.WARN_INT, (String) message, null, e);
            return;
        }
        getCurrentLog().warn(((String) message).replace("{}", ""), e);
    }

...
	
}

参考:

Slf4j+logback实现日志打印-获取调用者类及方法行数信息_wang37444的专栏

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-23 11:19:04  更:2021-09-23 11:20:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:50:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码