指标
在平时的开发过程中,调试代码是很重要的。对于调试,打印日志又是最常见,最疾速的形式,本文钻研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 <sys/time.h>#include <syslog.h>#include <stdarg.h>#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 <fcntl.h>#include <time.h>#include "atomicvar.h"#include <unistd.h>#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)));#elsevoid _serverLog(int level, const char *fmt, ...);#endifvoid 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: cleanclean: 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动手