Skip to content

Commit 3a75cc0

Browse files
committed
FEATURE: Add cmdlog optional filtering
1 parent 9e93d37 commit 3a75cc0

File tree

3 files changed

+251
-10
lines changed

3 files changed

+251
-10
lines changed

cmdlog.c

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <fcntl.h>
2626
#include <assert.h>
2727
#include <errno.h>
28+
#include <ctype.h>
2829

2930
#include "memcached/util.h"
3031

@@ -38,6 +39,10 @@
3839
#define CMDLOG_FILENAME_LENGTH CMDLOG_DIRPATH_LENGTH + 128
3940
#define CMDLOG_FILENAME_FORMAT "%s/command_%d_%d_%d_%d.log"
4041

42+
#define CMDLOG_FILTER_MAXNUM 10
43+
#define CMDLOG_FILTER_CMD_MAXLEN 15
44+
#define CMDLOG_FILTER_KEY_MAXLEN 16000
45+
4146
/* cmdlog state */
4247
#define CMDLOG_NOT_STARTED 0 /* not started */
4348
#define CMDLOG_OVERFLOW_STOP 1 /* stop by command log overflow */
@@ -59,6 +64,12 @@ struct cmd_log_buffer {
5964
uint32_t last;
6065
};
6166

67+
/* command log filter structure */
68+
struct cmd_log_filter {
69+
char command[CMDLOG_FILTER_CMD_MAXLEN + 1];
70+
char key[CMDLOG_FILTER_KEY_MAXLEN + 1];
71+
};
72+
6273
/*command log flush structure */
6374
struct cmd_log_flush {
6475
pthread_t tid; /* flush thread id */
@@ -85,6 +96,8 @@ struct cmd_log_global {
8596
struct cmd_log_buffer buffer;
8697
struct cmd_log_flush flush;
8798
struct cmd_log_stats stats;
99+
struct cmd_log_filter filters[CMDLOG_FILTER_MAXNUM];
100+
int nfilters;
88101
volatile bool reqstop;
89102
};
90103
struct cmd_log_global cmdlog;
@@ -397,25 +410,42 @@ char *cmdlog_stats(void)
397410
return str;
398411
}
399412

