Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Support for multiple log destinations and syslog #30

Closed
wants to merge 2 commits into from

1 participant

Jonah H. Harris
Jonah H. Harris

This branch contains support for multiple simultaneous log destinations (file, file+stdout, stdout+syslog) and additionally includes added support for logging to stderr and syslog.

While the configuration values have changed, the default behavior (logging to stdout) has been maintained.

Jonah H. Harris

Will submit a new PR based on IRC discussion.

s-tchaikovsky s-tchaikovsky referenced this pull request
Closed

Redis crush #1221

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 210 additions and 30 deletions.
  1. +19 −4 redis.conf
  2. +126 −15 src/config.c
  3. +55 −11 src/redis.c
  4. +10 −0 src/redis.h
23 redis.conf
View
@@ -45,10 +45,25 @@ timeout 300
# warning (only very important / critical messages are logged)
loglevel verbose
-# Specify the log file name. Also 'stdout' can be used to force
-# Redis to log on the standard output. Note that if you use standard
-# output for logging but daemonize, logs will be sent to /dev/null
-logfile stdout
+# Specify the log destinations. Must be one or more of the following:
+# stdout - standard output
+# stderr - standard error
+# file - a custom log file (must also be set by logfile)
+# syslog - the system logger
+#
+# NOTE: If you use standard output or standard error for logging but
+# daemonize, logs will be sent to /dev/null
+#
+# logdest stdout
+
+# Specify the syslog identity.
+# logident redis
+
+# Specify the syslog facility. Must be between LOCAL0-LOCAL7.
+# logfacility LOCAL0
+
+# Specify the log file name.
+# logfile /var/log/redis.log
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
141 src/config.c
View
@@ -95,25 +95,98 @@ void loadServerConfig(char *filename) {
err = "Invalid log level. Must be one of debug, notice, warning";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"logdest") && argc >= 2) {
+ struct
+ {
+ const char *name;
+ const int value;
+ } validLogDestinations[] =
+ {
+ {"stdout", REDIS_LOG_STDOUT},
+ {"stderr", REDIS_LOG_STDERR},
+ {"file", REDIS_LOG_FILE},
+ {"syslog", REDIS_LOG_SYSLOG},
+ {NULL, 0}
+ };
+ int i;
+
+ /* We set a default, so clear it first. */
+ server.logdest = 0;
+
+ for (i = 1 ; i < argc ; i++)
+ {
+ int j, value = 0;
+
+ for (j = 0; validLogDestinations[j].name; j++)
+ {
+ if (!strcasecmp(validLogDestinations[j].name, argv[i]))
+ {
+ value = validLogDestinations[j].value;
+ break;
+ }
+ }
+
+ if (value)
+ server.logdest |= value;
+ else
+ {
+ err = "Invalid log destination. Must be one of stdout, file, syslog";
+ goto loaderr;
+ }
+ }
+ } else if (!strcasecmp(argv[0],"logfacility") && argc == 2) {
+ struct
+ {
+ const char *name;
+ const int value;
+ } validSyslogFacilities[] =
+ {
+ {"local0", LOG_LOCAL0},
+ {"local1", LOG_LOCAL1},
+ {"local2", LOG_LOCAL2},
+ {"local3", LOG_LOCAL3},
+ {"local4", LOG_LOCAL4},
+ {"local5", LOG_LOCAL5},
+ {"local6", LOG_LOCAL6},
+ {"local7", LOG_LOCAL7},
+ {NULL, 0}
+ };
+ int i;
+
+ for (i = 0; validSyslogFacilities[i].name; i++)
+ {
+ if (!strcasecmp(validSyslogFacilities[i].name, argv[1]))
+ {
+ server.logfacility = validSyslogFacilities[i].value;
+ break;
+ }
+ }
+
+ if (!validSyslogFacilities[i].name)
+ {
+ err = "Invalid log facility. Must be one of LOCAL0-LOCAL7";
+ goto loaderr;
+ }
+
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
FILE *logfp;
- server.logfile = zstrdup(argv[1]);
- if (!strcasecmp(server.logfile,"stdout")) {
+ /*
+ * Test if we are able to open the file. The server will not
+ * be able to abort just for this problem later...
+ */
+ if (server.logfile)
zfree(server.logfile);
- server.logfile = NULL;
- }
- if (server.logfile) {
- /* Test if we are able to open the file. The server will not
- * be able to abort just for this problem later... */
- logfp = fopen(server.logfile,"a");
- if (logfp == NULL) {
- err = sdscatprintf(sdsempty(),
- "Can't open the log file: %s", strerror(errno));
- goto loaderr;
- }
- fclose(logfp);
+ server.logfile = zstrdup(argv[1]);
+ logfp = fopen(server.logfile,"a");
+ if (logfp == NULL) {
+ err = sdscatprintf(sdsempty(),
+ "Can't open the log file: %s", strerror(errno));
+ goto loaderr;
}
+ fclose(logfp);
+ } else if (!strcasecmp(argv[0],"logident") && argc == 2) {
+ server.logident = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"databases") && argc == 2) {
server.dbnum = atoi(argv[1]);
if (server.dbnum < 1) {
@@ -153,7 +226,7 @@ void loadServerConfig(char *filename) {
server.masterport = atoi(argv[2]);
server.replstate = REDIS_REPL_CONNECT;
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
- server.masterauth = zstrdup(argv[1]);
+ server.masterauth = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
@@ -263,6 +336,44 @@ void loadServerConfig(char *filename) {
sdsfree(line);
}
if (fp != stdin) fclose(fp);
+
+ /*
+ * Logging configurations require several parameters which may not always
+ * be provided in order.
+ */
+ if (server.logdest & REDIS_LOG_SYSLOG)
+ {
+ openlog(server.logident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
+ server.logfacility);
+ }
+
+ /*
+ * This check is kinda redundant, but because we have a default value
+ * for the server logfile, we have to evaluate that the default is
+ * valid as well as whether they specified a log file destination,
+ * but somehow unset the logfile.
+ */
+ if (server.logdest & REDIS_LOG_FILE)
+ {
+ linenum = 0;
+
+ if (server.logfile)
+ {
+ FILE *logfp = fopen(server.logfile, "a");
+ if (logfp == NULL) {
+ err = sdscatprintf(sdsempty(),
+ "Can't open the log file: %s", strerror(errno));
+ goto loaderr;
+ }
+ fclose(logfp);
+ }
+ else
+ {
+ err = "FILE found in logdest, but logfile is undefined";
+ goto loaderr;
+ }
+ }
+
return;
loaderr:
66 src/redis.c
View
@@ -190,25 +190,66 @@ struct redisCommand readonlyCommandTable[] = {
void redisLog(int level, const char *fmt, ...) {
va_list ap;
FILE *fp;
+ int logdest = server.logdest;
+ int logmask = 1;
char *c = ".-*#";
char buf[64];
time_t now;
if (level < server.verbosity) return;
- fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a");
- if (!fp) return;
-
- va_start(ap, fmt);
now = time(NULL);
strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
- fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
- vfprintf(fp, fmt, ap);
- fprintf(fp,"\n");
- fflush(fp);
- va_end(ap);
+ va_start(ap, fmt);
+
+ /*
+ * Handle writing to standard out and/or a custom log file. For a flag to
+ * be set, it would have to pass validation during config, so members such
+ * as server.logfile are guaranteed to be set if REDIS_LOG_FILE is.
+ */
+ while (logdest & (REDIS_LOG_STDOUT | REDIS_LOG_STDERR | REDIS_LOG_FILE))
+ {
+ if (logmask & logdest)
+ {
+ logdest &= ~logmask;
+
+ if (logmask & REDIS_LOG_STDOUT)
+ fp = stdout;
+ else if (logmask & REDIS_LOG_STDERR)
+ fp = stderr;
+ else
+ fp = fopen(server.logfile, "a");
+
+ fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
+ vfprintf(fp, fmt, ap);
+ fprintf(fp,"\n");
+ fflush(fp);
+ va_end(ap);
+ va_start(ap, fmt);
- if (server.logfile) fclose(fp);
+ if (fp && (logmask & REDIS_LOG_FILE))
+ fclose(fp);
+
+ }
+
+ logmask <<= 1;
+ }
+
+ if (server.logdest & REDIS_LOG_SYSLOG)
+ {
+ static const int redisToSyslogLevelMapping[] =
+ {
+ [REDIS_DEBUG] = LOG_DEBUG,
+ [REDIS_VERBOSE] = LOG_DEBUG,
+ [REDIS_NOTICE] = LOG_NOTICE,
+ [REDIS_WARNING] = LOG_WARNING,
+ };
+
+ /* NOTE this table requires that */
+ vsyslog(redisToSyslogLevelMapping[level], fmt, ap);
+ }
+
+ va_end(ap);
}
/* Redis generally does not try to recover from out of memory conditions
@@ -742,7 +783,10 @@ void initServerConfig() {
server.maxidletime = REDIS_MAXIDLETIME;
server.saveparams = NULL;
server.loading = 0;
- server.logfile = NULL; /* NULL = log on standard output */
+ server.logfile = zstrdup("/var/log/redis.log");
+ server.logdest = REDIS_LOG_STDOUT;
+ server.logident = zstrdup("redis");
+ server.logfacility = LOG_LOCAL0;
server.glueoutputbuf = 1;
server.daemonize = 0;
server.appendonly = 0;
10 src/redis.h
View
@@ -17,6 +17,7 @@
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
+#include <syslog.h>
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
@@ -174,6 +175,12 @@
#define REDIS_SORT_DESC 2
#define REDIS_SORTKEY_MAX 1024
+/* Log flags */
+#define REDIS_LOG_STDOUT 0x01
+#define REDIS_LOG_STDERR 0x02
+#define REDIS_LOG_FILE 0x04
+#define REDIS_LOG_SYSLOG 0x08
+
/* Log levels */
#define REDIS_DEBUG 0
#define REDIS_VERBOSE 1
@@ -402,6 +409,9 @@ struct redisServer {
struct saveparam *saveparams;
int saveparamslen;
char *logfile;
+ int logdest;
+ char *logident;
+ int logfacility;
char *dbfilename;
char *appendfilename;
char *requirepass;
Something went wrong with that request. Please try again.