1. 程式人生 > >閱讀Sofia-SIP原始碼

閱讀Sofia-SIP原始碼

su.log.h

首先看標頭檔案。最重要的莫過於su_log_t結構體了:

/** Type of log structure. */
typedef struct su_log_s su_log_t;

/** Prototype for logging function */
typedef void (su_logger_f)(void *stream, char const *fmt, va_list ap);

/** Log object. */
struct su_log_s {
  int          log_size;
  char const  *log_name;
  char const  *log_env;
  unsigned     log_default;
  unsigned     log_level;
  int          log_init;

  su_logger_f *log_logger;
  void        *log_stream;
};

結構體本身並不複雜。包括結構體物件空間大小、日誌名稱、日誌的環境變數名、日誌等級(包括預設)、日誌是否初始化標誌、日誌輸出函式和日誌流物件指標。

然後定義了一個初始化結構體變數的巨集,可以用做右值。

/** Initialize a su_log_t structure */
#define SU_LOG_INIT(name, env, level) \
  { sizeof(su_log_t), name, env, level, SU_LOG_MAX, 0, NULL, NULL, }

接著看到了一個之前未見過的用法:

SOFIAPUBFUN void su_log(char const *fmt, ...)
  __attribute__ ((__format__ (printf, 1, 2)));

這句主要作用是提示編譯器,對這個函式的呼叫需要像printf一樣,用對應的format字串來檢查可變引數的資料型別。__format__ (printf, 1, 2)告訴編譯器,fmt相當於printf的format,而可變引數是從my_printf的第2個引數開始。這樣編譯器就會在編譯時用和printf一樣的檢查法則來確認可變引數是否正確了。

標頭檔案內還定義了兩個su_log_t結構體全域性變數:
SOFIAPUBVAR su_log_t su_log_default[];
SOFIAPUBVAR su_log_t su_log_global[];

su_log.c

su_log_init

現在轉向su_log.c檔案。首先觀察su_log_init函式:

/** Initialize a log */
void su_log_init(su_log_t *log)
{
  char *env;

  if (log->log_init)
    return;

  if (explicitly_initialized == not_initialized)
    explicitly_initialized = getenv("SHOW_DEBUG_LEVELS");

  if (log != su_log_default && !su_log_default->log_init)
    su_log_init(su_log_default);

  if (log->log_env && (env = getenv(log->log_env))) {
    int level = atoi(env);

    /* Why? */
    /* if (level == 0) level = 1; */
    log->log_level = level;
    log->log_init = 2;

    if (explicitly_initialized)
      su_llog(log, 0, "%s: initialized log to level %u (%s=%s)\n",
	      log->log_name, log->log_level, log->log_env, env);
  }
  else {
    log->log_level = log->log_default;
    log->log_init = 1;
    if (explicitly_initialized) {
      if (log != su_log_default)
	su_llog(log, 0, "%s: logging at default level %u\n",
		log->log_name, su_log_default->log_level);
      else
	su_llog(log, 0, "%s: initialized log to level %u (default)\n",
		log->log_name, log->log_level);
    }
  }
}
之前看到的巨集只是簡單地初始化變數,這個函式會做更復雜的初始化操作。log_init成員被巨集初始化為0。如果su_log_t結構體變數已經初始化過了,此成員會大於1。所以首先判斷是否大於1,如果大於1立即結束。然後是處理一個全域性靜態變數,將環境變數SHOW_DEBUG_LEVELS值賦給explicitly_initialized變數。此全域性靜態變數的作用是:用來判斷是否是第一次初始化su_log_t結構體變數,如果是則用此變數輸出初始化日誌。su_log_default肯定會是此型別變數中第一個被初始化的。log_level成員值首先取決於log_env成員,初始化函式會利用log_env值找到相應的環境變數對應的值。如果log_env為空,則log_level使用log_default值(預設是SU_LOG_MAX = 9)。如果log_level值由log_env決定那麼log_init值是2,否則log_init值是1。這裡唯一的疑惑是SHOW_DEBUG_LEVELS環境變數。它是被硬編碼進程式碼內的,也就是說su模組的執行環境之一包括了SHOW_DEBUG_LEVLES環境變數。使用su模組的應用程式必須非常清楚這點。

其他函式都非常淺顯易懂並沒有什麼特殊難理解的地方。

唯一不解的地方是:兩個全域性變數陣列su_log_default和su_log_global,並沒看到哪個初始化函式或巨集為它們賦上實際的輸出資訊的函式和流物件。而且我也很想看到有個地方使用了SU_LOG_INIT巨集,可惜也沒看到。只能說這個檔案只是日誌框架。只能猜測使用SU_LOG_INIT巨集以及提供輸出日誌函式和流物件應該都是客戶程式碼的職責。

補充

在分析su_debug.h檔案時,註釋內容將我引向了iptsec_debug.c/h檔案。

su_log_t iptsec_log[] = { SU_LOG_INIT("iptsec", "IPTSEC_DEBUG", SU_DEBUG) };
SOFIA_BEGIN_DECLS

/** Common log for client and server components. */
SOFIAPUBVAR su_log_t iptsec_log[];

SOFIA_END_DECLS

#define SU_LOG (iptsec_log)
首次看到了呼叫SU_LOG_INIT巨集,此巨集用來初始化一個結構體變數。但只是初始化一些簡單屬性,輸出函式以及流物件不包括在內。這裡iptse模組使用了IPTSEC_DEBUG環境變數。上述這些程式碼印證了su_log.c/h是日誌框架程式碼。