日誌模組是Tor系統中一個非常重要的組件。它將Tor系統中的所有事件,分成不同的嚴重層級,分成不同的系統域,進行統一的Tlog。同時它還維護著一個日誌記錄鏈表。日誌記錄鏈表記憶體儲的是所有日誌需要輸出的目標記錄檔或目標日誌輸出位置。下文中我們會詳細地對日誌模組進行分析,並簡要說明源檔案中的各函數的簡單作用。
1. 嚴重等級和域
日誌模組內定義了5個嚴重等級,其具體的設定如下:
/** Debug-level severity: for hyper-verbose messages of no interest to * anybody but developers. */#define LOG_DEBUG 7/** Info-level severity: for messages that appear frequently during normal * operation. */#define LOG_INFO 6/** Notice-level severity: for messages that appear infrequently * during normal operation; that the user will probably care about; * and that are not errors. */#define LOG_NOTICE 5/** Warn-level severity: for messages that only appear when something has gone * wrong. */#define LOG_WARN 4/** Error-level severity: for messages that only appear when something has gone * very wrong, and the Tor process can no longer proceed. */#define LOG_ERR 3
五個嚴重等級所對應的數字越小,說明他們的嚴重性越高。
日誌模組內同時定義了許多個域,用於記錄日誌時標記日誌是系統中哪個域輸出的日誌:
/* Logging domains *//** Catch-all for miscellaneous events and fatal errors. */#define LD_GENERAL (1u<<0)/** The cryptography subsystem. */#define LD_CRYPTO (1u<<1)/** Networking. */#define LD_NET (1u<<2)/** Parsing and acting on our configuration. */#define LD_CONFIG (1u<<3)/** Reading and writing from the filesystem. */#define LD_FS (1u<<4)/** Other servers' (non)compliance with the Tor protocol. */#define LD_PROTOCOL (1u<<5)/** Memory management. */#define LD_MM (1u<<6)/** HTTP implementation. */#define LD_HTTP (1u<<7)/** Application (socks) requests. */#define LD_APP (1u<<8)/** Communication via the controller protocol. */#define LD_CONTROL (1u<<9)/** Building, using, and managing circuits. */#define LD_CIRC (1u<<10)/** Hidden services. */#define LD_REND (1u<<11)/** Internal errors in this Tor process. */#define LD_BUG (1u<<12)/** Learning and using information about Tor servers. */#define LD_DIR (1u<<13)/** Learning and using information about Tor servers. */#define LD_DIRSERV (1u<<14)/** Onion routing protocol. */#define LD_OR (1u<<15)/** Generic edge-connection functionality. */#define LD_EDGE (1u<<16)#define LD_EXIT LD_EDGE/** Bandwidth accounting. */#define LD_ACCT (1u<<17)/** Router history */#define LD_HIST (1u<<18)/** OR handshaking */#define LD_HANDSHAKE (1u<<19)/** Heartbeat messages */#define LD_HEARTBEAT (1u<<20)/** Number of logging domains in the code. */#define N_LOGGING_DOMAINS 21/** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */#define LD_NOCB (1u<<31)
每個域由其所佔用的位元位來標識。
在定義了嚴重等級與域之後,日誌模組又定義了域掩碼和域掩碼數組:
/** Mask of zero or more log domains, OR'd together. */typedef uint32_t log_domain_mask_t;/** Configures which severities are logged for each logging domain for a given * log target. */typedef struct log_severity_list_t { /** For each log severity, a bitmask of which domains a given logger is * logging. */ log_domain_mask_t masks[LOG_DEBUG-LOG_ERR+1];} log_severity_list_t;
通過以上這三部分的說明,我們大致可以猜測,日誌模組的分級分域輸出的實現,就是根據嚴重層級以及域掩碼的判斷來實現的。作為一個普通的日誌模組,其大致的功能只要是能將需要輸出的日誌資訊,存放在一定的位置,方便管理員查看即可。按照這種簡單的想法,似乎日誌模組也就是如此這般便可。但是,實際上Tor系統內部的日誌模組所具有的功能還相對複雜:臨時日誌功能;多重日誌輸出功能;日誌回調功能。所以,我們還需要進一步分析。
2. 概述
Tor系統的日誌模組維護著一個日誌輸出資料行表,用於儲存所有需要輸出的日誌儲存位置。當一個日誌訊息到達,日誌模組會遍曆列表,一個一個比對其嚴重等級與域掩碼是否符合對應輸出的要求,如果符合,則輸出,若不符合則跳過。多重日誌輸出功能就是通過這樣的方式進行實現的。臨時日誌功能是Tor系統剛剛啟動之時,還未讀取設定檔中需要記錄日誌的記錄檔資訊,用於臨時輸出系統資訊的功能。預設情況下,臨時日誌會被直接輸出到終端上,也就是說其輸出位置是stdout。而在系統成功開機記錄系統,讀取日誌設定檔資訊之後,就會關閉臨時輸出日誌資訊的位置,開啟設定檔中要求記錄日誌的位置。
我們先針對以上兩個功能進行舉例說明:
系統剛剛啟動之時,日誌模組還未初始化完全,使用臨時日誌功能。日誌輸出資料行表如下:
logfile = stdout(old) --> NULL
系統成功讀取設定檔,利用設定檔資訊初始化日誌模組之後,日誌輸出資料行表如下:
logfile = stdout(new) --> file2 --> file1 --> NULL
其相對應的設定檔關於日誌管理部分的內容為:
Log debug file1
Log notice file2
Log warn stdout
也就是說,系統會通過設定檔內的配置,往日誌輸出資料行表內加入輸出位置;刪除臨時輸出位置。此處我們沒有具體說出是先添加輸出位置還是先刪除臨時輸出位置,憑猜測應該是先添加輸出位置,再刪除臨時輸出位置。具體的先後請大家自行參看源碼。
當有日誌訊息需要輸出只是,系統會遍曆以上的logfile鏈表,匹配嚴重等級與域掩碼,然後相應地輸出日誌到對應的檔案或位置。
以上談論的是日誌模組的兩個主要功能,還有一個是日誌資訊的回調功能。這個功能和Tor系統的控制模組相關,在下文當中我們簡要的描述,不會多講,等到具體介紹控制模組的時候,我們會再深入地進行說明。
3. 全域變數
日誌模組的全域變數有一些,但是其中有部分是沒有多大作用的。我們這裡只列舉出那些對日誌模組功能起著很大作用的全域變數,對他們進行部分分析:
/** A mutex to guard changes to logfiles and logging. */static tor_mutex_t log_mutex; //日誌鎖,用於互斥操作/** True iff we have initialized log_mutex */static int log_mutex_initialized = 0; //日誌鎖標誌,用於標記日誌鎖是否初始化/** Linked list of logfile_t. */static logfile_t *logfiles = NULL; //日誌輸出位置鏈表/** Boolean: do we report logging domains? */static int log_domains_are_logged = 0; //日誌輸出時是否輸出相對應的日誌域
/** Log messages waiting to be replayed onto callback-based logs */static smartlist_t *pending_cb_messages = NULL; //延遲輸出的回調訊息鏈表,用於系統控制模組
/** NULL-terminated array of names for log domains such that domain_list[dom] * is a description of <b>dom</b>. */static const char *domain_list[] = { //域列表,用於字串與域對應數字之間的轉換 "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", NULL};
通過以上的解釋,應該只對pending_cb_messages不大理解。為什麼需要有這樣的鏈表?為什麼需要掛起日誌?回調日誌又是怎麼回事?這個部分,我們留到之後分析控制模組之時回答。這裡只做簡要的說明:系統的控制模組有的時候對一些日誌訊息有濃厚的興趣,他需要日誌模組在某些日誌產生的時候提醒他。如何進行提醒呢?控制模組在日誌模組註冊一個日誌輸出位置,其實不是真正的輸出位置,他同時提供一個回呼函數。當日誌模組接收到對應的日誌之時,就會通過回呼函數通知控制模組,相應的日誌已經出現。於是控制模組就可以進行控制操作。
4.函數說明
init_logging
parse_log_level
parse_log_severity_config
set_log_severity_config
log_level_to_string
初始化日誌模組及基本工具函數;
add_sys_log
add_callback_log
add_temp_log --> add_stream_log
add_file_log --> add_stream_log
往系統輸出鏈表內添加日誌輸出位置的函數。由上述這些函數我們可以將輸出位置的類型分為3類:sys,callback,stream。其中我們最常見的就是流類型。因為該類型就指代了我們平時所常見的輸出位置:檔案,標準輸入輸出等。
flush_pending_log_callbacks
處理掛起的回調日誌的函數,分析控制模組之時我們會再次詳細講述這個部分。
其他餘下的函數其實大致都是對日誌模組的小部分操作,與工具函數基本類似,分析到此處不必在過多得分析函數功能。我們可以憑照我們平時開發系統的經驗,大致猜測出還需要什麼函數或小功能以完善整個日誌系統。最重要的函數,應該就是記錄日誌的操作函數。日誌模組根據不同的嚴重等級,提供了不同的記錄日誌函數:
log_debug
log_info
log_notice
log_warn
log_err
但是其實,他們也只是對一個核心日誌記錄函數的上層封裝,該函數就是:logv
有興趣的朋友可以自行查看logv函數的核心實現,其實現過程簡單的說,就是根據不同的輸出位置類型,執行不同的輸出操作。