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 - 多线程基础之单例模式
大家好 , 这篇文章给大家带来的是单例模式 , 单例模式中分为懒汉模式和饿汉模式 , 懒汉模式是需要用的到的时候才去创建实例 , 而饿汉模式是程序一启动就立刻创建实例 , 在这其中还有很多其他问题需要我们去研究 推荐大家跳转到这里 , 观看效果更加 上一篇文章的链接我也贴在这…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
