springboot自定义日志以及行号正确展示
在开发springboot项目时,我们可能需要自定义日志实现。需要对slf4j的日志实现进行一次外层包装 这个很简单,按照org.slf4j.Logger方式定义一个类Logger类MyLogger。
让后实现MyLoggerImpl:
public class MyLoggerImpl implements CoreLogger {private static final String FQCN = CoreLoggerImpl.class.getName();private Logger logger = null;private Class<?> clazz;private String className;public CoreLoggerImpl(Logger logger, Class<?> clazz){this.logger = logger;this.clazz = clazz;}public CoreLoggerImpl(Logger logger, String clazz){this.logger = logger;this.className = clazz;}public CoreLoggerImpl(String clazz){this.logger = LoggerFactory.getLogger(clazz);this.className = clazz;}public CoreLoggerImpl(Class<?> clazz){this.logger = LoggerFactory.getLogger(clazz);this.clazz = clazz;}@Overridepublic String getName() {return logger.getName();}@Overridepublic boolean isTraceEnabled() {return logger.isTraceEnabled();}@Overridepublic void trace(String msg) {logger.trace(msg);}/*省略其他的*/@Overridepublic void error(Marker marker, String format, Object arg) {logger.error(marker, format, arg);}@Overridepublic void error(Marker marker, String format, Object arg1, Object arg2) {logger.error(marker, format, arg1, arg2);}@Overridepublic void error(Marker marker, String format, Object... arguments) {logger.error(marker, format, arguments);}@Overridepublic void error(Marker marker, String msg, Throwable t) {logger.error(marker, msg, t);}
}
然后就可以直接在外部实现获取我么自定义的日志打印了,其实这只是对slf4j进行了一次包装。
在使用过程中,发现这样的包装方式会导致日志打印出来的行号是错误的,行号打印的是MyLoggerImpl的调用行。
在使用slf4j的时候,默认引用的是slf4j的org.slf4j.Logger接口这个Logger接口还有一个子接口org.slf4j.spi.LocationAwareLogger,logback的ch.qos.logback.classic.Logger实现类也实现了这个接口:
public interface LocationAwareLogger extends Logger {// these constants should be in EventContants. However, in order to preserve binary backward compatibility// we keep these constants herefinal 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实现部分:
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,final Throwable t) {final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);if (decision == FilterReply.NEUTRAL) {if (effectiveLevelInt > level.levelInt) {return;}} else if (decision == FilterReply.DENY) {return;}buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);}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);}public void debug(String msg) {filterAndLog_0_Or3Plus(FQCN, null, Level.DEBUG, msg, null, null);}
}
通过更深的追踪,发现,最终决定行号的是以下的代码:LineOfCallerConverter
public class LineOfCallerConverter extends ClassicConverter {public String convert(ILoggingEvent le) {StackTraceElement[] cda = le.getCallerData();if (cda != null && cda.length > 0) {return Integer.toString(cda[0].getLineNumber());} else {return CallerData.NA;}}}
ILoggingEvent .getCallerData是获取调用栈信息的。然后去第一个节点(也就是我自定义的类)
public class LoggingEvent implements ILoggingEvent {public LoggingEvent(String fqcn, Logger logger, Level level, String message, Throwable throwable, Object[] argArray) {this.fqnOfLoggerClass = fqcn;this.loggerName = logger.getName();this.loggerContext = logger.getLoggerContext();this.loggerContextVO = loggerContext.getLoggerContextRemoteView();this.level = level;this.message = message;this.argumentArray = argArray;if (throwable == null) {throwable = extractThrowableAnRearrangeArguments(argArray);}if (throwable != null) {this.throwableProxy = new ThrowableProxy(throwable);LoggerContext lc = logger.getLoggerContext();if (lc.isPackagingDataEnabled()) {this.throwableProxy.calculatePackagingData();}}timeStamp = System.currentTimeMillis();}public StackTraceElement[] getCallerData() {if (callerDataArray == null) {callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass, loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());}return callerDataArray;}
}
CallerData.extract()方法就是获取slf4j的Logger调用之前的调用栈信息的。
public static StackTraceElement[] extract(Throwable t, String fqnOfInvokingClass, final int maxDepth, List<String> frameworkPackageList) {if (t == null) {return null;}StackTraceElement[] steArray = t.getStackTrace();StackTraceElement[] callerDataArray;int found = LINE_NA;for (int i = 0; i < steArray.length; i++) {if (isInFrameworkSpace(steArray[i].getClassName(), fqnOfInvokingClass, frameworkPackageList)) {// the caller is assumed to be the next stack frame, hence the +1.found = i + 1;} else {if (found != LINE_NA) {break;}}}// we failed to extract caller dataif (found == LINE_NA) {return EMPTY_CALLER_DATA_ARRAY;}int availableDepth = steArray.length - found;int desiredDepth = maxDepth < (availableDepth) ? maxDepth : availableDepth;callerDataArray = new StackTraceElement[desiredDepth];for (int i = 0; i < desiredDepth; i++) {callerDataArray[i] = steArray[found + i];}return callerDataArray;}
主要在于 fqnOfLoggerClass, 和 loggerContext.getMaxCallerDataDepth()参数,一个日志的调用连的开始处,一个是获取的栈深度。fqnOfLoggerClass就是 Logger.log()方法参数中的fqcn 参数。而fqcn就是日志Logger的类名。
所以只要我们控制了fqcn 参数,就可以控制,日志查找的栈位置,进而控制获取的文件以及行号。所以我们需要在我们自定义的日志类中直接调用LocationAwareLogger的log方法,并传入我们自己的 fqcn(MyLoggerImpl.class.getName)。
public class MyLoggerImpl implements MyLogger {private static final String FQCN = MyLoggerImpl.class.getName();private Logger logger = null;private Class<?> clazz;private String className;public CoreLoggerImpl(Logger logger, Class<?> clazz){this.logger = logger;this.clazz = clazz;}public CoreLoggerImpl(Logger logger, String clazz){this.logger = logger;this.className = clazz;}public CoreLoggerImpl(String clazz){this.logger = LoggerFactory.getLogger(clazz);this.className = clazz;}public CoreLoggerImpl(Class<?> clazz){this.logger = LoggerFactory.getLogger(clazz);this.clazz = clazz;}@Overridepublic String getName() {return logger.getName();}/*省略其他的*/@Overridepublic void error(Marker marker, String format, Object arg) {// 做判断是以防更改了日志实现框架之后,logger未实现LocationAwareLogger接口。如果只是logback使用,不需要//(final String localFQCN, final Marker marker, final Level level, final String msg, final Object param, final Throwable t)if (this.logger instanceof LocationAwareLogger) {((LocationAwareLogger) this.logger).log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, null, null);return;}logger.error(marker, format, arg);}
}
相关文章:
springboot自定义日志以及行号正确展示
在开发springboot项目时,我们可能需要自定义日志实现。需要对slf4j的日志实现进行一次外层包装 这个很简单,按照org.slf4j.Logger方式定义一个类Logger类MyLogger。 让后实现MyLoggerImpl: public class MyLoggerImpl implements CoreLogge…...
【GAOPS055】verilog 乘法、除法和取余
乘法硬件原理 结论 可以将乘法A x B转为A的移位相加。 利用乘2n就是左移n位的特性乘2^n就是左移n位的特性乘2n就是左移n位的特性,将数拆分为2n2^n2n表示 思路1 原始列竖式计算方法ref例2.9 思路2 B总是可以拆分为:B(an2nan−12n−1...a121a020)B(…...
TCP UPD详解
文章目录TCP UDP协议1. 概述2. 端口号 复用 分用3. TCP3.1 TCP首部格式3.2 建立连接-三次握手3.3 释放连接-四次挥手3.4 TCP流量控制3.5 TCP拥塞控制3.6 TCP可靠传输的实现3.7 TCP超时重传4. UDP5.TCP与UDP的区别TCP UDP协议 1. 概述 TCP、UDP协议是TCP/IP体系结构传输层中的…...
金三银四、金九银十 面试宝典 MySQL面试题 超级无敌全的面试题汇总(超万字的面试题,让你的MySQL无可挑剔)
MySQL数据库 - 面试宝典 又到了 金三银四、金九银十 的时候了,是时候收藏一波面试题了,面试题可以不学,但不能没有!🥁🥁🥁 一个合格的 计算机打工人 ,收藏夹里必须有一份 MySQL 八…...
【Java】初识Java
Java和C语言有许多类似之处,这里就只挑不一样的点来说,所以会比较杂乱哈~ 目录 1.数据类型 2.输入与输出 2.1三种输出 2.2输入 2.3循环输入输出 //猜数字小游戏 //打印乘法口诀表 3.方法 //交换两个数(数组的应用) //模…...
JVM相关知识
JVM类加载过程类什么时候被加载什么情况下会发生栈内存溢出JVM内存模型常量池回收方法区垃圾回收流程圾收集算法分代收集理论标记-清除算法标记-复制算法标记-整理算法类加载过程 加载–验证–准备–解析–初始化–使用–卸载 加载:通过全类名获取类的二进制流…...
【LeetCode】剑指 Offer(21)
目录 题目:剑指 Offer 39. 数组中出现次数超过一半的数字 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 40. 最小的k个数 -…...
线性求解器Ax=b的验证
文章目录前言Matrix MarketMatlab IORead dataWrite data测试C IORead and write dataDownload MatrixIO 代码下载参考网址前言 一般情况集成了一个线性求解器(Axb),我们需要验证其性能和精度,这时需要大量数据来做验证ÿ…...
java 事件处理机制 观察者模式
事件处理机制有三个要素事件、事件源、事件监听与java的对应关系如下事件事件源事件监听javaclassjava.util.EventObjectjava.util.EventObject 的 source 属性interfacejava.util.EventListener观察者模式又被称为发布-订阅(Publish/Subscribe)模式&…...
使用 HTML5 轻松验证表单插件
下载:https://download.csdn.net/download/mo3408/87559594 效果图: 当您通过表单从人们那里收集信息时,必须应用某种验证。如果不这样做,可能会导致客户流失、数据库中的垃圾数据甚至网站的安全漏洞。从历史上看,构建表单验证一直很痛苦。在服务器端,全栈框架会为您处理…...
【Error: ImagePullBackOff】Kubernetes中Nginx服务启动失败排查流程
❌pod节点启动失败,nginx服务无法正常访问,服务状态显示为ImagePullBackOff。 [rootm1 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-f89759699-cgjgp 0/1 ImagePullBackOff 0 103…...
九龙证券|直逼1.5万亿!A股融资余额创年内新高,青睐这些行业和个股
2023年以来,A股商场震动重复,商场走势整体先扬后抑,各路资金看法纷歧,但数据显现,融资客在此期间整体持续净买入,未受到商场动摇的明显冲击,融资余额日前已迫临1.5万亿元,创出年内新…...
【JavaScript】36_正则表达式
正则表达式 正则表达式 正则表达式用来定义一个规则通过这个规则计算机可以检查一个字符串是否符合规则 或者将字符串中符合规则的内容提取出来 正则表达式也是JS中的一个对象, 所以要使用正则表达式,需要先创建正则表达式的对象 new RegExp() 可以…...
参考 | 辨别真假笔记本三星内存条 (ddr4)
参考 | 辨别真假笔记本三星内存条 (ddr4) 文章目录参考 | 辨别真假笔记本三星内存条 (ddr4)1. 三星内存条标签纸上编码的含义2. 三星内存颗粒上编码的含义3. 辨别内容参考1. 三星内存条标签纸上编码的含义 内存条贴张上面有两串值得注意的编码, 其中编码的具体意义参考三星官方…...
JavaScript Math(算数)对象
Math(算数)对象的作用是:执行常见的算数任务。在线实例round()如何使用 round()。random()如何使用 random() 来返回 0 到 1 之间的随机数。max()如何使用 max() 来返回两个给定的数中的较大的数。(在 ECMASCript v3 之前…...
MyBatis里面用了多少种设计模式?
在MyBatis的两万多行的框架源码中,使用了大量的设计模式对工程架构中的复杂场景进行解耦,这些设计模式的巧妙使用是整个框架的精华。经过整理,大概有以下设计模式,如图1所示。图101类型:创建型模式▊ 工厂模式SqlSessi…...
第三十二周精华分享(2023.02.27-2023.03.06)
本帖是知识星球各类问答以及文章精华沉淀区,而知识星球相关资源沉淀则在置顶帖的「资源沉淀」中。 学计算机的都应该知道有个局部性原理,其实局部性原理在很多场合都适用,比如80%的圈友的痛点或者疑惑其实都集中在一些固定的方面或者问题上&…...
数学建模资料整理
数学建模中有三类团队: 第一类:拿到题目,讨论,然后建模手开始建模,编程手开始处理数据,写作手开始写作。 第二类:拿到题目,团内大佬,开始建模,然后编程&#…...
设计模式---抽象工厂模式
目录 1 介绍 2 优缺点 3 实现 1 介绍 抽象工厂模式(Abstract Factory Pattern) 是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在抽象工厂模式中,接口是负…...
Java Web 实战 07 - 多线程基础之单例模式
大家好 , 这篇文章给大家带来的是单例模式 , 单例模式中分为懒汉模式和饿汉模式 , 懒汉模式是需要用的到的时候才去创建实例 , 而饿汉模式是程序一启动就立刻创建实例 , 在这其中还有很多其他问题需要我们去研究 推荐大家跳转到这里 , 观看效果更加 上一篇文章的链接我也贴在这…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
