刨根問底Java日志

日志對于日常開發(fā)懦尝、問題解決的重要性不言而喻蝇摸,工作中由于接觸各種類型的項(xiàng)目和框架,看到很多日志的不同用法捌袜,造成項(xiàng)目依賴和代碼的混亂说搅,故梳理總結(jié)。本文包括Java日志體系的發(fā)展歷史虏等、推薦應(yīng)用方式和部分底層原理弄唧;運(yùn)維層面的日志系統(tǒng)搭建及不同組件的對比;及常用的查詢?nèi)罩镜腖inux命令霍衫,方便Linux機(jī)器上日志的查詢候引。

@[TOC]

一. Java日志體系

Java日志體系伴隨著Java語言的發(fā)展,同時也夾雜著開發(fā)者之間敦跌、組織之間的較量背伴,一直至今。

1.1. Java日志體系發(fā)展歷史及相互關(guān)聯(lián)

1.1.1. System.out 和 System.err

Java語言自1995年向互聯(lián)網(wǎng)公開峰髓,作為一門編程語言,就如C語言的 printf("Hello, World!"); 一樣息尺,Java當(dāng)然也有內(nèi)置的輸出方式携兵,即 System.out.println()System.err.println(),前者指向標(biāo)準(zhǔn)輸出搂誉,后者指向標(biāo)準(zhǔn)錯誤輸出徐紧。在日志工具出現(xiàn)之前,大部分是用這種原始的方式打印查看日志的。

1.1.2. Log4j

Apache Log4j 是一個基于Java的日志記錄工具并级。它是由 Ceki Gülcü 首創(chuàng)的拂檩,于2001年初推出后備受歡迎,后來成為 Apache 基金會項(xiàng)目中的一員嘲碧,一度成為 Java 日志的標(biāo)桿稻励。傳言 Apache 基金會曾建議 Sun 將 Log4j 引入 Java 標(biāo)準(zhǔn)類庫,但是被拒絕了愈涩。

1.1.3. JUL

2002年2月發(fā)布的 JDK1.4 中望抽,Sun 推出了自己的日志庫,java.util.logging履婉,很多實(shí)現(xiàn)方法都是仿照 Log4j煤篙,雖然不太有風(fēng)度,但是此后打日志有了兩種方式毁腿。

1.1.4. JCL

JCL全稱 Apache Commons Logging辑奈,據(jù)說之前叫 Jakarta Commons Logging,于2002年8月由 Apache 發(fā)布已烤。不同于 Log4j 和 JUL鸠窗,JCL是一種日志門面(Logging Facade),只提供 Log API草戈,不提供實(shí)現(xiàn)塌鸯。

理想上是很優(yōu)雅的,大家記錄日志都使用 JCL 的接口唐片,運(yùn)行時可以按照自己的需求(或者喜好)來選擇使用合適的Log Implementation丙猬。如果用Log4j,就添加 Log4j 的 jar 包進(jìn)去费韭,然后寫一個 Log4j 的配置文件茧球;如果喜歡用JUL,就只需要寫個 JUL 的配置文件星持。如果有其他的新的日志庫出現(xiàn)抢埋,也只需要它提供一個Adapter,運(yùn)行的時候把這個日志庫的 jar 包加進(jìn)去督暂。這也是面向接口編程思想的體現(xiàn)揪垄,但是由于運(yùn)行時動態(tài)綁定等原因,實(shí)際使用中出現(xiàn)了性能問題及類加載等問題逻翁。


image.png

1.1.5. SLF4J

