Skip to content

Commit 55bbbe0

Browse files
authored
Configurable log and timestamp formats (logfmt, ISO8601) (#1022)
Add ability to configure log output format and timestamp format in the logs. This change adds two new configs: * `log-format`: Either legacy or logfmt (See https://brandur.org/logfmt) * `log-timestamp-format`: legacy, iso8601 or milliseconds (since the eppch). Related to #1006. Example: ``` $ ./valkey-server /home/zhaoz12/git/valkey/valkey/valkey.conf pid=109463 role=RDB/AOF timestamp="2024-09-10T20:37:25.738-04:00" level=warning message="WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see jemalloc/jemalloc#1328. 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." pid=109463 role=RDB/AOF timestamp="2024-09-10T20:37:25.738-04:00" level=notice message="oO0OoO0OoO0Oo Valkey is starting oO0OoO0OoO0Oo" pid=109463 role=RDB/AOF timestamp="2024-09-10T20:37:25.738-04:00" level=notice message="Valkey version=255.255.255, bits=64, commit=affbea5d, modified=1, pid=109463, just started" pid=109463 role=RDB/AOF timestamp="2024-09-10T20:37:25.738-04:00" level=notice message="Configuration loaded" pid=109463 role=master timestamp="2024-09-10T20:37:25.738-04:00" level=notice message="monotonic clock: POSIX clock_gettime" pid=109463 role=master timestamp="2024-09-10T20:37:25.739-04:00" level=warning message="Failed to write PID file: Permission denied" ``` --------- Signed-off-by: azuredream <[email protected]>
1 parent 2956367 commit 55bbbe0

File tree

3 files changed

+127
-18
lines changed

3 files changed

+127
-18
lines changed

src/config.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ configEnum propagation_error_behavior_enum[] = {
152152
{"panic-on-replicas", PROPAGATION_ERR_BEHAVIOR_PANIC_ON_REPLICAS},
153153
{NULL, 0}};
154154

155+
configEnum log_format_enum[] = {{"legacy", LOG_FORMAT_LEGACY}, {"logfmt", LOG_FORMAT_LOGFMT}, {NULL, 0}};
156+
157+
configEnum log_timestamp_format_enum[] = {{"legacy", LOG_TIMESTAMP_LEGACY},
158+
{"iso8601", LOG_TIMESTAMP_ISO8601},
159+
{"milliseconds", LOG_TIMESTAMP_MILLISECONDS},
160+
{NULL, 0}};
161+
155162
/* Output buffer limits presets. */
156163
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
157164
{0, 0, 0}, /* normal */
@@ -3190,6 +3197,8 @@ standardConfig static_configs[] = {
31903197
createEnumConfig("propagation-error-behavior", NULL, MODIFIABLE_CONFIG, propagation_error_behavior_enum, server.propagation_error_behavior, PROPAGATION_ERR_BEHAVIOR_IGNORE, NULL, NULL),
31913198
createEnumConfig("shutdown-on-sigint", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigint, 0, isValidShutdownOnSigFlags, NULL),
31923199
createEnumConfig("shutdown-on-sigterm", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigterm, 0, isValidShutdownOnSigFlags, NULL),
3200+
createEnumConfig("log-format", NULL, MODIFIABLE_CONFIG, log_format_enum, server.log_format, LOG_FORMAT_LEGACY, NULL, NULL),
3201+
createEnumConfig("log-timestamp-format", NULL, MODIFIABLE_CONFIG, log_timestamp_format_enum, server.log_timestamp_format, LOG_TIMESTAMP_LEGACY, NULL, NULL),
31933202

31943203
/* Integer configs */
31953204
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),

src/server.c

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,69 @@ const char *replstateToString(int replstate);
109109
* function of the server may be called from other threads. */
110110
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
111111

112+
/* Formats the timezone offset into a string. daylight_active indicates whether dst is active (1)
113+
* or not (0). */
114+
void formatTimezone(char *buf, size_t buflen, int timezone, int daylight_active) {
115+
serverAssert(buflen >= 7);
116+
serverAssert(timezone >= -50400 && timezone <= 43200);
117+
// Adjust the timezone for daylight saving, if active
118+
int total_offset = (-1) * timezone + 3600 * daylight_active;
119+
int hours = abs(total_offset / 3600);
120+
int minutes = abs(total_offset % 3600) / 60;
121+
buf[0] = total_offset >= 0 ? '+' : '-';
122+
buf[1] = '0' + hours / 10;
123+
buf[2] = '0' + hours % 10;
124+
buf[3] = ':';
125+
buf[4] = '0' + minutes / 10;
126+
buf[5] = '0' + minutes % 10;
127+
buf[6] = '\0';
128+
}
129+
130+
bool hasInvalidLogfmtChar(const char *msg) {
131+
if (msg == NULL) return false;
132+
133+
for (int i = 0; msg[i] != '\0'; i++) {
134+
if (msg[i] == '"' || msg[i] == '\n' || msg[i] == '\r') {
135+
return true;
136+
}
137+
}
138+
return false;
139+
}
140+
141+
/* Modifies the input string by:
142+
* replacing \r and \n with whitespace
143+
* replacing " with '
144+
*
145+
* Parameters:
146+
* safemsg - A char pointer where the modified message will be stored
147+
* safemsglen - size of safemsg
148+
* msg - The original message */
149+
void filterInvalidLogfmtChar(char *safemsg, size_t safemsglen, const char *msg) {
150+
serverAssert(safemsglen == LOG_MAX_LEN);
151+
if (msg == NULL) return;
152+
153+
size_t index = 0;
154+
while (index < safemsglen - 1 && msg[index] != '\0') {
155+
if (msg[index] == '"') {
156+
safemsg[index] = '\'';
157+
} else if (msg[index] == '\n' || msg[index] == '\r') {
158+
safemsg[index] = ' ';
159+
} else {
160+
safemsg[index] = msg[index];
161+
}
162+
index++;
163+
}
164+
safemsg[index] = '\0';
165+
}
166+
112167
/* Low level logging. To use only for very big messages, otherwise
113168
* serverLog() is to prefer. */
114169
void serverLogRaw(int level, const char *msg) {
115170
const int syslogLevelMap[] = {LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING};
116171
const char *c = ".-*#";
172+
const char *verbose_level[] = {"debug", "info", "notice", "warning"};
173+
const char *roles[] = {"sentinel", "RDB/AOF", "replica", "primary"};
174+
const char *role_chars = "XCSM";
117175
FILE *fp;
118176
char buf[64];
119177
int rawmode = (level & LL_RAW);
@@ -133,23 +191,54 @@ void serverLogRaw(int level, const char *msg) {
133191
} else {
134192
int off;
135193
struct timeval tv;
136-
int role_char;
137194
pid_t pid = getpid();
138195
int daylight_active = atomic_load_explicit(&server.daylight_active, memory_order_relaxed);
139196

140197
gettimeofday(&tv, NULL);
141198
struct tm tm;
142199
nolocks_localtime(&tm, tv.tv_sec, server.timezone, daylight_active);
143-
off = strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S.", &tm);
144-
snprintf(buf + off, sizeof(buf) - off, "%03d", (int)tv.tv_usec / 1000);
200+
switch (server.log_timestamp_format) {
201+
case LOG_TIMESTAMP_LEGACY:
202+
off = strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S.", &tm);
203+
snprintf(buf + off, sizeof(buf) - off, "%03d", (int)tv.tv_usec / 1000);
204+
break;
205+
206+
case LOG_TIMESTAMP_ISO8601:
207+
off = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.", &tm);
208+
char tzbuf[7];
209+
formatTimezone(tzbuf, sizeof(tzbuf), server.timezone, server.daylight_active);
210+
snprintf(buf + off, sizeof(buf) - off, "%03d%s", (int)tv.tv_usec / 1000, tzbuf);
211+
break;
212+
213+
case LOG_TIMESTAMP_MILLISECONDS:
214+
snprintf(buf, sizeof(buf), "%lld", (long long)tv.tv_sec * 1000 + (long long)tv.tv_usec / 1000);
215+
break;
216+
}
217+
int role_index;
145218
if (server.sentinel_mode) {
146-
role_char = 'X'; /* Sentinel. */
219+
role_index = 0; /* Sentinel. */
147220
} else if (pid != server.pid) {
148-
role_char = 'C'; /* RDB / AOF writing child. */
221+
role_index = 1; /* RDB / AOF writing child. */
149222
} else {
150-
role_char = (server.primary_host ? 'S' : 'M'); /* replica or Primary. */
223+
role_index = (server.primary_host ? 2 : 3); /* Replica or Primary. */
224+
}
225+
switch (server.log_format) {
226+
case LOG_FORMAT_LOGFMT:
227+
if (hasInvalidLogfmtChar(msg)) {
228+
char safemsg[LOG_MAX_LEN];
229+
filterInvalidLogfmtChar(safemsg, LOG_MAX_LEN, msg);
230+
fprintf(fp, "pid=%d role=%s timestamp=\"%s\" level=%s message=\"%s\"\n", (int)getpid(), roles[role_index],
231+
buf, verbose_level[level], safemsg);
232+
} else {
233+
fprintf(fp, "pid=%d role=%s timestamp=\"%s\" level=%s message=\"%s\"\n", (int)getpid(), roles[role_index],
234+
buf, verbose_level[level], msg);
235+
}
236+
break;
237+
238+
case LOG_FORMAT_LEGACY:
239+
fprintf(fp, "%d:%c %s %c %s\n", (int)getpid(), role_chars[role_index], buf, c[level], msg);
240+
break;
151241
}
152-
fprintf(fp, "%d:%c %s %c %s\n", (int)getpid(), role_char, buf, c[level], msg);
153242
}
154243
fflush(fp);
155244

src/server.h

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,15 @@ typedef enum {
567567
#define PAUSE_ACTION_EVICT (1 << 3)
568568
#define PAUSE_ACTION_REPLICA (1 << 4) /* pause replica traffic */
569569

570+
/* Sets log format */
571+
typedef enum { LOG_FORMAT_LEGACY = 0,
572+
LOG_FORMAT_LOGFMT } log_format_type;
573+
574+
/* Sets log timestamp format */
575+
typedef enum { LOG_TIMESTAMP_LEGACY = 0,
576+
LOG_TIMESTAMP_ISO8601,
577+
LOG_TIMESTAMP_MILLISECONDS } log_timestamp_type;
578+
570579
/* common sets of actions to pause/unpause */
571580
#define PAUSE_ACTIONS_CLIENT_WRITE_SET \
572581
(PAUSE_ACTION_CLIENT_WRITE | PAUSE_ACTION_EXPIRE | PAUSE_ACTION_EVICT | PAUSE_ACTION_REPLICA)
@@ -1978,17 +1987,19 @@ struct valkeyServer {
19781987
serverOpArray also_propagate; /* Additional command to propagate. */
19791988
int replication_allowed; /* Are we allowed to replicate? */
19801989
/* Logging */
1981-
char *logfile; /* Path of log file */
1982-
int syslog_enabled; /* Is syslog enabled? */
1983-
char *syslog_ident; /* Syslog ident */
1984-
int syslog_facility; /* Syslog facility */
1985-
int crashlog_enabled; /* Enable signal handler for crashlog.
1986-
* disable for clean core dumps. */
1987-
int crashed; /* True if the server has crashed, used in catClientInfoString
1988-
* to indicate that no wait for IO threads is needed. */
1989-
int memcheck_enabled; /* Enable memory check on crash. */
1990-
int use_exit_on_panic; /* Use exit() on panic and assert rather than
1991-
* abort(). useful for Valgrind. */
1990+
char *logfile; /* Path of log file */
1991+
int syslog_enabled; /* Is syslog enabled? */
1992+
char *syslog_ident; /* Syslog ident */
1993+
int syslog_facility; /* Syslog facility */
1994+
int crashlog_enabled; /* Enable signal handler for crashlog.
1995+
* disable for clean core dumps. */
1996+
int crashed; /* True if the server has crashed, used in catClientInfoString
1997+
* to indicate that no wait for IO threads is needed. */
1998+
int memcheck_enabled; /* Enable memory check on crash. */
1999+
int use_exit_on_panic; /* Use exit() on panic and assert rather than
2000+
* abort(). useful for Valgrind. */
2001+
int log_format; /* Print log in specific format */
2002+
int log_timestamp_format; /* Timestamp format in log */
19922003
/* Shutdown */
19932004
int shutdown_timeout; /* Graceful shutdown time limit in seconds. */
19942005
int shutdown_on_sigint; /* Shutdown flags configured for SIGINT. */

0 commit comments

Comments
 (0)