背景
- 最近在閱讀Postgresql 10.3源碼時發現,很多函數中打印了
elog(ERROR)
之後並沒有明顯的return,但從上下文邏輯看,此時必須返回錯誤、無法繼續執行了。難道elog(ERROR)
自帶函數返回功能?帶着這個疑問,簡單梳理了一下elog的調用流程.
elog 源碼淺析
- elog 宏定義
/*
* 如果有可變參數宏,我們將給編譯器一個暗示:當elevel>=ERROR時,函數調用不會返回
* If we have variadic macros, we can give the compiler a hint about the
* call not returning when elevel >= ERROR. See comments for ereport().
* Note that historically elog() has called elog_start (which saves errno)
* before evaluating "elevel", so we preserve that behavior here.
*/
#ifdef HAVE__BUILTIN_CONSTANT_P
#define elog(elevel, ...) \
do { \
elog_start(__FILE__, __LINE__, PG_FUNCNAME_MACRO); \
elog_finish(elevel, __VA_ARGS__); \
if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
pg_unreachable(); \
} while(0)
#else /* !HAVE__BUILTIN_CONSTANT_P */
#define elog(elevel, ...) \
do { \
int elevel_; \
elog_start(__FILE__, __LINE__, PG_FUNCNAME_MACRO); \
elevel_ = (elevel); \
elog_finish(elevel_, __VA_ARGS__); \
if (elevel_ >= ERROR) \
pg_unreachable(); \
} while(0)
簡單分析下上述宏定義:
- 首先是判斷下是否支持
__builtin_constant_p
,這是編譯器gcc內置函數,用於判斷一個值是否爲編譯時常量,如果是常數,函數返回1 ,否則返回0。如果爲常量,可以在代碼中做一些優化來減少表達式的複雜度 elog
處理日誌打印,它通常會調用write_log
寫日誌elog_finish
最後會調用errfinish(0)
,對後者的描述如下:
errfinish
結束錯誤報告循環,它會產生合適的錯誤報告並對錯誤棧執行pop操作。
如果錯誤等級爲ERROR或更嚴重級別,它將不會返回到調用方。- 首先是判斷下是否支持
再來看 errfinish
的代碼:
/*
* errfinish --- end an error-reporting cycle
*
* Produce the appropriate error report(s) and pop the error stack.
*
* If elevel is ERROR or worse, control does not return to the caller.
* See elog.h for the error level definitions.
*/
void
errfinish(int dummy,...)
ERROR
,調用PG_RE_THROW
, 類似於java、C++語言中的拋異常,結束當前調用;FATAL
,調用proc_exit(1)
,會按正常流程清理釋放資源並退出進程;PANIC
或更嚴重級別,在控制檯打印錯誤後直接abort
.
ereport
和elog
類似,不過它調用ereport_domain
實現,該函數允許通過message domain的概念,將某些指定模塊的日誌打印到一個單獨的目錄。 總結
- 要慎重評估代碼中的日誌級別,不要輕易打
elog(ERROR)
、ereport(ERROR)
或更嚴重級別的日誌 - 如果出現一些小錯誤,但當前操作仍可繼續的,可以打
WARNING
級別日誌 - 如果出現錯誤且當前流程無法繼續、但不影響其他業務請求的,可打印
ERROR
- 如出現可能導致整個服務異常的問題,則打印
FATAL
甚至PANIC