空了的時候,我都會在羣裏偷偷摸摸地潛水,對小夥伴們的一舉一動、一言一行篩查診斷。一副班主任的即時感,讓我感到很是的快樂,略微夾帶一絲絲的枯燥。java
這不,我在戰國時代讀者羣裏發現了這麼一串聊天記錄:程序員
居然有小夥伴不知道「打日誌」是什麼意思,不知道該怎麼學習,還有小夥伴回答說,只知道 Log4j!數據庫
有那麼一刻,我遭受到了一萬點暴擊,心裏莫名的傷感,猶如一匹垂頭喪氣的狗。由於網絡上總有一些不懷好意的人不停地攻擊我,說我寫的文章入門,毫無深度——他們就是我命中註定的黑子,不信你到脈脈上搜「沉默王二」,就能看到他們毫無新意的抨擊。apache
我就想問一下,怎麼了,入門的文章有入門的羣體須要,而我剛好幫助了這麼一大批初學者,我應該受到褒獎好很差?安全
(說好的不在意,怎麼在意起來了呢?手動狗頭)服務器
管他呢,我行我素吧,保持初心不改就對了!這篇文章就來講說 Log4j,這個打印日誌的鼻祖。Java 中的日誌打印實際上是個藝術活,我保證,這句話毫不是忽悠。微信
事實證實,打印日誌絕逼會影響到程序的性能,這是不能否認的,畢竟多作了一項工做。尤爲是在交易很是頻繁的程序裏,涌現大量的日誌確實會比較低效。網絡
基於性能上的考量,小夥伴們頗有必要認認真真地學習一下如何優雅地打印 Java 日誌。畢竟,性能是一個程序員優不優秀的重要考量。多線程
System.out.println()
恐怕是咱們在學習 Java 的時候,最經常使用的一種打印日誌的方式了,幾乎每一個 Java 初學者都這樣幹過,甚至一些老鳥。app
之因此這樣打印日誌,是由於很方便,上手難度很低,尤爲是在 IDEA 的幫助下,只需在鍵盤上按下 so
兩個字母就能夠調出 System.out.println()
。
在本地環境下,使用 System.out.println()
打印日誌是沒問題的,能夠在控制檯看到信息。但若是是在生產環境下的話,System.out.println()
就變得毫無用處了。
控制檯打印出的信息並無保存到日誌文件中,只能即時查看,在一屏日誌的狀況下還能夠接受。若是日誌量很是大,控制檯根本就裝不下。因此就須要更高級的日誌記錄 API(好比 Log4j 和 java.util.logging)。
它們能夠把大量的日誌信息保存到文件中,而且控制每一個文件的大小,若是滿了,就存儲到下一個,方便查找。
使用 Java 日誌的時候,必定要注意日誌的級別,好比常見的 DEBUG、INFO、WARN 和 ERROR。
DEBUG 的級別最低,當須要打印調試信息的話,就用這個級別,不建議在生產環境下使用。
INFO 的級別高一些,當一些重要的信息須要打印的時候,就用這個。
WARN,用來記錄一些警告類的信息,好比說客戶端和服務端的鏈接斷開了,數據庫鏈接丟失了。
ERROR 比 WARN 的級別更高,用來記錄錯誤或者異常的信息。
FATAL,當程序出現致命錯誤的時候使用,這意味着程序可能非正常停止了。
OFF,最高級別,意味着全部消息都不會輸出了。
這個級別是基於 Log4j 的,和 java.util.logging 有所不一樣,後者提供了更多的日誌級別,好比說 SEVERE、FINER、FINEST。
爲何說錯誤的日誌記錄方式會影響程序的性能呢?由於日誌記錄的次數越多,意味着執行文件 IO 操做的次數就越多,這也就意味着會影響到程序的性能,能 get 吧?
雖說普通硬盤升級到固態硬盤後,讀寫速度快了不少,但磁盤相對於內存和 CPU 來講,仍是太慢了!就像馬車和奔馳之間的速度差距。
這也就是爲何要選擇日誌級別的重要性。對於程序來講,記錄日誌是必選項,因此能控制的就是日誌的級別,以及在這個級別上打印的日誌。
對於 DEBUG 級別的日誌來講,必定要使用下面的方式來記錄:
if(logger.isDebugEnabled()){ logger.debug("DEBUG 是開啓的"); }
當 DEBUG 級別是開啓的時候再打印日誌,這種方式在你看不少源碼的時候就能夠發現,很常見。
切記,在生產環境下,必定不要開啓 DEBUG 級別的日誌,不然程序在大量記錄日誌的時候會變很慢,還有可能在你不注意的狀況下,悄悄地把磁盤空間撐爆。
java.util.logging 屬於原生的日誌 API,Log4j 屬於第三方類庫,但我建議使用 Log4j,由於 Log4j 更好用。java.util.logging 的日誌級別比 Log4j 更多,但用不着,就變成了多餘。
Log4j 的另一個好處就是,不須要從新啓動 Java 程序就能夠調整日誌的記錄級別,很是靈活。能夠經過 log4j.properties 文件來配置 Log4j 的日誌級別、輸出環境、日誌文件的記錄方式。
Log4j 仍是線程安全的,能夠在多線程的環境下放心使用。
先來看一下 java.util.logging 的使用方式:
package com.itwanger; import java.io.IOException; import java.util.logging.FileHandler; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; /** * @author 微信搜「沉默王二」,回覆關鍵字 PDF */ public class JavaUtilLoggingDemo { public static void main(String[] args) throws IOException { Logger logger = Logger.getLogger("test"); FileHandler fileHandler = new FileHandler("javautillog.txt"); fileHandler.setFormatter(new SimpleFormatter()); logger.addHandler(fileHandler); logger.info("細小的信息"); } }
程序運行後會在 target 目錄下生成一個名叫 javautillog.txt 的文件,內容以下所示:
再來看一下 Log4j 的使用方式。
第一步,在 pom.xml 文件中引入 Log4j 包:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
第二步,在 resources 目錄下建立 log4j.properties 文件,內容以下所示:
### 設置### log4j.rootLogger = debug,stdout,D,E ### 輸出信息到控制檯 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n ### 輸出DEBUG 級別以上的日誌到=debug.log ### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = debug.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ### 輸出ERROR 級別以上的日誌到=error.log ### log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
1)配置根 Logger,語法以下所示:
log4j.rootLogger = [ level ] , appenderName, appenderName, …
level 就是日誌的優先級,從高到低依次是 ERROR、WARN、INFO、DEBUG。若是這裏定義的是 INFO,那麼低級別的 DEBUG 日誌信息將不會打印出來。
appenderName 就是指把日誌信息輸出到什麼地方,能夠指定多個地方,當前的配置文件中有 3 個地方,分別是 stdout、D、E。
2)配置日誌輸出的目的地,語法以下所示:
log4j.appender.appenderName = fully.qualified.name.of.appender.class log4j.appender.appenderName.option1 = value1 … log4j.appender.appenderName.option = valueN
Log4j 提供的目的地有下面 5 種:
3)配置日誌信息的格式,語法以下所示:
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class log4j.appender.appenderName.layout.option1 = value1 … log4j.appender.appenderName.layout.option = valueN
Log4j 提供的格式有下面 4 種:
自定義格式的參數以下所示:
method:com.itwanger.Log4jDemo.main(Log4jDemo.java:14)
第三步,寫個使用 Demo:
package com.itwanger; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; /** * @author 微信搜「沉默王二」,回覆關鍵字 PDF */ public class Log4jDemo { private static final Logger logger = LogManager.getLogger(Log4jDemo.class); public static void main(String[] args) { // 記錄debug級別的信息 logger.debug("debug."); // 記錄info級別的信息 logger.info("info."); // 記錄error級別的信息 logger.error("error."); } }
1)獲取 Logger 對象
要使用 Log4j 的話,須要先獲取到 Logger 對象,它用來負責日誌信息的打印。一般的格式以下所示:
private static final Logger logger = LogManager.getLogger(Log4jDemo.class);
2)打印日誌
有了 Logger 對象後,就能夠按照不一樣的優先級打印日誌了。常見的有如下 4 種:
Logger.debug() ; Logger.info() ; Logger.warn() ; Logger.error() ;
程序運行後會在 target 目錄下生成兩個文件,一個名叫 debug.log,內容以下所示:
2020-10-20 20:53:27 [ main:0 ] - [ DEBUG ] debug. 2020-10-20 20:53:27 [ main:3 ] - [ INFO ] info. 2020-10-20 20:53:27 [ main:3 ] - [ ERROR ] error.
另一個名叫 error.log,內容以下所示:
2020-10-20 20:53:27 [ main:3 ] - [ ERROR ] error.
1)在打印 DEBUG 級別的日誌時,切記要使用 isDebugEnabled()
!那小夥伴們確定很是好奇,爲何要這樣作呢?
先來看一下 isDebugEnabled()
方法的源碼:
public boolean isDebugEnabled() { if(repository.isDisabled( Level.DEBUG_INT)) return false; return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel()); }
內部使用了 isDisabled()
方法進行了日誌級別的判斷,若是 DEBUG 是禁用的話,就 return false 了。
再來看一下 debug()
方法的源碼:
public void debug(Object message) { if(repository.isDisabled(Level.DEBUG_INT)) return; if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { forcedLog(FQCN, Level.DEBUG, message, null); } }
咦,不是也用 isDisabled()
方法判斷嗎?難道使用 isDebugEnabled()
不是多此一舉嗎?直接用 logger.debug()
不香嗎?我來給小夥伴們解釋下。
若是咱們在打印日誌信息的時候須要附帶一個方法去獲取參數值,就像下面這樣:
logger.debug("用戶名是:" + getName());
假如 getName()
方法須要耗費的時間長達 6 秒,那完了!儘管配置文件裏的日誌級別定義的是 INFO,getName()
方法仍然會倔強地執行 6 秒,完過後再 debug()
,這就很崩了!
明明 INFO 的時候 debug()
是不執行的,意味着 getName()
也不須要執行的,恰恰就執行了 6 秒,是否是很傻?
if(logger.isDebugEnabled()) { logger.debug("用戶名是:" + getName()); }
換成上面這種方式,那肯定此時 getName()
是不執行的,對吧?
爲了程序性能上的考量,isDebugEnabled()
就變得頗有必要了!假如說 debug()
的時候沒有傳參,確實是不須要判斷 DEBUG 是否啓用的。
2)慎重選擇日誌信息的打印級別,由於這過重要了!若是隻能經過日誌查看程序發生了什麼問題,那必要的信息是必需要打印的,但打印得太多,又會影響到程序的性能。
因此,該 INFO 的 info()
,該 DEBUG 的 debug()
,不要隨便用。
3)使用 Log4j 而不是 System.out
、System.err
或者 e.printStackTrace()
來打印日誌,緣由以前講過了,就再也不贅述了。
4)使用 log4j.properties 文件來配置日誌,儘管它不是必須項,使用該文件會讓程序變得更靈活,有一種個人地盤我作主的味道。
5)不要忘記在打印日誌的時候帶上類的全名和線程名,在多線程環境下,這點尤其重要,不然定位問題的時候就太難了。
6)打印日誌信息的時候儘可能要完整,不要太過於缺省,尤爲是在遇到異常或者錯誤的時候(信息要保留兩類:案發現場信息和異常堆棧信息,若是不作處理,經過 throws 關鍵字往上拋),省得在找問題的時候都是一些無用的日誌信息。
7)要對日誌信息加以區分,把某一類的日誌信息在輸出的時候加上前綴,好比說全部數據庫級別的日誌裏添加 DB_LOG
,這樣的日誌很是大的時候能夠經過 grep
這樣的 Linux 命令快速定位。
8)不要在日誌文件中打印密碼、銀行帳號等敏感信息。
打印日誌真的是一種藝術活,搞很差會嚴重影響服務器的性能。最可怕的是,記錄了日誌,但最後發現屁用沒有,那簡直是蒼了個天啊!尤爲是在生產環境下,問題沒有記錄下來,但重現有必定的隨機性,到那時候,真的是叫每天不該,叫地地不靈啊!
嗯哼,其實我已經寫完了整個日誌系統,包括 Log4j、 SLF4J、Logback、Log4j 它弟 Log4j 2,但我以爲分開來發的話,更利於 SEO(瞧我這爲了流量的心機,手動狗頭)。若是你確實須要看完整版的話,我也貼心地爲你準備了,點擊下面的連接就能夠下載 PDF:
https://pan.baidu.com/s/1dPwsQhT5OMVapE7hGi7vww 提取碼:fxxy
碼字不易,平常求個贊吧,動一動金手指,bug 少一個(逃。