記nodejs在pm2下使用log4js cluster模式的日誌打印丟失問題 頂 原 薦

我使用pm2的cluster集羣模式管理node服務,使用log4js打印日誌。最近公司業務量上升,與此同時問題訂單也隨之增多。但多次在排查異常訂單時找不到訂單的正常日誌,在基本排除程序本身問題後我將目光投在了日誌打印插件log4js身上。

1:這是我目前的log4js的配置

log4js.configure({
    appenders: {
        out: { type: 'stdout' },
        info: {
            type: 'dateFile',
            filename: 'info',
            pattern: '-yyyy-MM-dd.log',
            layout: {
                type: 'pattern',
                pattern: '%d{yyyy-MM-ddThh:mm:ss.SSSO} %p %c - [%m]'
            }
        },
      
    },
    categories: {
        default: { appenders: ['out','info'], level: 'all' },
    },  
    pm2: true,
    disableClustering: true
});

我在網上也找到了很多博客,看到很多人都是這麼寫的。這麼寫可以解決cluster集羣模式下只有一個進程輸出的問題。但是在翻了log4js的源碼之後我發現,如果設置了disableClustering爲true之後,每個cluster進程都會單獨向文件輸出日誌。

log4js.configure()配置的源碼如下:

當你設置了disableClustering爲true時log4js會做如上處理。然後當你getLogger時:

 

執行getLogger()函數時,會執行isMaster()方法,判斷當前進程是不是主進程。isMaster函數執行時,如果disableClustering爲true的話則認爲當前進程是主進程。則會將sendLogEventToAppender作爲打印日誌時的執行函數。真正將日誌打印到文件中。這個函數的具體執行大家可以自己去看下源碼。裏面邏輯很簡單,就不再贅述。

這樣就會發生多個進程同時向一個文件寫入的情況,在某種情況下就會出現日誌丟失情況。 我司遇到的問題就是在這種情況下發生的。而且官方文檔也有提到這種模式會發生一些莫名其妙的問題(說的應該就是日誌丟失吧)。log4js官方文檔

好。到了這裏,我們的node程序在pm2的cluster模式在使用log4js丟日誌的原因基本是確定了。就是disableClustering的鍋。那我們就要去解決它,怎麼解決?

1:將disableClustering去掉。在配置中將disableClustering去掉之後,運行4個進程後發現,只有一個進程能正常打印日誌,其它三個進程日誌全部丟失。繼續看官方文檔發現下面一段話。

在log4js的配置中設置pm2爲true時需要下載pm2-intercom參加才能保持日誌的正常打印。ok,下載pm2-intercom。使用ab發起10個併發請求,打印結果如下。

ok。看起來一起完美。你以爲結束了?當然不可能。

按照當前設置將服務發到測試環境,一跑,同樣的代碼,同樣的設置,同樣發起10個請求,只有3個打印出來了。試了好幾次都是這樣。so,還是在丟日誌。

爲什麼呢?不可能啊!是不是電腦有問題?是不是pm2版本問題?是不是Node版本問題?是不是Log4js版本問題?在各種嘗試了n次,將所有環境全部統一之後我發現,還是這樣。so,老老實實看源碼吧。

前面的一堆就不再說了,直接到關鍵代碼。log4js下打印日誌會有可能有兩條路徑。如下:

pm2下使用cluster模式時,其實所有的進程都是cluster,只有pm2主進程是master進程。但是pm2內部對進程做了標誌。例如,啓動4個cluster進程,則會有一個標誌字段(默認是NODE_APP_INSTANCE)記錄它的內部標誌, 0、1、2、3。當標誌位爲0時,log4js就認爲它是主進程(通過isPM2Master函數)。則此時master進程負責執行sendLogEventToAppender函數打印日誌,cluster進程負責執行workerDispatch將日誌發送給主進程(其實這時發送給的是pm2主進程)。

到了這裏我們可以看到的結果就是,如果是cluster進程,則日誌會發送給master進程,現在讓我們來看pm2-intercom插件。我先是在github上找了一下它的源碼發現沒找到,後來問了log4js的官方人員後得到的結果是源碼丟了!源碼丟了!丟了! 原因不知道,結果就是找不到了。。issues地址

後來找到了一份貌似是第三方的pm2-intercom源碼。pm2-intercom源碼 找到源碼看了一下。發現pm2-intercom會通過pm2的api連接到pm2,並接收到所有的消息:

上面是pm2官方的文檔pm2文檔 。pm2-intercom接收消息代碼如下:

通過打印日誌發現,所有cluster進程發送的消息都會被pm2-intercom接收。然後通過pm2-intercom轉發出去。如下:

然後我發現在執行async.forEachLimit函數往別的進程廣播時因爲設置了4,所有最後執行下來。只會想pm2 list列表中的前4個進程推送消息,所有往後的進程都接收不到。 此時! 終於找到了爲什麼我在本地調試時爲什麼能接收到消息,而到了測試環境就不行。因爲測試環境跑了很多服務。我的程序遠在4之外。

此時,問題的原因已經找到了。通過在async.forEachLimit函數裏面加上next()函數執行,發現果然所有的進程都能接收到消息。測試環境也已經可以接收到消息。

但是,這種方式有幾個弊端:

1:所有的進程都能接收到消息,所有的主進程都會打印日誌,所有可能你的日誌會在不同的程序之間串。

2:往所有的進程發送日誌,很沒有效率。

所以最好可以根據自己的需求往特定的進程發送消息。

如你有一個進程專門記錄日誌,則可以都往那一個上面發。或者每個程序的master進程記錄日誌,則可以使用packet中的name來進行區分做分發。具體的實現可以自行定義。

至此,多進程打印日誌的問題算是找到了。我也將修改代碼發佈到線上觀察後續的結果。希望能解決問題。哈哈。

謹以此篇記錄我踩的這些坑,如有錯漏歡迎指教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章