关于redis:Redis源码4-日志和时间

4次阅读

共计 5791 个字符,预计需要花费 15 分钟才能阅读完成。

指标

在平时的开发过程中,调试代码是很重要的。对于调试,打印日志又是最常见,最疾速的形式,本文钻研 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)));
#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 动手

码字不易,感激点赞

正文完
 0