python logging 日誌重複打印問題定位

圖:

圖編號按順序1-4,嫌長能夠跳過定位過程看總結函數

clipboard.png

clipboard.png

clipboard.png

clipboard.png

定位過程:

我司項目幾個服務進程的初始化log都是這樣的:
clipboard.pngspa

這些進程都會初始化一個叫 sdsomlogger,而且把handler加到了這個logger對象中,後面getlogger的時候咱們是 sdsom.xx, 這個按點分隔會致使認爲是子logger,好比是sdsom.A,就會新建一個loggersdsom.A,而後把 sdsom 這個logger設爲它的parent(圖1),打日誌的時候,會一直往上遍歷,把全部parent的全部handler都打一遍(圖2)。這些實例進程間是獨立的,但若是在一個進程裏,好比在A進程中 import 了 B 的模塊,而這個模塊 import 了B本身的log.py模塊,觸發一次 addHandler (圖3),就把 B 的 handler 加進了 A進程的 sdsom logger裏(把它設爲了parent),因此 A 的 sdsom logger裏有兩個handler (圖4),因而A 的log同時打到了B的日誌文件裏。(注意對比圖3 圖4的對象地址 是一致的)日誌

這個logger父子關係前人要這麼用的緣由,我估計是咱們項目的common這個模塊,用父子關係能夠實現這樣一個方式:不需另外初始化,log = logging.getLogger('sdsom.common') 只須要執行這一句,這個loggerparent就被設爲 <import這個common模塊的> 進程的 sdsom logger,實際上sdsom.xxx 點號後面的內容都沒有影響了,這個common logger打印時,會調parent,因而也就被相應進程的handler打印了。
自己也算方便的機制,但因爲這種方式內部實現不可見, 容易誤用。code

若是要共享日誌, 還有一種方式就是對相應的logger顯式加handler
好比要在其餘日誌裏打印zerorpc的日誌, 咱們大部分日誌初始化處都有這句: logging.getLogger('zerorpc').addHandler(handler), 給rpcLogger加上本身的handler就能夠了,因爲有了handler,那麼只要zerorpc的源碼裏是getLogger('zerorpc')的(實際源碼中通常是getLogger(__name__),在包內__name__即爲'zerorpc.xxx'),日誌就能打印到對應進程的日誌裏。
因此咱們徹底能夠不用父子關係,而是像zerorpc同樣在進程logger初始化的地方加上:
logging.getLogger('common').addHandler(handler)
而後common裏的模塊直接log = logging.getLogger('common')用便可,爲避免和三方庫重複要注意一下命名
固然還有一種方式就是本身的handler也經過函數觸發,不要在模塊全局上執行,加入一個函數手動調,只在進程初始化時調。對象

總結:

logging的父子關係是一個基礎機制,稍微看下源碼便可理解(其實主要就是圖1圖2):以點號.分隔,取最後一個點號的左邊爲前綴,以此前綴名做父,一個logger觸發記錄時,會調用全部父親的handler。在同一系統中咱們有時要用到這種機制來方便日誌打印,所以有時會不一樣進程使用同一前綴名來初始化logger。這時,不一樣進程的模塊如有相互import,容易形成一個日誌打到多個日誌文件裏。如:進程

進程A:
A.py:
logger = logging.getLogger('xxsystem.A')
logger.addHandler(logging.FileHandler('service1.log'))
進程B 兩個模塊:

B.py:
from C import func
logger = logging.getLogger('xxsystem.B')
logger.addHandler(logging.FileHandler('service2.log'))

C.py:
from A import func

這樣,就會形成B進程的log老是同時打到兩個service1.log, service2.log日誌文件裏。這裏是簡化環境,只要B的import樹裏有A模塊,就會形成一樣結果。ip

避免日誌重複的原則是:
logger名有相同前綴的狀況下,對於一個模塊兩個進程調的狀況,涉及到會被其餘進程import的模塊,不該觸發任何同名前綴的loggeraddHandler操做。 (不能import <調用了addHandler方法的> 模塊,自身也不能執行getLogger(prefix).addHandlerrpc

實際上我司使用這種機制原本也沒有什麼問題,只要注意不要隨便import,都用getLogger便可。但因爲代碼不規範仍是出現了不該有的import 日誌初始化模塊的狀況。get

要達到:源碼

  • 哪一個進程調用模塊,日誌就打在那個進程對應的日誌裏:
    a)getLogger,只要前綴相同,就會把當前進程的'prefix' logger設爲父, 因爲上面說的緣由,這個logger會且只會被打到調用它的進程中(本身的handler沒有初始化過)
    b)logger對象
  • 不管哪一個進程調用模塊,日誌都打在本身規劃所屬的進程對應日誌裏:
    不要有任何父子關係, 日誌名不要帶點。這時反過來,必須調用日誌初始化模塊觸發初始化,而不能只用getLogger, 不然是一個空logger,哪裏都不會打印。
相關文章
相關標籤/搜索