400-
void cmdlog_write(char *client_ip, char *command)
413+
void cmdlog_write(char *client_ip, char *command, int cmdlen)
401414
{
402415
struct tm *ptm;
403416
struct timeval val;
404417
struct cmd_log_buffer *buffer = &cmdlog.buffer;
405418
char inputstr[CMDLOG_INPUT_SIZE];
419+
char *ptr = command;
406420
int inputlen;
407421
int nwritten;
422+
int seglen;
408423

409424
gettimeofday(&val, NULL);
410425
ptm = localtime(&val.tv_sec);
411426

412-
nwritten = snprintf(inputstr, CMDLOG_INPUT_SIZE, "%02d:%02d:%02d.%06ld %s %s\n",
413-
ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (long)val.tv_usec, client_ip, command);
427+
nwritten = snprintf(inputstr, CMDLOG_INPUT_SIZE, "%02d:%02d:%02d.%06ld %s ",
428+
ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (long)val.tv_usec, client_ip);
429+
430+
while (ptr < command + cmdlen && nwritten < CMDLOG_INPUT_SIZE) {
431+
seglen = snprintf(inputstr + nwritten, CMDLOG_INPUT_SIZE - nwritten, "%s", ptr);
432+
nwritten += seglen;
433+
ptr += seglen;
434+
435+
if (ptr < command + cmdlen && nwritten < CMDLOG_INPUT_SIZE) {
436+
inputstr[nwritten++] = ' ';
437+
ptr++;
438+
}
439+
}
440+
414441
/* truncated ? */
415-
if (nwritten > CMDLOG_INPUT_SIZE) {
442+
if (nwritten >= CMDLOG_INPUT_SIZE) {
416443
inputstr[CMDLOG_INPUT_SIZE-4] = '.';
417444
inputstr[CMDLOG_INPUT_SIZE-3] = '.';
418445
inputstr[CMDLOG_INPUT_SIZE-2] = '\n';
446+
} else {
447+
inputstr[nwritten++] = '\n';
448+
inputstr[nwritten] = '\0';
419449
}
420450
inputlen = strlen(inputstr);
421451

@@ -447,3 +477,130 @@ void cmdlog_write(char *client_ip, char *command)
447477
}
448478
pthread_mutex_unlock(&buffer->lock);
449479
}
480+
481+
static int get_key_idx_by_command(const char *command)
482+
{
483+
assert(command);
484+
if (strstr(command, "mget") ||
485+
strcmp(command, "scan") == 0 ||
486+
strcmp(command, "flush") == 0 ||
487+
strcmp(command, "scrub") == 0 ||
488+
strcmp(command, "stats") == 0 ||
489+
strcmp(command, "config") == 0 ||
490+
strcmp(command, "cmdlog") == 0 ||
491+
strcmp(command, "lqdetect") == 0 ||
492+
strcmp(command, "dump") == 0 ||
493+
strcmp(command, "zkensemble") == 0 ||
494+
strcmp(command, "help") == 0 ||
495+
strcmp(command, "shutdown") == 0 ||
496+
strcmp(command, "reload") == 0) {
497+
return -1;
498+
} else if ((command[0] == 'l' ||
499+
command[0] == 's' ||
500+
command[0] == 'm' ||
501+
command[0] == 'b') &&
502+
strcmp(command + 1, "op") == 0) {
503+
return 2;
504+
}
505+
506+
return 1;
507+
}
508+
509+
bool is_cmdlog_filter_match(token_t *tokens, size_t ntokens)
510+
{
511+
assert(ntokens >= 2);
512+
char command[CMDLOG_FILTER_CMD_MAXLEN + 1] = "";
513+
char key[CMDLOG_FILTER_KEY_MAXLEN + 1] = "";
514+
int key_idx = -1;
515+
516+
if (cmdlog.nfilters == 0) {
517+
return true;
518+
}
519+
520+
if (ntokens >= 3 && strcmp(tokens[0].value + 1, "op") == 0) {
521+
snprintf(command, CMDLOG_FILTER_CMD_MAXLEN + 1, "%3s_%s", tokens[0].value, tokens[1].value);
522+
} else {
523+
snprintf(command, CMDLOG_FILTER_CMD_MAXLEN + 1, "%s", tokens[0].value);
524+
}
525+
526+
key_idx = get_key_idx_by_command(command);
527+
if (key_idx > 0) {
528+
snprintf(key, CMDLOG_FILTER_KEY_MAXLEN + 1, "%s", tokens[key_idx].value);
529+
}
530+
531+
for (int i = 0; i < cmdlog.nfilters; ++i) {
532+
if (cmdlog.filters[i].command[0] == '\0' || strcmp(command, cmdlog.filters[i].command) == 0) {
533+
if (cmdlog.filters[i].key[0] == '\0' || strcmp(key, cmdlog.filters[i].key) == 0) {
534+
return true;
535+
}
536+
}
537+
}
538+
539+
return false;
540+
}
541+
542+
int cmdlog_filter_add(const char *command, int command_len, const char *key, int key_len)
543+
{
544+
if (cmdlog.nfilters >= CMDLOG_FILTER_MAXNUM) {
545+
return 0;
546+
}
547+
548+
if (command_len > CMDLOG_FILTER_CMD_MAXLEN || key_len > CMDLOG_FILTER_KEY_MAXLEN) {
549+
return -1;
550+
}
551+
552+
if (key_len > 0) {
553+
for (int i = 0; key[i] != '\0' && i < CMDLOG_FILTER_KEY_MAXLEN; ++i) {
554+
if (!isgraph(key[i])) {
555+
return -1;
556+
}
557+
}
558+
}
559+
560+
pthread_mutex_lock(&cmdlog.lock);
561+
strcpy(cmdlog.filters[cmdlog.nfilters].command, command);
562+
strcpy(cmdlog.filters[cmdlog.nfilters].key, key);
563+
cmdlog.nfilters++;
564+
pthread_mutex_unlock(&cmdlog.lock);
565+
return 1;
566+
}
567+
568+
int cmdlog_filter_remove(int idx, bool remove_all)
569+
{
570+
int nremove = 0;
571+
572+
if (idx < 0 || idx >= cmdlog.nfilters) {
573+
return -1;
574+
}
575+
576+
pthread_mutex_lock(&cmdlog.lock);
577+
if (remove_all) {
578+
nremove += cmdlog.nfilters;
579+
cmdlog.nfilters = 0;
580+
} else {
581+
for (int i = idx; i < cmdlog.nfilters - 1; ++i) {
582+
cmdlog.filters[i] = cmdlog.filters[i + 1];
583+
}
584+
nremove++;
585+
cmdlog.nfilters--;
586+
}
587+
pthread_mutex_unlock(&cmdlog.lock);
588+
return nremove;
589+
}
590+
591+
char *cmdlog_filter_list(void)
592+
{
593+
char *buf = (char *)malloc((cmdlog.nfilters + 1) * CMDLOG_INPUT_SIZE);
594+
int nwritten = 0;
595+
596+
pthread_mutex_lock(&cmdlog.lock);
597+
if (buf) {
598+
nwritten += snprintf(buf, CMDLOG_INPUT_SIZE, "\t(%d / %d)\n", cmdlog.nfilters, CMDLOG_FILTER_MAXNUM);
599+
for (int i = 0; i < cmdlog.nfilters; ++i) {
600+
nwritten += snprintf(buf + nwritten, CMDLOG_INPUT_SIZE, "\t%d. command = %s, key = %s\n",
601+
i, cmdlog.filters[i].command, cmdlog.filters[i].key);
602+
}
603+
}
604+
pthread_mutex_unlock(&cmdlog.lock);
605+
return buf;
606+
}

