前言

Redis源码中定义了几个和日志相关的函数,用于将不同级别的信息打印到不同的位置(日志文件或标准输出,取决于配置文件的设置),这些函数的定义位于 server.h server.c 文件中,包括:

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

其中, serverLogRaw() 是 serverLog() 的底层实现,差别在于 serverLog() 进行了上层的简单封装,以支持可视化字符串的打印,而 serverLogRaw() 则只接收完整的字符串进行打印。

Redis Logging定义了四种日志等级,从低到高分别为:调试、详细、注意、警告。对应的宏定义如下:

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

serverLog()

serverLog() 函数提供了一个printf-alike的日志打印支持,能够支持格式化字符串,并接收可变参数。

 void serverLog(int level, const char *fmt, ...) {
va_list ap;
char msg[LOG_MAX_LEN]; if ((level&0xff) < server.verbosity) return; va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap); serverLogRaw(level,msg);
}

函数中对日志打印级别进行了控制,只有给定日志级别不小于服务器设置的级别时,日志才会打印出来,否则,函数提前返回。
利用了 va_list 、va_start 、va_end 等函数对可变参数进行了支持,其原理是利用函数参数在栈中的空间排布,栈空间排布和va_list 的操作如下图所示:

int vsnprintf (char * s, size_t n, const char * format, va_list arg) 函数利用可变参数列表来格式化字符串,把字符串保存在 指向的空间中。

serverLogRaw()

serverLogRaw()函数是日志打印的底层实现,主要控制了日志打印的如下几个方面:

  • 日志级别。  如果给定级别低于服务器设置的server.verbosity 级别,日志不输出,函数直接返回。
  • 日志打印位置。如果服务器配置了 server.logfile ,日志会打印到相应的日志文件中。否则,直接输出至标准输出。
  • 日志打印格式。如果在日志级别中设置了原始位,则只打印原始字符串信息。否则,会在字符串首加入进程号、日期等信息。
 void serverLogRaw(int level, const char *msg) {
// Defined in syslog.h
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*#";
FILE *fp;
char buf[];
int rawmode = (level & LL_RAW);
int log_to_stdout = server.logfile[] == '\0'; level &= 0xff; /* clear flags */
if (level < server.verbosity) return; fp = log_to_stdout ? stdout : fopen(server.logfile,"a");
if (!fp) return; // log without timestamp
if (rawmode) {
fprintf(fp,"%s",msg);
} else {
int off;
struct timeval tv;
int role_char;
pid_t pid = getpid(); gettimeofday(&tv,NULL);
struct tm tm;
/*
* 自定义的localtime函数
* 标准的 localtime 在多线程下可能出现死锁的情况
*/
nolocks_localtime(&tm,tv.tv_sec,server.timezone,server.daylight_active);
off = strftime(buf,sizeof(buf),"%d %b %Y %H:%M:%S.",&tm);
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/);
if (server.sentinel_mode) {
  role_char = 'X'; /* Sentinel. */
   } else if (pid != server.pid) {
  role_char = 'C'; /* RDB / AOF writing child. */
   } else {
  role_char = (server.masterhost ? 'S':'M'); /* Slave or Master. */
   }
  /*
  * 依次存放:
  * pid, X/C/S/M, time, .-*#, msg
  */
  fprintf(fp,"%d:%c %s %c %s\n",
  (int)getpid(),role_char, buf,c[level],msg);
}
fflush(fp); if (!log_to_stdout) fclose(fp);
if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg);
}

对于包含时间信息的日志打印会被加入如下信息在字符串首部:

  • 服务器进程号
  • X/C/S/M:  体现当前进程的状态,主进程 / 从进程 / Sentinel / RDB / AOF子进程
  • 格式化后的日期
  • 表示不同日志等级的字符
  • 信息字符串

日志打印实例:

 :C  Sep ::35.518 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
:C Sep ::35.518 # Redis version=4.0., bits=, commit=, modified=, pid=, just started
:C Sep ::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
:M Sep ::35.519 # You requested maxclients of requiring at least max file descriptors.
:M Sep ::35.519 # Server can't set maximum open files to 10032 because of OS error: Invalid argument.
:M Sep ::35.519 # Current maximum open files is . maxclients has been reduced to to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 4.0. (/) bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port:
| `-._ `._ / _.-' | PID: 2084
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-' :M Sep ::35.523 # WARNING: The TCP backlog setting of cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of .
:M Sep ::35.523 # Server initialized
:M Sep ::35.523 # WARNING overcommit_memory is set to ! 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.
:M Sep ::35.525 * DB loaded from disk: 0.002 seconds
:M Sep ::35.525 * Ready to accept connections

nolocks_localtime()

值得一提的是,在对时间进行转换时,Redis源码考虑到标准的 localtime() 在多线程下可能出现的死锁问题,所以自定义了一个不带锁的函数 nolocks_localtime() ,用于完成时间格式的转换。
关于 localtime() 函数多线程下可能带来的死锁问题,可以参考这篇博文的分析:https://juejin.im/post/6844903775539298318

参考资料:

localtime函数的死锁风险 : https://juejin.im/post/6844903775539298318

Redis源码笔记--服务器日志和函数可变参数处理server.c的更多相关文章

  1. redis源码笔记(一) —— 从redis的启动到command的分发

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...

  2. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  3. Redis 源码简洁剖析 07 - main 函数启动

    前言 问题 阶段 1:基本初始化 阶段 2:检查哨兵模式,执行 RDB 或 AOF 检测 阶段 3:运行参数解析 阶段 4:初始化 server 资源管理 初始化数据库 创建事件驱动框架 阶段 5:执 ...

  4. Redis源码笔记-初步

    目录 目录 1 1. 前言 2 2. 名词 2 3. dict.c 2 3.1. siphash算法 2 3.2. 核心函数 3 3.3. 核心宏 3 3.4. 核心结构体 3 3.4.1. dict ...

  5. redis源码笔记-内存管理zmalloc.c

    redis的内存分配主要就是对malloc和free进行了一层简单的封装.具体的实现在zmalloc.h和zmalloc.c中.本文将对redis的内存管理相关几个比较重要的函数做逐一的介绍 参考: ...

  6. redis源码笔记 - redis-cli.c

    这份代码是redis的client接口,其和server端的交互使用了deps目录下的hiredis c库,同时,在这部分代码中,应用了linenoise库完成类似history命令查询.自动补全等终 ...

  7. Redis 源码简洁剖析 09 - Reactor 模型

    Reactor 模型 事件驱动框架 Redis 如何实现 Reactor 模型 事件的数据结构:aeFileEvent 主循环:aeMain 函数 事件捕获与分发:aeProcessEvents 函数 ...

  8. 曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  9. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

随机推荐

  1. 使用部分函数时并未include其所在头文件,但是能编译成功且能运行,为什么?

    最近在看APUE,试了上面的一些例子,其中有个例子是使用getpid函数获取进程id,但是在我写demo时,并未引入其所在的头文件unistd.h,结果也能编译成功,也能运行,于是就琢磨下为啥. En ...

  2. equals&hashCode

    经典规则:如果重写了equals,必须重写hashCode 为什么??? 这个规则考虑的应用场景:如果你的对象可能放入HashMap,HashSet,不重写会出问题 问题场景: new一个对象,重写了 ...

  3. 隐藏jqgrid滚动条

    .ui-jqgrid .ui-jqgrid-bdiv{ overflow-x: hidden; } /* 隐藏jqgrid滚动条 */

  4. springboot~通过面向接口编程对控制反转IOC的理解

    IOC,把控制反转到业务端,这句话没什么问题,在springboot框架里,对象的管理是通过spring ioc来实现的,而开发人员的开发原则里总是说"面向接口编程",而为什么要面 ...

  5. pypcap rpm制作

    1.下载地址 https://pypi.org/project/pypcap/#history 2.下载后,解压并制作rpm tar -xvf pypcap-1.2.3.tar.gz python s ...

  6. Jenkins(1)—— 部署安装

    最近有聊到接口自动化,持续集成这方面,所以想从持续集成工具Jenkins作为切入点来学习一下 一.jenkins概念 Jenkins是一个开源的.可扩展的持续集成.交付.部署(软件/代码的编译.打包. ...

  7. 为什么我们需要Logstash,Fluentd等日志摄取器?

    前文传送门:Logging with ElasticSearch, Kibana, ASP.NET Core and Docker 疑问:既然应用能直接向ElasticSearch写日志,为什么我们还 ...

  8. filebeat 启动失败

    最近在ELK架构中启动filebeat时,启动失败,检查启动节点不存在 查看/var/log/message中报错日志,有如下内容 filebeat: Exiting:error loading co ...

  9. C# DataTable查询示例

    代码 public void Test() { #region 初始化数据 /* 数据 张三 语文 34.00 张三 数学 58.00 张三 英语 61.00 李四 语文 45.00 李四 数学 87 ...

  10. cmd 和powershell 用git 显示乱码

    错误: 解决: 只需在环境变量中加入 LESSCHARSET=utf-8