SLF4J 全稱 Simple Logging Facade for Java饥努。雖然 JCL 在設(shè)計上想法很好,但是由于其造成的一系列問題八回,Ceki Gülcü 大神表示強(qiáng)烈不滿酷愧,此時他已經(jīng)離開了 Apache驾诈,于是在2005年推出了自創(chuàng)的日志門面,即SLF4J溶浴。但是由于 Log4j 和 JUL 已經(jīng)在那里了乍迄,而且不是按照 SLF4J 的 API 實(shí)現(xiàn)的,所以存在兼容性的問題士败,SLF4J 面臨了只有 API 沒有實(shí)現(xiàn)的局面闯两。此時 Ceki Gülcü 推出了 橋接包,如果需要使用某一種日志實(shí)現(xiàn)拱烁,那么選擇相對應(yīng)的 SLF4J 的橋接包即可生蚁。比如使用 log4j 日志組件,就加入 slf4j-log4j12 橋接包戏自。

不得不說 Ceki Gülcü 野心很大邦投,為了一統(tǒng)江湖甚至推出了 slf4j-jcl 橋接包,把作為日志門面的 JCL 也視為了他可以匹配的“實(shí)現(xiàn)”擅笔。

現(xiàn)在江湖變成了這樣的:


image.png

但是還存在一個問題志衣,

1.1.6. Logback

1.2. Java日志相關(guān)部分原理

1.2.1. System.out.println()

初學(xué)時會像記公式一樣背下來這一串代碼,沒有按照J(rèn)ava方法調(diào)用的角度去理解猛们,先來刨一刨這個方法念脯。

  • System 是 java.lang 包中的一個 final 類:/classes/java/lang/System.java。根據(jù) JavaDoc弯淘,“ System 類提供的包括:標(biāo)準(zhǔn)輸入绿店,標(biāo)準(zhǔn)輸出和錯誤輸出流;訪問外部定義的屬性和環(huán)境變量庐橙;一種加載文件和庫的方法假勿;以及用于快速復(fù)制陣列的一部分的實(shí)用方法√睿”
  • out 是 System 類的靜態(tài)成員字段转培,類型為 PrintStream。它在啟動時就會被實(shí)例化浆竭,并與主機(jī)的標(biāo)準(zhǔn)輸出控制臺進(jìn)行映射浸须。該流在實(shí)例化之后立即打開,并準(zhǔn)備接受數(shù)據(jù)邦泄。其在 System 類中的定義語句如下:
/**
 * The "standard" output stream. This stream is already
 * open and ready to accept output data. Typically this stream
 * corresponds to display output or another output destination
 * specified by the host environment or user.
 */
public final static PrintStream out = null;
  • println 是 PrintStream 類的一個方法删窒。println 打印(參數(shù)內(nèi)容 + 換行符)到控制臺顺囊。PrintStream 類中有多個重載的 println 方法易稠。每個 println 是通過調(diào)用 print 方法并添加一個換行符實(shí)現(xiàn)的。print 方法是通過調(diào)用流的 write 方法實(shí)現(xiàn)的包蓝。

了解了 System.out.println() 的表層原理驶社,System.err.println() 和其幾乎相同,err 也是System 類的一個 PrintStream 類型成員测萎。

但是不解的問題仍然存在亡电。

  • System.out 這個變量被 final 修飾,初始化為 null硅瞧,為何執(zhí)行 println() 方法不報空指針異常份乒?
  • System.out 是如何被指向標(biāo)準(zhǔn)輸出(默認(rèn)控制臺)的;
  • out 變量被聲明為 final腕唧,是否意味著 System.out.println() 方法只能向控制臺打踊蛳健;

