一、基本思路
日志庫的設計,抓住最核心的一條,就是日志從產生到到達最終目的地期間的處理流程。
一般而言,為了設計一個靈活可擴展,可配置的日志庫,可將日志庫抽象為4個部分:記錄器、過濾器、格式化器、輸出器四部分。
- 記錄器——負責產生日志記錄的原始信息,比如(原始信息,日志等級,時間,記錄的位置)等信息
- 過濾器——負責按指定的過濾條件過濾掉我們不需要的日志(比如按日志等級過濾)
- 格式化器——負責對原始日志信息按照我們想要的格式去格式化
- 輸出器——負責將將要進行記錄的日志(一般經過過濾器及格式化器的處理后)記錄到日志目的地(例如:輸出到文件中)
通過將日志庫分為4個抽象,使之成了一個較為靈活可擴展的日志庫。比如你想實現輸出到文件和輸出到TCP中這兩個功能,你只需分別實現這兩個輸出器的實例即可。
實現了上面的抽象就基本實現日志庫的核心功能。在具體實現上,需要設計一個Logger
,將上面的抽象組合起來。另外還有一些其他的工作需要完善,比如讀取日志配置文件,根據配置文件中的配置條件去構建相應的代碼等等其他工作。
二、一條日志的生命周期
可能到目前對日志庫是怎么工作的還有些模糊,下面以一條日志的生命周期為例來說明日志庫是怎么工作的:
- 產生。
info!("log information.");
- 經過記錄器。記錄器去獲取日志發生的時間,位置,線程信息等等信息,會有一個數據結構去存儲你需要的信息(例如:
msg:"log information.",time:2018-3-20 10:00:00,level:info,location:main.rs:3 lines
) - 經過過濾器。決定是否記錄(例如,過濾條件設為
info
級以下的過濾掉,這里條日志信息等級是info
,滿足條件,繼續。) - 經過格式化器。假設我們想輸出為
2018-3-22 10:00:00 [info] log information.
- 到輸出器。例如輸出到文件中,我們就將這條信息寫到文件上(
File::write(....);
,文件中會記錄2018-3-22 10:00:00 [info] log information.
). - 假如你還實現了日志回滾等功能的話,在日志寫入文件之后,還要判斷是否觸發日志回滾操作,如果滿足了日志回滾的條件(比如文件
Size
超過某一大小),則進行日志回滾操作。 - 這條日志的生命周期結束了。
三、偽代碼實現:
- 從配置文件中讀取配置(可通過序列化或其他方式),生成Config。
- LoggerBuilder根據Config去構造Logger。
- 由Logger實現日志庫的核心功能。
//配置
struct Config {
level:Level,
...
}
//Logger建造者
struct LoggerBuilder {
...
}
//Logger
struct Logger {
record:Recorder,
filter:Filter,
formater:Formater,
output:Output,
}
......
四、更多功能細節
上面是日志庫設計的主干,可能我們還需要更多的功能,比如日志回滾、運行時修改配置等......
對於日志回滾,可抽象為兩部分,一個是觸發回滾操作的條件,一個是何種具體的回滾操作。比如,我們希望當文件大小為1G時就觸發回滾操作,回滾的具體操作是刪除舊的內容從新開始記錄。這里需要你做的就是實現(觸發條件+具體操作)這兩個抽象。
對於運行時修改配置,實質是根據最新的配置重新生成日志實例,具體實現時可設置一個定時器,定時去讀取新的配置,依據新的配置重新生成日志實例,再用這個日志實例記錄日志......
五、后記
是不是設計日志庫的時候功能越多越好呢?個人認為不是的,功能越多,抽象越復雜,代碼越臃腫,比如日志回滾功能的實現,實現過程中會不可避免的增加了鎖等,造成性能的下降,所以,夠用就好。
各種語言對應的日志庫已經有很多了,一般都能夠滿足需要,如果不滿需要,也可以加以擴展已滿足自己的項目需要。