cmdlog.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@ void cmdlog_final(void);
3030
int cmdlog_start(char *file_path, bool *already_started);
3131
void cmdlog_stop(bool *already_stopped);
3232
char *cmdlog_stats(void);
33-
void cmdlog_write(char *client_ip, char *command);
33+
void cmdlog_write(char *client_ip, char *command, int cmdlen);
34+
bool is_cmdlog_filter_match(token_t *tokens, size_t ntokens);
35+
int cmdlog_filter_add(const char *command, int command_len, const char *key, int key_len);
36+
int cmdlog_filter_remove(int idx, bool remove_all);
37+
char *cmdlog_filter_list(void);
3438
#endif

memcached.c

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10567,6 +10567,84 @@ static void process_scan_command(conn *c, token_t *tokens, const size_t ntokens)
1056710567
#endif
1056810568

1056910569
#ifdef COMMAND_LOGGING
10570+
static void process_cmdlog_filter(conn *c, token_t *tokens, const size_t ntokens)
10571+
{
10572+
assert(ntokens > 3);
10573+
const char *subcommand = tokens[2].value;
10574+
int ret = 0;
10575+
10576+
if (strcmp(subcommand, "add") == 0) {
10577+
char *command = "";
10578+
char *key = "";
10579+
int command_len = 0;
10580+
int key_len = 0;
10581+
10582+
if (ntokens == 6 && strcmp(tokens[3].value, "command") == 0) {
10583+
command = tokens[4].value;
10584+
command_len = tokens[4].length;
10585+
} else if (ntokens == 6 && strcmp(tokens[3].value, "key") == 0) {
10586+
key = tokens[4].value;
10587+
key_len = tokens[4].length;
10588+
} else if (ntokens == 8 && strcmp(tokens[3].value, "command") == 0 && strcmp(tokens[5].value, "key") == 0) {
10589+
command = tokens[4].value;
10590+
command_len = tokens[4].length;
10591+
key = tokens[6].value;
10592+
key_len = tokens[6].length;
10593+
} else {
10594+
out_string(c, "CLIENT_ERROR bad command line format");
10595+
return;
10596+
}
10597+
10598+
ret = cmdlog_filter_add(command, command_len, key, key_len);
10599+
if (ret > 0) {
10600+
int bufsize = command_len + key_len + 31;
10601+
char buf[bufsize];
10602+
snprintf(buf, bufsize, "\tFilter: command = %s, key = %s", command, key);
10603+
out_string(c, buf);
10604+
} else if (ret == 0) {
10605+
out_string(c, "CLIENT_ERROR filter list is full");
10606+
} else {
10607+
out_string(c, "CLIENT_ERROR invalid parameters");
10608+
}
10609+
} else if (strcmp(subcommand, "remove") == 0) {
10610+
int idx = 0;
10611+
bool remove_all = false;
10612+
10613+
if (ntokens == 4) {
10614+
remove_all = true;
10615+
} else if (ntokens == 5) {
10616+
if (!safe_strtol(tokens[3].value, &idx)) idx = -1;
10617+
} else {
10618+
out_string(c, "CLIENT_ERROR bad command line format");
10619+
return;
10620+
}
10621+
10622+
ret = cmdlog_filter_remove(idx, remove_all);
10623+
if (ret > 0) {
10624+
int bufsize = 24;
10625+
char buf[bufsize];
10626+
snprintf(buf, bufsize, "\t%d filters removed", ret);
10627+
out_string(c, buf);
10628+
} else {
10629+
out_string(c, "CLIENT_ERROR invalid parameters");
10630+
}
10631+
} else if (strcmp(subcommand, "list") == 0) {
10632+
if (ntokens != 4) {
10633+
out_string(c, "CLIENT_ERROR bad command line format");
10634+
return;
10635+
}
10636+
10637+
char *ret_str = cmdlog_filter_list();
10638+
if (ret_str) {
10639+
write_and_free(c, ret_str, strlen(ret_str));
10640+
} else {
10641+
out_string(c, "SERVER_ERROR out of memory");
10642+
}
10643+
} else {
10644+
out_string(c, "CLIENT_ERROR bad command line format");
10645+
}
10646+
}
10647+
1057010648
static void process_cmdlog_command(conn *c, token_t *tokens, const size_t ntokens)
1057110649
{
1057210650
char *type = tokens[SUBCOMMAND_TOKEN].value;
@@ -10608,8 +10686,10 @@ static void process_cmdlog_command(conn *c, token_t *tokens, const size_t ntoken
1060810686
} else {
1060910687
out_string(c, "\tcommand logging failed to get stats memory.\n");
1061010688
}
10689+
} else if (ntokens > 3 && strcmp(type, "filter") == 0) {
10690+
process_cmdlog_filter(c, tokens, ntokens);
1061110691
} else {
10612-
out_string(c, "\t* Usage: cmdlog [start [path] | stop | stats]\n");
10692+
out_string(c, "\t* Usage: cmdlog [start [path] | stop | stats | filter [add|remove|list]]\n");
1061310693
}
1061410694
}
1061510695
#endif
@@ -13861,14 +13941,14 @@ static void process_command_ascii(conn *c, char *command, int cmdlen)
1386113941
return;
1386213942
}
1386313943

13944+
ntokens = tokenize_command(command, cmdlen, tokens, MAX_TOKENS);
13945+
1386413946
#ifdef COMMAND_LOGGING
13865-
if (cmdlog_in_use) {
13866-
cmdlog_write(c->client_ip, command);
13947+
if (cmdlog_in_use && is_cmdlog_filter_match(tokens, ntokens)) {
13948+
cmdlog_write(c->client_ip, command, cmdlen);
1386713949
}
1386813950
#endif
1386913951

13870-
ntokens = tokenize_command(command, cmdlen, tokens, MAX_TOKENS);
13871-
1387213952
if ((ntokens >= 3) && (strcmp(tokens[COMMAND_TOKEN].value, "get") == 0))
1387313953
{
1387413954
process_get_command(c, tokens, ntokens, false, false);

0 commit comments

Comments
 (0)