為了解決這些疑惑枣接,深入理解 System.out.println() 調(diào)用的原理:

  1. System 類在 vm 啟動后優(yōu)先初始化颂暇,比 out 對象的類型 PrintStream 靠前,而 out 對象是 static final 修飾的但惶,定義的時候必須初始化耳鸯,故在定義時初始化為 null ;

  2. 想修改已經(jīng)被賦值的 final 變量膀曾,可用的一種方法是在沒有內(nèi)聯(lián)優(yōu)化的情況下利用反射將 final 修飾符去掉再設(shè)置新值县爬,但是這對 System 類不可行。故 Java 采用更底層的方法繞過 final 限制添谊;

  3. 絕大部分博客财喳,包括所有筆者瀏覽過的中文博客中都陳述:System 類有唯一一個 static 代碼段執(zhí)行的方法:registerNatives(),源碼見 /native/java/lang/System.c#l47斩狱。此方法中調(diào)用了該類的 initializeSystemClass() 方法做一些初始化工作耳高,依據(jù)是 Java 源碼這個方法上的注釋內(nèi)容,如下圖喊废。但是此解釋是完全錯誤的祝高。

    image.png

    筆者翻看了和 registerNatives() 方法有關(guān)的所有 hotspot 源碼,沒有找到 initializeSystemClass() 方法的調(diào)用邏輯污筷。registerNatives() 方法是一個常見于各種類中的工闺,用于初始化該類的 native 方法的方法。如在 System 類中瓣蛀,有三個 native 方法需要初始化陆蟆,如下圖。
    image.png

    這里的 *env 為 JNI 環(huán)境惋增, 方法進(jìn)入 jni_RegisterNatives():/vm/prims/jni.cpp#l4050叠殷,代碼邏輯就是遍歷了上圖中的 methods 數(shù)組,注冊了 native 方法诈皿。和我們所關(guān)心的 out 對象一點(diǎn)關(guān)系都沒有林束,不多說像棘。

  4. 實(shí)際上 initializeSystemClass() 方法是在 /vm/runtime/thread.cpp#l3529 中被調(diào)用的,在初始化 System 類的同時也初始化了很多其他基礎(chǔ)類壶冒,如 Class缕题、Method、Finalizer 等胖腾,只不過初始化 System 類用了一個專門的方法烟零。

    image.png

    此方法內(nèi)邏輯是,先解析出 System 類元數(shù)據(jù) Klass咸作,再用 call_static 調(diào)用已被編譯的 initializeSystemClass() 方法锨阿。整體在 thread 初始化后進(jìn)行。

  5. 破案了记罚∈睿回到 Java 中的 initializeSystemClass() 方法,該方法對 in毫胜、out书斜、err 三個流進(jìn)行了初始化:


    image.png

    其中,F(xiàn)ileDescriptor 是文件描述符酵使,對于每個打開的文件荐吉,操作系統(tǒng)都會分配一個文件描述符,0口渔、1样屠、2 文件描述符分別預(yù)留給標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出缺脉、錯誤輸出痪欲,System.out.println() 最終是在標(biāo)準(zhǔn)輸出文件描述符上執(zhí)行 write() 操作,in 和 err 同理攻礼,故在初始化的時候傳入了參數(shù)0业踢、1、2:

public static final FileDescriptor in = new FileDescriptor(0);
public static final FileDescriptor out = new FileDescriptor(1);
public static final FileDescriptor err = new FileDescriptor(2);
  1. 注意礁扮,這里 setOut0() 方法也是 native 方法知举,實(shí)現(xiàn)在 /native/java/lang/System.c#l464。首先 (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;") 獲取 System.java 的靜態(tài)成員 out 的 jfieldID太伊,(*env)->SetStaticObjectField(env,cla,fid,stream) 設(shè)置 fid(即 out 的 jfieldID)對應(yīng)的靜態(tài)成員的值為傳入的 stream雇锡。源碼在注釋中也大方說明了這么做的原因:

    image.png

  2. System 類中提供了 setOut() 方法用于修改標(biāo)準(zhǔn)輸出,setOut() 方法也是調(diào)用了 setOut0() 才得以實(shí)現(xiàn)僚焦,在此之前還會先檢查是否有 setIO 的權(quán)限:


    image.png

    故只要在 Java 代碼中調(diào)用 setOut() 方法即可修改 System.out.println() 的輸出目標(biāo)锰提,舉例如下:


    image.png
  3. 至此知道了 out 對象的來龍去脈。關(guān)于那個大家都誤解的注釋,個人認(rèn)為那個注釋應(yīng)該是加在 System 類上的立肘,但被加在了方法上边坤,不仔細(xì)研究很容易跑偏。再后來谅年,Java 改變了 System 類初始化的邏輯惩嘉,變?yōu)榱巳齻€步驟,方法分別取名為 initPhase1踢故、initPhase2initPhase3惹苗,負(fù)責(zé)不同的功能殿较,out 的初始化在 call_initPhase1() 方法中,詳見 /vm/runtime/thread.cpp#l3376桩蓉。但是 Java 的開發(fā)者依舊忘記了更新那段令人迷惑的注釋淋纲,后來在被提 issue 后進(jìn)行了 update,可以借此看看 Java 的 issue 流程院究,也是趣事:https://bugs.openjdk.java.net/browse/JDK-8232617洽瞬。

  4. 如果想給控制臺輸出自定義一些格式,也不是不可以业汰,如下圖伙窃。但是這個格式的設(shè)定和 Java 沒什么關(guān)系,參考:ANSI轉(zhuǎn)義序列

    image.png

