❶ 大厂都是如何解决java日志级别,重复记录、丢日志问题
1SLF4J日志行业的现状框架繁不同类库可能使用不同日志框架,兼容难,无法接入统一日志,让运维很头疼!
配置复杂由于配置文件一般是xml文件,内容繁杂!很多人喜欢从其他项目或网上闭眼!
随意度高因为不会直接导致代码bug,测试人员也难发现问题,开发就没仔细考虑日志内容获取的性能开销,随意选用日志级别!
Logback、Log4j、Log4j2、commons-logging及java.util.logging等,都是Java体系的日志框架。不同的类库,还可能选择使用不同的日志框架,导致日志统一管理困难。
SLF4J(SimpleLoggingFacadeForJava)就为解决该问题而生
提供统一的日志门面API图中紫色部分,实现中立的日志记录API
桥接功能蓝色部分,把各种日志框架API桥接到SLF4JAPI。这样即使你的程序使用了各种日志API记录日志,最终都可桥接到SLF4J门面API
适配功能红色部分,绑定SLF4JAPI和实际的日志框架(灰色部分)
SLF4J只是日志标准,还是需要实际日志框架。日志框架本身未实现SLF4JAPI,所以需要有个前置转换。Logback本身就按SLF4JAPI标准实现,所以无需绑定模块做转换。
虽然可用log4j-over-slf4j实现Log4j桥接到SLF4J,也可使用slf4j-log4j12实现SLF4J适配到Log4j,也把它们画到了一列,但是它不能同时使用它们,否则就会产生死循环。jcl和jul同理。
虽然图中有4个灰色的日志实现框架,但业务开发使用最多的还是Logback和Log4j,都是同一人开发的。Logback可认为是Log4j改进版,更推荐使用,已是社会主流。
SpringBoot的日志框架也是Logback。那为什么我们没有手动引入Logback包,就可直接使用Logback?
spring-boot-starter模块依赖spring-boot-starter-logging模块,而spring-boot-starter-logging自动引入logback-classic(包含SLF4J和Logback日志框架)和SLF4J的一些适配器。
2异步日志就肯定能提高性能?如何避免日志记录成为系统性能瓶颈呢?这关系到磁盘(比如机械磁盘)IO性能较差、日志量又很大的情况下,如何记录日志。
2.1案例定义如下的日志配置,一共有两个Appender:
FILE是一个FileAppender,用于记录所有的日志
CONSOLE是一个ConsoleAppender,用于记录带有time标记的日志
把大量日志输出到文件中,日志文件会非常大,若性能测试结果也混在其中,就很难找到那条日志了。所以,这里使用EvaluatorFilter对日志按照标记进行过滤,并将过滤出的日志单独输出到控制台。该案例中给输出测试结果的那条日志上做了time标记。
配合使用标记和EvaluatorFilter,可实现日志的按标签过滤。
测试代码:实现记录指定次数的大日志,每条日志包含1MB字节的模拟数据,最后记录一条以time为标记的方法执行耗时日志:
执行程序后发现,记录1000次日志和10000次日志的调用耗时,分别是5.1s和39s
对只记录文件日志的代码,这耗时过长了。
2.2源码解析FileAppender继承自OutputStreamAppender
在追加日志时,是直接把日志写入OutputStream中,属同步记录日志
所以日志大量写入才会旷日持久。如何才能实现大量日志写入时,不会过多影响业务逻辑执行耗时而影响吞吐量呢?
2.3AsyncAppender使用Logback的AsyncAppender,即可实现异步日志记录。
AsyncAppender类似装饰模式,在不改变类原有基本功能情况下,为其增添新功能。这便可把AsyncAppender附加在其他Appender,将其变为异步。
定义一个异步AppenderASYNCFILE,包装之前的同步文件日志记录的FileAppender,即可实现异步记录日志到文件
记录1000次日志和10000次日志的调用耗时,分别是537ms和1019ms
异步日志真的如此高性能?并不,因为它并没有记录下所有日志。
3AsyncAppender异步日志的天坑记录异步日志撑爆内存
记录异步日志出现日志丢失
记录异步日志出现阻塞。
3.1案例模拟个慢日志记录场景:首先,自定义一个继承自ConsoleAppender的MySlowAppender,作为记录到控制台的输出器,写入日志时睡1s。
配置文件中使用AsyncAppender,将MySlowAppender包装为异步日志记录
测试代码
耗时很短但出现日志丢失:要记录1000条日志,最终控制台只能搜索到215条日志,而且日志行号变问号。
原因分析AsyncAppender提供了一些配置参数,而当前没用对。
源码解析includeCallerData默认false:方法行号、方法名等信息不显示
queueSize控制阻塞队列大小,使用的ArrayBlockingQueue阻塞队列,默认容量256:内存中最多保存256条日志
discardingThreshold丢弃日志的阈值,为防止队列满后发生阻塞。默认队列剩余容量<队列长度的20%,就会丢弃TRACE、DEBUG和INFO级日志
neverBlock控制队列满时,加入的数据是否直接丢弃,不会阻塞等待,默认是false
//阻塞队列:实现异步日志的核心BlockingQueueblockingQueue;//默认队列大小publicstaticfinalintDEFAULT_QUEUE_SIZE=256;intqueueSize=DEFAULT_QUEUE_SIZE;staticfinalintUNDEFINED=-1;intdiscardingThreshold=UNDEFINED;//当队列满时:加入数据时是否直接丢弃,不会阻塞等待booleanneverBlock=false;
@Overridepublicvoidstart(){...blockingQueue=newArrayBlockingQueue(queueSize);if(discardingThreshold==UNDEFINED)//默认丢弃阈值是队列剩余量低于队列长度的20%,参见方法discardingThreshold=queueSize/5;...}
@Overrideprotectedvoidappend(EeventObject){if(()&&isDiscardable(eventObject)){//判断是否可以丢数据return;}preprocess(eventObject);put(eventObject);}
privateboolean(){return(blockingQueue.remainingCapacity()<discardingThreshold);}
privatevoidput(EeventObject){if(neverBlock){//根据neverBlock决定使用不阻塞的offer还是阻塞的put方法blockingQueue.offer(eventObject);}else{putUninterruptibly(eventObject);}}//以阻塞方式添加数据到队列privatevoidputUninterruptibly(EeventObject){booleaninterrupted=false;try{while(true){try{blockingQueue.put(eventObject);break;}catch(InterruptedExceptione){interrupted=true;}}}finally{if(interrupted){Thread.currentThread().interrupt();}}}}
队列满时:offer不阻塞,而put会阻塞
neverBlock为true时,使用offer
<ILoggingEvent>{//是否收集调用方数据booleanincludeCallerData=false;protectedbooleanisDiscardable(ILoggingEventevent){Levellevel=event.getLevel();//丢弃≤INFO级日志returnlevel.toInt()<=Level.INFO_INT;}protectedvoidpreprocess(ILoggingEventeventObject){eventObject.prepareForDeferredProcessing();if(includeCallerData)eventObject.getCallerData();}}publicclassAsyncAppenderBase<E><E>implementsAppenderAttachable<E>{默认队列大小256,达到80%后开始丢弃<=INFO级日志后,即可理解日志中为什么只有两百多条INFO日志了。
queueSize过大可能导致OOM
queueSize较小默认值256就已经算很小了,且discardingThreshold设置为大于0(或为默认值),队列剩余容量少于discardingThreshold的配置就会丢弃<=INFO日志。这里的坑点有两个:
因为discardingThreshold,所以设置queueSize时容易踩坑。比如本案例最大日志并发1000,即便置queueSize为1000,同样会导致日志丢失
discardingThreshold参数容易有歧义,它不是百分比,而是日志条数。对于总容量10000队列,若希望队列剩余容量少于1000时丢弃,需配置为1000
neverBlock默认false意味总可能会出现阻塞。
若discardingThreshold=0,那么队列满时再有日志写入就会阻塞
若discardingThreshold!=0,也只丢弃≤INFO级日志,出现大量错误日志时,还是会阻塞
queueSize、discardingThreshold和neverBlock三参密不可分,务必按业务需求设置:
若优先绝对性能,设置neverBlock=true,永不阻塞
若优先绝不丢数据,设置discardingThreshold=0,即使≤INFO级日志也不会丢。但最好把queueSize设置大一点,毕竟默认的queueSize显然太小,太容易阻塞。
若兼顾,可丢弃不重要日志,把queueSize设置大点,再设置合理的discardingThreshold
以上日志配置最常见两个误区
再看日志记录本身的误区。
4如何选择日志级别?使用{}占位符,就不用判断loglevel了吗?
据不知名网友说道:SLF4J的{}占位符语法,到真正记录日志时才会获取实际参数,因此解决了日志数据获取的性能问题。是真的吗?
验证代码:返回结果耗时1s
若记录DEBUG日志,并设置只记录>=INFO级日志,程序是否也会耗时1s?三种方法测试:
拼接字符串方式记录slowString
使用占位符方式记录slowString
先判断日志级别是否启用DEBUG。
前俩方式都调用slowString,所以都耗时1s。且方式二就是使用占位符记录slowString,这种方式虽允许传Object,不显式拼接String,但也只是延迟(若日志不记录那就是省去)日志参数对象.toString()和字符串拼接的耗时。
本案例除非事先判断日志级别,否则必调用slowString。所以使用{}占位符不能通过延迟参数值获取,来解决日志数据获取的性能问题。
除事先判断日志级别,还可通过lambda表达式延迟参数内容获取。但SLF4J的API还不支持lambda,因此需使用Log4j2日志API,把Lombok的@Slf4j注解替换为@Log4j2注解,即可提供lambda表达式参数的方法:
这样调用debug,签名Supplier<?>,参数就会延迟到真正需要记录日志时再获取:
所以debug4并不会调用slowString方法
只是换成Log4j2API,真正的日志记录还是走的Logback,这就是SLF4J适配的好处。
总结SLF4J统一了Java日志框架。在使用SLF4J时,要理清楚其桥接API和绑定。若程序启动时出现SLF4J错误提示,可能是配置问题,可使用Maven的dependency:tree命令梳理依赖关系。
异步日志解决性能问题,是用空间换时间。但空间毕竟有限,当空间满,要考虑阻塞等待or丢弃日志。若更希望不丢弃重要日志,那么选择阻塞等待;如果更希望程序不要因为日志记录而阻塞,那么就需要丢弃日志。
日志框架提供的参数化记录方式不能完全取代日志级别的判断。若日志量很大,获取日志参数代价也很大,就要判断日志级别,避免不记录日志也要耗时获取日志参数!
❷ 阿里强制使用SLF4J日志框架的缘由
想必小伙伴们都用过日志,虽然日志看起来可有可无,但是等到出问题的时候,就比较棘手。所以说日志框架使用好不好,规范不规范,直接影响了解决生产环境故障的效率,日志框架选的不好、用的不好,有可能影响环境的性能,也有可能影响排查问题的难易程度。
阿里Java开发手册--日志规约第一条:
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
代码样例:
为什么会有此规范呢?我们先来了解下日志框架。
Java中的日志框架分如下几种:
重点来看下Slf4j的官方说明
为什么阿里要强制 依赖使用日志框架 SLF4J 中的 API 或者说Slf4j的特点: 除了得益于面向接口编程(使用了门面模式),还有一个特性支持占位符,以及SELF4J的生态(SLF4J与其他日志组件的桥接)
门面(Facade)模式,对外隐藏了系统的复杂性,并向客户端提供了可以访问的接口,门面模式的好处是将客户端和子系统松耦合,方便子系统的扩展和维护。
正是门面模式这样的特点,使用SLF4J门面,不管日志组件使用的是log4j还是logback等等,对于调用者而言并不关心使用的是什么日志组件,而且对于日志组件的更换或者升级,调用的地方也不用做任何修改。
SLF4J中有一个重要的特性:占位符,{}可以拼接任意字符串,相比如其他框架的优点即不需要用+来拼接字符串,也就不会创建新的字符串对象。
使用注意点:
小贴士:
在发布SDK或服务框架时,要遵循 面向接口编程的思想 ,不要把SLF4J实现类进行发布向下传递,SLF4J实现类可以通过maven的scope来控制不进行向下传递。
以上是使用SLF4J的缘由和注意点的说明。
Slf4j Manual中有一张图清晰的展示了接入方式,如下:
slf4j bound to 其它log框架
slf4j bound to log redirection
在使用slf4j桥接时要注意避免形成死循环,在项目依赖的jar包中不要存在以下情况
想要更好的了解SLF4J,你需要了解JVM类加载机制
设计模式:门面模式、桥接模式。源码解析过程这里忽略。
❸ java日志
首先,在项目中的classes 中新建立一个log4j.properties文件即可;
在实际编程时,要使Log4j真正在系统中运行事先还要对配置文件进行定义。定义步骤就是对Logger、Appender及Layout的分别使用。Log4j支持两种配置文件格式,一种是XML格式的文件,一种是java properties(key=value)【Java特性文件(键=值)】。(这里只说明properties文件)
1、配置根Logger
其语法为:
log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
level : 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。appenderName:就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
例如:log4j.rootLogger=info,A1,B2,C3
2、配置日志信息输出目的地
其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class //
"fully.qualified.name.of.appender.class" 可以指定下面五个目的地中的一个:
1.org.apache.log4j.ConsoleAppender(控制台)
2.org.apache.log4j.FileAppender(文件)
3.org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
4.org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
5.org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
1.ConsoleAppender选项
Threshold=WARN:指定日志消息的输出最低层次。
ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
Target=System.err:默认情况下是:System.out,指定输出控制台
2.FileAppender 选项
Threshold=WARN:指定日志消息的输出最低层次。
ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
File=mylog.txt:指定消息输出到mylog.txt文件。
Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
3.DailyRollingFileAppender 选项
Threshold=WARN:指定日志消息的输出最低层次。
ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
File=mylog.txt:指定消息输出到mylog.txt文件。
Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
DatePattern=''.''yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
1)''.''yyyy-MM: 每月
2)''.''yyyy-ww: 每周
3)''.''yyyy-MM-dd: 每天
4)''.''yyyy-MM-dd-a: 每天两次
5)''.''yyyy-MM-dd-HH: 每小时
6)''.''yyyy-MM-dd-HH-mm: 每分钟
4.RollingFileAppender 选项
Threshold=WARN:指定日志消息的输出最低层次。
ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
File=mylog.txt:指定消息输出到mylog.txt文件。
Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。
MaxBackupIndex=2:指定可以产生的滚动文件的最大数。
3、配置日志信息的格式
其语法为:
1). log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
"fully.qualified.name.of.layout.class" 可以指定下面4个格式中的一个:
1.org.apache.log4j.HTMLLayout(以HTML表格形式布局),
2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
1.HTMLLayout 选项
LocationInfo=true:默认值是false,输出java文件名称和行号
Title=my app file: 默认值是 Log4J Log Messages.
2.PatternLayout 选项
ConversionPattern=%m%n :指定怎样格式化指定的消息。
3.XMLLayout 选项
LocationInfo=true:默认值是false,输出java文件和行号
2). log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
这里需要说明的就是日志信息格式中几个符号所代表的含义:
-X号: X信息输出时左对齐;
%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%r: 输出自应用启动到输出该log信息耗费的毫秒数
%c: 输出日志信息所属的类目,通常就是所在类的全名
%t: 输出产生该日志事件的线程名
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%: 输出一个"%"字符
%F: 输出日志消息产生时所在的文件名称
%L: 输出代码中的行号
%m: 输出代码中指定的消息,产生的日志具体信息
%n: 输出一个回车换行符,Windows平台为"
",Unix平台为"
"输出日志信息换行
可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。
3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉
比较详细的例子
log4j.rootLogger=INFO,consoleAppender,logfile,MAIL
log4j.addivity.org.apache=true
#ConsoleAppender,控制台输出
#FileAppender,文件日志输出
#SMTPAppender,发邮件输出日志
#SocketAppender,Socket日志
#NTEventLogAppender,Window NT日志
#SyslogAppender,
#JMSAppender,
#AsyncAppender,
#NullAppender
#文件输出:RollingFileAppender
#log4j.rootLogger = INFO,logfile
log4j.appender.logfile = org.apache.log4j.RollingFileAppender
log4j.appender.logfile.Threshold = INFO
# 输出以上的INFO信息
log4j.appender.logfile.File = INFO_log.html
#保存log文件路径
log4j.appender.logfile.Append = true
# 默认为true,添加到末尾,false在每次启动时进行覆盖
log4j.appender.logfile.MaxFileSize = 1MB
# 一个log文件的大小,超过这个大小就又会生成1个日志 # KB ,MB,GB
log4j.appender.logfile.MaxBackupIndex = 3
# 最多保存3个文件备份
log4j.appender.logfile.layout = org.apache.log4j.HTMLLayout
# 输出文件的格式
log4j.appender.logfile.layout.LocationInfo = true
#是否显示类名和行数
log4j.appender.logfile.layout.Title =title:\u63d0\u9192\u60a8\uff1a\u7cfb\u7edf\u53d1\u751f\u4e86\u4e25\u91cd\u9519\u8bef
#html页面的 < title >
############################## SampleLayout ####################################
# log4j.appender.logfile.layout = org.apache.log4j.SampleLayout
############################## PatternLayout ###################################
# log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
# log4j.appender.logfile.layout.ConversionPattern =% d % p [ % c] - % m % n % d
############################## XMLLayout #######################################
# log4j.appender.logfile.layout = org.apache.log4j.XMLLayout
# log4j.appender.logfile.layout.LocationInfo = true #是否显示类名和行数
############################## TTCCLayout ######################################
# log4j.appender.logfile.layout = org.apache.log4j.TTCCLayout
# log4j.appender.logfile.layout.DateFormat = ISO8601
#NULL, RELATIVE, ABSOLUTE, DATE or ISO8601.
# log4j.appender.logfile.layout.TimeZoneID = GMT - 8 : 00
# log4j.appender.logfile.layout.CategoryPrefixing = false ##默认为true 打印类别名
# log4j.appender.logfile.layout.ContextPrinting = false ##默认为true 打印上下文信息
# log4j.appender.logfile.layout.ThreadPrinting = false ##默认为true 打印线程名
# 打印信息如下:
#2007 - 09 - 13 14 : 45 : 39 , 765 [http - 8080 - 1 ] ERROR com.poxool.test.test - error成功关闭链接
###############################################################################
#每天文件的输出:DailyRollingFileAppender
#log4j.rootLogger = INFO,errorlogfile
log4j.appender.errorlogfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorlogfile.Threshold = ERROR
log4j.appender.errorlogfile.File = ../logs/ERROR_log
log4j.appender.errorlogfile.Append = true
#默认为true,添加到末尾,false在每次启动时进行覆盖
log4j.appender.errorlogfile.ImmediateFlush = true
#直接输出,不进行缓存
# ' . ' yyyy - MM: 每个月更新一个log日志
# ' . ' yyyy - ww: 每个星期更新一个log日志
# ' . ' yyyy - MM - dd: 每天更新一个log日志
# ' . ' yyyy - MM - dd - a: 每天的午夜和正午更新一个log日志
# ' . ' yyyy - MM - dd - HH: 每小时更新一个log日志
# ' . ' yyyy - MM - dd - HH - mm: 每分钟更新一个log日志
log4j.appender.errorlogfile.DatePattern = ' . ' yyyy - MM - dd ' .log '
#文件名称的格式
log4j.appender.errorlogfile.layout = org.apache.log4j.PatternLayout
log4j.appender.errorlogfile.layout.ConversionPattern =%d %p [ %c] - %m %n %d
#控制台输出:
#log4j.rootLogger = INFO,consoleAppender
log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.Threshold = ERROR
log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern =%d %-5p %m %n
log4j.appender.consoleAppender.ImmediateFlush = true
# 直接输出,不进行缓存
log4j.appender.consoleAppender.Target = System.err
# 默认是System.out方式输出
#发送邮件:SMTPAppender
#log4j.rootLogger = INFO,MAIL
log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold = INFO
log4j.appender.MAIL.BufferSize = 10
log4j.appender.MAIL.From = [email protected]
log4j.appender.MAIL.SMTPHost = smtp.gmail.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = [email protected]
log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern =%d - %c -%-4r [%t] %-5p %c %x - %m %n
#数据库:JDBCAppender
log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL = jdbc:oracle:thin:@ 210.51 . 173.94 : 1521 :YDB
log4j.appender.DATABASE.driver = oracle.jdbc.driver.OracleDriver
log4j.appender.DATABASE.user = ydbuser
log4j.appender.DATABASE.password = ydbuser
log4j.appender.DATABASE.sql = INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n ' )
log4j.appender.DATABASE.layout = org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern =% d - % c -%- 4r [ % t] %- 5p % c % x - % m % n
#数据库的链接会有问题,可以重写org.apache.log4j.jdbc.JDBCAppender的getConnection() 使用数据库链接池去得链接,可以避免insert一条就链接一次数据库
❹ Java日志框架:slf4j作用及其实现原理
日志技术框架一览JUL:JDK中的日志记录工具,也常称为JDKLog、jdk-logging。
LOG4J1:一个具体的日志实现框架。
LOG4J2:一个具体的日志实现框架,是LOG4J1的下一个版本。
LOGBACK:一个具体的日志实现框架,但其性能更好。
JCL:一个日志门面,提供统一的日志记录接口,也常称为commons-logging。
SLF4J:一个日志门面,与JCL一样提供统一的日志记录接口,可以方便地切换看具体的实现框架。
注意:JUL、LOG4J1、LOG4J2、LOGBACK是日志实现框架,而JCL、SLF4J是日志实现门面(接口)。
SLF4J结构概述slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:
提供日志接口
提供获取具体日志对象的方法如如上图所示:SLF4J作为日志打印实现的适配层,业务代码不需要关心具体使用哪一种日志系统,且如何打印日志。
1.引入对应jar包
25<dependency>26<groupId>org.slf4j</groupId>27<artifactId>slf4j-api</artifactId>28<version>1.7.32</version>29</dependency>30<dependency>31<groupId>ch.qos.logback</groupId>32<artifactId>logback-classic</artifactId>33<version>1.2.6</version>34</dependency>2.增加配置文件log4j2.xml放在resource目录下:
<?xmlversion="1.0"encoding="UTF-8"?><configuration><!--<includeresource="org/springframework/boot/logging/logback/base.xml"/>--><!--appenders--><appendername="DEFAULT-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/common.log</file><encoder><pattern>[%d][%level][%X{xRequestId}]%logger-%m%n</pattern><charset>UTF-8</charset><!--此处设置字符集--></encoder><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--dailyrollover--><fileNamePattern>${LOG_PATH}/common.log.%d{yyyy-MM-dd}</fileNamePattern></rollingPolicy></appender><!--loggers--><loggername="com.zenlayer.zenconsole.ocs"level="INFO"additivity="false"><appender-refref="APP-DEFAULT-APPENDER"/><appender-refref="ERROR-APPENDER"/></logger><rootlevel="INFO"><appender-refref="DEFAULT-APPENDER"/><appender-refref="ERROR-APPENDER"/></root></configuration>3.测试方法
=LoggerFactory.getLogger(SelectBillingOrderImplTest.class);@TestpublicvoidlogTest()throwsIOException{logger.info("");logger.warn("");logger.error("");}4.执行结果
1.创建Logger对象(Logger常定义为全局单例对象)
2.getLogger方法
3.拿到实力化工场,并根据日志类返回Logger对象
4.实力化日志工场
5.bind方法为slf4j的核心
6.slf4j回去classPatch下面找所有org.slf4j.impl下StaticLoggerBinder这个类注:存在引入多个Log实现的情况,所以slf4j返回的是一个Set集合但是实际情况系统加载器只加载一个StaticLoggerBinder类。所以启动时会报错,且默认使用加载到的第一个Log实现类,然后调用改类的getSingleton方法
7.以上已经成功获取了ILoggerFactory之后的逻辑是调用Factory的getLogger方法来获取Logger对象。回到了源码:3的位置
找到一种实现,我以LogBack为例: