目标
在平时的开发过程中,调试代码是很重要的。对于调试,打印日志又是最常见,最快速的方式,本文研究redis中的日志模块。研究其中的使用方式。
整体分析
redis中的日志模块写在了server.c
中,没有独立成一个文件。本文以最小可运行的方式,抽离出日志模块,以及日志依赖的时间模块。
对于日志,redis封装了serverLog
, 该方法第一个参数是日志等级,剩余参数会拼接成字符串。保持了和高级语言一样的方式。可以把日志输出到stdout
, 也可以输出到指定文件。
日志中会包含时间,redis又封装了不同精度的时间。 主要关注unix时间戳。它支持原子操作。
源代码
准备工作:从redis源码中拷贝代码
cp /home/vagrant/github/server_installer/servers/redis/redis-6.2/src/localtime.c .
cp /home/vagrant/github/server_installer/servers/redis/redis-6.2/src/atomicvar.h .
新建server.c
#include "stdio.h"
#include "server.h"
#include
#include
#include
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
struct redisServer server;
/*
* Gets the proper timezone in a more portable fashion
* i.e timezone variables are linux specific.
*/
long getTimeZone(void) {
#if defined(__linux__) || defined(__sun)
return timezone;
#else
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return tz.tz_minuteswest * 60L;
#endif
}
/* We take a cached value of the unix time in the global state because with
* virtual memory and aging there is to store the current time in objects at
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL).
*
* This function should be fast because it is called at every command execution
* in call(), so it is possible to decide if to update the daylight saving
* info or not using the 'update_daylight_info' argument. Normally we update
* such info only when calling this function from serverCron() but not when
* calling it from call(). */
void updateCachedTime(int update_daylight_info) {
server.ustime = ustime();
server.mstime = server.ustime / 1000;
time_t unixtime = server.mstime / 1000;
atomicSet(server.unixtime, unixtime);
/* To get information about daylight saving time, we need to call
* localtime_r and cache the result. However calling localtime_r in this
* context is safe since we will never fork() while here, in the main
* thread. The logging function will call a thread safe version of
* localtime that has no locks. */
if (update_daylight_info) {
struct tm tm;
time_t ut = server.unixtime;
localtime_r(&ut,&tm);
server.daylight_active = tm.tm_isdst;
}
}
/* Return the UNIX time in microseconds */
long long ustime(void) {
struct timeval tv;
long long ust;
gettimeofday(&tv, NULL);
ust = ((long long)tv.tv_sec)*1000000;
ust += tv.tv_usec;
return ust;
}
void initServerConfig(void) {
updateCachedTime(1);
server.timezone = getTimeZone();
}
int main() {
tzset();
initServerConfig();
server.sentinel_mode = 0;
server.masterhost = NULL;
//server.logfile = "/tmp/server.log";
server.logfile = "";
server.verbosity = 0;
server.syslog_enabled = 1;
serverLog(LL_WARNING, " Redis is starting unixtime is: %ld", server.unixtime);
}
/* Like serverLogRaw() but with printf-alike support. This is the function that
* is used across the code. The raw version is only used in order to dump
* the INFO output on crash. */
void _serverLog(int level, const char *fmt, ...) {
va_list ap;
char msg[LOG_MAX_LEN];
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
serverLogRaw(level,msg);
}
/*============================ Utility functions ============================ */
/* We use a private localtime implementation which is fork-safe. The logging
* function of Redis may be called from other threads. */
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
/* Low level logging. To use only for very big messages, otherwise
* serverLog() is to prefer. */
void serverLogRaw(int level, const char *msg) {
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*#";
FILE *fp;
char buf[64];
int rawmode = (level & LL_RAW);
int log_to_stdout = server.logfile[0] == '\0';
level &= 0xff; /* clear flags */
if (level < server.verbosity) return;
fp = log_to_stdout ? stdout : fopen(server.logfile,"a");
if (!fp) return;
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;
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/1000);
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. */
}
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);
}
新建server.h
#include
#include
#include "atomicvar.h"
#include
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
typedef long long ustime_t; /* microsecond time type. */
typedef long long mstime_t; /* millisecond time type. */
long long ustime(void);
/* 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 */
/* Use macro for checking log level to avoid evaluating arguments in cases log
* should be ignored due to low level. */
#define serverLog(level, ...) do {\
if (((level)&0xff) < server.verbosity) break;\
_serverLog(level, __VA_ARGS__);\
} while(0)
#ifdef __GNUC__
void _serverLog(int level, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
void _serverLog(int level, const char *fmt, ...);
#endif
void serverLogRaw(int level, const char *msg);
struct redisServer {
pid_t pid; /* Main process pid. */
char *masterhost; /* Hostname of master */
int sentinel_mode; /* True if this instance is a Sentinel. */
char *logfile; /* Path of log file */
int syslog_enabled; /* Is syslog enabled? */
int verbosity; /* Loglevel in redis.conf */
time_t timezone; /* Cached timezone. As set by tzset(). */
int daylight_active; /* Currently in daylight saving time. */
mstime_t mstime; /* 'unixtime' in milliseconds. */
ustime_t ustime; /* 'unixtime' in microseconds. */
redisAtomic time_t unixtime; /* Unix time sampled every cron cycle. */
};
在redisServer中,因为在log中使用,所有需要加了一些看似没有用属性。这一点redis做的不是很好,业务和库耦合了。redisServer
可以理解为有状态的容器,需要全局有效,共享的,都可以加到这上面来。在函数中直接访问属性就可以了。
新建 Makefile
all: server
@echo "anet demo"
server : server.o localtime.o
$(CC) -o $@ $^
.PHONY: clean
clean:
rm -rf *.o *.d server client
输出
执行make
后生成server可执行文件, 执行server后输出
279214:C 08 Nov 2021 15:19:27.074 # Redis is starting unixtime is: 1636355967
使用到的api
- serverLog(level, ...)
文章思路
- 动手实践是最快的学习方式。用能理解的方式,轻松的学习Redis,借鉴Redis, 并应用。
- 不过分关注细节,从Api入手