Redis 服务器日志函数解读
前言
Redis源码中定义了几个和日志相关的函数,用于将不同级别的信息打印到不同的位置(日志文件或标准输出,取决于配置文件的设置),这些函数的定义位于server.h和server.c文件中,包括:
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 的操作如下图所示:
int vsnprintf (char * s, size_t n, const char 函数利用可变参数列表来格式化字符串,把字符串保存在 s 指向的空间中。
serverLogRaw()
serverLogRaw()函数是日志打印的底层实现,主要控制了日志打印的如下几个方面:
- 日志级别。 如果给定级别低于服务器设置的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 }
对于包含时间信息的日志打印会被加入如下信息在字符串首部:
- 服务器进程号
- 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() 函数多线程下可能带来的死锁问题,可以参考这篇博文的分析:。