❶ 大廠都是如何解決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為例: