Redis源碼筆記--服務器日誌和函數可變參數處理server.c

前言

Redis源碼中定義了幾個和日誌相關的函數,用於將不一樣級別的信息打印到不一樣的位置(日誌文件或標準輸出,取決於配置文件的設置),這些函數的定義位於 server.h server.c 文件中,包括:redis

1 void serverLog(int level, const char *fmt, ...);
2 void serverLogRaw(int level, const char *msg);
3 void serverLogFromHandler(int level, const char *msg);

其中, serverLogRaw() 是 serverLog() 的底層實現,差異在於 serverLog() 進行了上層的簡單封裝,以支持可視化字符串的打印,而 serverLogRaw() 則只接收完整的字符串進行打印。服務器

Redis Logging定義了四種日誌等級,從低到高分別爲:調試、詳細、注意、警告。對應的宏定義以下:多線程

1 /* Log levels */
2 #define LL_DEBUG 0
3 #define LL_VERBOSE 1
4 #define LL_NOTICE 2
5 #define LL_WARNING 3
6 #define LL_RAW (1<<10) /* Modifier to log without timestamp */

 

serverLog()

serverLog() 函數提供了一個printf-alike的日誌打印支持,可以支持格式化字符串,並接收可變參數。函數

 1 void serverLog(int level, const char *fmt, ...) {
 2     va_list ap;
 3     char msg[LOG_MAX_LEN];
 4 
 5     if ((level&0xff) < server.verbosity) return;
 6 
 7     va_start(ap, fmt);
 8     vsnprintf(msg, sizeof(msg), fmt, ap);
 9     va_end(ap);
10 
11     serverLogRaw(level,msg);
12 }

函數中對日誌打印級別進行了控制,只有給定日誌級別不小於服務器設置的級別時,日誌纔會打印出來,不然,函數提早返回。
利用了 va_list 、va_start 、va_end 等函數對可變參數進行了支持,其原理是利用函數參數在棧中的空間排布,棧空間排布和va_list 的操做以下圖所示:post


Variadic Macros

 int vsnprintf (char * s, size_t n, const char * format, va_list arg) 函數利用可變參數列表來格式化字符串,把字符串保存在 指向的空間中。ui

 

serverLogRaw()

serverLogRaw()函數是日誌打印的底層實現,主要控制了日誌打印的以下幾個方面:this

  • 日誌級別。  若是給定級別低於服務器設置的server.verbosity 級別,日誌不輸出,函數直接返回。
  • 日誌打印位置。若是服務器配置了 server.logfile ,日誌會打印到相應的日誌文件中。不然,直接輸出至標準輸出。
  • 日誌打印格式。若是在日誌級別中設置了原始位,則只打印原始字符串信息。不然,會在字符串首加入進程號、日期等信息。
 1 void serverLogRaw(int level, const char *msg) {
 2     // Defined in syslog.h
 3     const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
 4     const char *c = ".-*#";
 5     FILE *fp;
 6     char buf[64];
 7     int rawmode = (level & LL_RAW);
 8     int log_to_stdout = server.logfile[0] == '\0';
 9 
10     level &= 0xff; /* clear flags */
11     if (level < server.verbosity) return;
12 
13     fp = log_to_stdout ? stdout : fopen(server.logfile,"a");
14     if (!fp) return;
15 
16     // log without timestamp
17     if (rawmode) {
18         fprintf(fp,"%s",msg);
19     } else {
20         int off;
21         struct timeval tv;
22         int role_char;
23         pid_t pid = getpid();
24 
25         gettimeofday(&tv,NULL);
26         struct tm tm;
27         /*
28          * 自定義的localtime函數
29          * 標準的 localtime 在多線程下可能出現死鎖的狀況
30          */
31         nolocks_localtime(&tm,tv.tv_sec,server.timezone,server.daylight_active);
32         off = strftime(buf,sizeof(buf),"%d %b %Y %H:%M:%S.",&tm);
33         snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
34         if (server.sentinel_mode) {
35           role_char = 'X'; /* Sentinel. */
36        } else if (pid != server.pid) {
37           role_char = 'C'; /* RDB / AOF writing child. */
38        } else {
39           role_char = (server.masterhost ? 'S':'M'); /* Slave or Master. */
40        }
41       /*
42        * 依次存放:
43        * pid, X/C/S/M, time, .-*#, msg
44        */
45       fprintf(fp,"%d:%c %s %c %s\n",
46       (int)getpid(),role_char, buf,c[level],msg);
47     }
48     fflush(fp);
49 
50     if (!log_to_stdout) fclose(fp);
51     if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg);
52 }      

 

對於包含時間信息的日誌打印會被加入以下信息在字符串首部:spa

  • 服務器進程號
  • X/C/S/M:  體現當前進程的狀態,主進程 / 從進程 / Sentinel / RDB / AOF子進程
  • 格式化後的日期
  • 表示不一樣日誌等級的字符
  • 信息字符串

日誌打印實例:線程

 1 2084:C 10 Sep 16:06:35.518 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
 2 2084:C 10 Sep 16:06:35.518 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=2084, just started
 3 2084:C 10 Sep 16:06:35.518 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
 4 2084:M 10 Sep 16:06:35.519 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
 5 2084:M 10 Sep 16:06:35.519 # Server can't set maximum open files to 10032 because of OS error: Invalid argument.
 6 2084:M 10 Sep 16:06:35.519 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
 7                 _._
 8            _.-``__ ''-._
 9       _.-``    `.  `_.  ''-._           Redis 4.0.9 (00000000/0) 64 bit
10   .-`` .-```.  ```\/    _.,_ ''-._
11  (    '      ,       .-`  | `,    )     Running in standalone mode
12  |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
13  |    `-._   `._    /     _.-'    |     PID: 2084
14   `-._    `-._  `-./  _.-'    _.-'
15  |`-._`-._    `-.__.-'    _.-'_.-'|
16  |    `-._`-._        _.-'_.-'    |           http://redis.io
17   `-._    `-._`-.__.-'_.-'    _.-'
18  |`-._`-._    `-.__.-'    _.-'_.-'|
19  |    `-._`-._        _.-'_.-'    |
20   `-._    `-._`-.__.-'_.-'    _.-'
21       `-._    `-.__.-'    _.-'
22           `-._        _.-'
23               `-.__.-'
24 
25 2084:M 10 Sep 16:06:35.523 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
26 2084:M 10 Sep 16:06:35.523 # Server initialized
27 2084:M 10 Sep 16:06:35.523 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
28 2084:M 10 Sep 16:06:35.525 * DB loaded from disk: 0.002 seconds
29 2084:M 10 Sep 16:06:35.525 * Ready to accept connections

 

nolocks_localtime()

值得一提的是,在對時間進行轉換時,Redis源碼考慮到標準的 localtime() 在多線程下可能出現的死鎖問題,因此自定義了一個不帶鎖的函數 nolocks_localtime() ,用於完成時間格式的轉換。
關於 localtime() 函數多線程下可能帶來的死鎖問題,能夠參考這篇博文的分析:https://juejin.im/post/68449037755392983183d

 

參考資料:

localtime函數的死鎖風險 : https://juejin.im/post/6844903775539298318

相關文章
相關標籤/搜索