1.3. 常見框架中的日志應(yīng)用(以springboot和mybatis為例)

二. Java日志推薦方式

三. 日志收集處理分析平臺

四. 常用于日志查詢的Linux命令

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末样漆,一起剝皮案震驚了整個濱河市为障,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌放祟,老刑警劉巖鳍怨,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跪妥,居然都是意外死亡鞋喇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門眉撵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侦香,“玉大人,你說我怎么就攤上這事执桌”苫剩” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵仰挣,是天一觀的道長伴逸。 經(jīng)常有香客問我,道長膘壶,這世上最難降的妖魔是什么错蝴? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任洲愤,我火速辦了婚禮,結(jié)果婚禮上顷锰,老公的妹妹穿的比我還像新娘柬赐。我一直安慰自己,他們只是感情好官紫,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布肛宋。 她就那樣靜靜地躺著,像睡著了一般束世。 火紅的嫁衣襯著肌膚如雪酝陈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天毁涉,我揣著相機(jī)與錄音沉帮,去河邊找鬼。 笑死贫堰,一個胖子當(dāng)著我的面吹牛穆壕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播其屏,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼喇勋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漫玄?” 一聲冷哼從身側(cè)響起茄蚯,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎睦优,沒想到半個月后渗常,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汗盘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年皱碘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隐孽。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡癌椿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出菱阵,到底是詐尸還是另有隱情踢俄,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布晴及,位于F島的核電站都办,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜琳钉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一势木、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歌懒,春花似錦啦桌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至验烧,卻和暖如春查剖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背噪窘。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留效扫,地道東北人倔监。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像菌仁,于是被迫代替她去往敵國和親浩习。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 一济丘、日志簡介 1.1 日志是什么(WHAT) 日志:記錄程序的運(yùn)行軌跡谱秽,方便查找關(guān)鍵信息,也方便快速定位解決問題摹迷。...
    GeekerLou閱讀 27,373評論 6 20
  • JAVA日志系統(tǒng)的演變史 我們先看一個故事疟赊。項(xiàng)目經(jīng)理A帶著一幫兄弟開發(fā)了一套復(fù)雜的企業(yè)ERP系統(tǒng),這個系統(tǒng)一連開發(fā)...
    糖寶_閱讀 649評論 0 4
  • idea 添加注釋/** 然后回車 選中代碼塊 Ctrl+Shift+/ 重點(diǎn)推薦閱讀:https://www....
    Helen_Cat閱讀 19,962評論 0 37
  • 最近準(zhǔn)備看一下各個日志框架能否以及如何實(shí)現(xiàn)多線程下寫入自定義文件峡碉。同時深入的整理并學(xué)習(xí)一下這些日志框架近哟。 一、 目...
    ZMSunrise閱讀 2,933評論 2 7
  • 看了一本書鲫寄,叫做《java工程師修煉之道》吉执,第107頁講了各種日志框架及其配置。平時我們開發(fā)地来,都是在控制臺輸出日志...
    呼嚕嚕睡閱讀 166評論 0 0