--- modules/loggers/mod_log_config.c | 159 +++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 6 deletions(-)
diff --git a/modules/loggers/mod_log_config.c b/modules/loggers/mod_log_config.c index d142c888ad..188131ebac 100644 --- a/modules/loggers/mod_log_config.c +++ b/modules/loggers/mod_log_config.c @@ -179,7 +179,10 @@ module AP_MODULE_DECLARE_DATA log_config_module; static int xfer_flags = (APR_WRITE | APR_APPEND | APR_CREATE | APR_LARGEFILE); static apr_fileperms_t xfer_perms = APR_OS_DEFAULT; -static apr_hash_t *log_hash; + +static apr_hash_t *log_hash; // tag to log_struct +static apr_hash_t *json_hash; // tag to json attribute name + static apr_status_t ap_default_log_writer(request_rec *r, void *handle, const char **strs, @@ -194,6 +197,14 @@ static apr_status_t ap_buffered_log_writer(request_rec *r, int nelts, void *items, apr_size_t len); +static apr_status_t ap_json_log_writer(request_rec *r, + void *handle, + const char **strs, + int *strl, + int nelts, + void *items, + apr_size_t len); + static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s, const char* name); static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s, @@ -287,11 +298,11 @@ typedef struct { */ typedef struct { - char *tag; /* tag that did create this lfi */ ap_log_handler_fn_t *func; char *arg; int condition_sense; int want_orig; + char *tag; /* tag that did create this lfi */ apr_array_header_t *conditions; } log_format_item; @@ -967,6 +978,7 @@ static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa) it->want_orig = -1; it->arg = ""; /* For safety's sake... */ + it->tag = NULL; while (*s) { int i; @@ -1026,16 +1038,16 @@ static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa) } } if (!handler) { - handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1); + handler = (ap_log_handler *)apr_hash_get(log_hash, s, 1); if (!handler) { char dummy[2]; - dummy[0] = s[-1]; + dummy[0] = s[0]; dummy[1] = '\0'; return apr_pstrcat(p, "Unrecognized LogFormat directive %", dummy, NULL); } - it->tag=apr_pstrmemdup(p, s, 1); + it->tag=apr_pstrmemdup(p, s++, 1); } it->func = handler->func; if (it->want_orig == -1) { @@ -1378,6 +1390,17 @@ static const char *set_transfer_log(cmd_parms *cmd, void *dummy, return add_custom_log(cmd, dummy, fn, NULL, NULL); } +static const char *set_json_logs_on(cmd_parms *parms, void *dummy, int flag) +{ + if (flag) { + ap_log_set_writer(ap_json_log_writer); + } + else { + ap_log_set_writer(ap_default_log_writer); + } + return NULL; +} + static const char *set_buffered_logs_on(cmd_parms *parms, void *dummy, int flag) { buffered_logs = flag; @@ -1391,6 +1414,7 @@ static const char *set_buffered_logs_on(cmd_parms *parms, void *dummy, int flag) } return NULL; } + static const command_rec config_log_cmds[] = { AP_INIT_TAKE23("CustomLog", add_custom_log, NULL, RSRC_CONF, @@ -1404,6 +1428,8 @@ AP_INIT_TAKE12("LogFormat", log_format, NULL, RSRC_CONF, "a log format string (see docs) and an optional format name"), AP_INIT_FLAG("BufferedLogs", set_buffered_logs_on, NULL, RSRC_CONF, "Enable Buffered Logging (experimental)"), +AP_INIT_FLAG("JsonLogs", set_json_logs_on, NULL, RSRC_CONF, + "Enable JSON Logging (experimental)"), {NULL} }; @@ -1608,6 +1634,84 @@ static ap_log_writer *ap_log_set_writer(ap_log_writer *handle) return old; } +/* see https://www.rfc-editor.org/rfc/rfc8259#section-7 */ +static int json_needs_encoding(const char* string) +{ + for(int i = 0, n = strlen(string); i < n; i++) { + char c = string[i]; + if(c < 0x20 || c == 0x22 || c == 0x5c) { + return 1; // true + } + } + + return 0; // false +} + +static const char* json_encode(apr_pool_t *p, const char* utf8_string_to_encode) +{ + for(int i = 0, n = strlen(utf8_string_to_encode); i < n; i++) { + char c = utf8_string_to_encode[i]; + if(c < 0x20 || c == 0x22 || c == 0x5c) { + return 1; // true + } + } + + return 0; // false +} + +static apr_status_t ap_json_log_writer( request_rec *r, + void *handle, + const char **strs, + int *strl, + int nelts, + void *itms, + apr_size_t len) + +{ + log_format_item *items = (log_format_item *) itms; + apr_size_t len_file_write; + const char* attribute_name; + const char* attribute_value; + apr_size_t json_str_total_len = len * 4; + char *json_str = apr_palloc(r->pool, json_str_total_len + 1); //TODO: Why can't this fail? + + // build json + apr_cpystrn(json_str, "{", json_str_total_len); + for (int i = 0; i < nelts; ++i) { + if(items[i].tag == NULL) { + continue; + } + + attribute_name = apr_hash_get(json_hash, items[i].tag, APR_HASH_KEY_STRING ); + if(!attribute_name) { + attribute_name = items[i].tag; // use tag as attribute name as fallback + + // TODO: do we really needs to check for json string encoding for tags? + if(json_needs_encoding(attribute_name)) { + attribute_name = json_encode(r->pool, attribute_name); + } + } + // TODO: enhance attribute_name with argument from log_format_item in case of {...} + + strncat(json_str, "\"", json_str_total_len - strnlen(json_str, json_str_total_len)); + strncat(json_str, attribute_name, json_str_total_len - strnlen(json_str, json_str_total_len)); + strncat(json_str, "\":\"", json_str_total_len - strnlen(json_str, json_str_total_len)); + + attribute_value = strs[i]; + if(json_needs_encoding(attribute_value)) { + attribute_value = json_encode(r->pool, attribute_value); + } + strncat(json_str, attribute_value, json_str_total_len - strnlen(json_str, json_str_total_len)); + strncat(json_str, "\",", json_str_total_len - strnlen(json_str, json_str_total_len)); + } + // remove last ',' again + json_str[strnlen(json_str, json_str_total_len) - 1] = '\0'; + strncat(json_str, "}" APR_EOL_STR, json_str_total_len - strnlen(json_str, json_str_total_len)); + + len_file_write = strnlen(json_str, json_str_total_len); + return apr_file_write((apr_file_t*)handle, json_str, &len_file_write); +} + static apr_status_t ap_default_log_writer( request_rec *r, void *handle, const char **strs, @@ -1615,7 +1719,6 @@ static apr_status_t ap_default_log_writer( request_rec *r, int nelts, void *items, apr_size_t len) - { char *str; char *s; @@ -1733,6 +1836,15 @@ static apr_status_t ap_buffered_log_writer(request_rec *r, return rv; } +static void json_register_attribute(apr_pool_t *p, const char *tag, const char* attribute_name) +{ + if(json_needs_encoding(attribute_name)) { + attribute_name = json_encode(p, attribute_name); + } + + apr_hash_set(json_hash, tag, strlen(tag), (const void *)attribute_name); +} + static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) { static APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register; @@ -1775,6 +1887,40 @@ static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) log_pfn_register(p, "^to", log_trailer_out, 0); } + // TODO: align attribute names with https://github.com/apache/tomcat/commit/00edb6d271f6ffbe65a01acc377b1930c7354ab0 + if(1) { + json_register_attribute(p, "h", "host"); + json_register_attribute(p, "a", "remoteAddr"); + json_register_attribute(p, "A", "localAddr"); + json_register_attribute(p, "l", "logicalUserName"); + json_register_attribute(p, "u", "user"); + json_register_attribute(p, "t", "time"); + json_register_attribute(p, "f", "file"); + json_register_attribute(p, "b", "size"); + json_register_attribute(p, "B", "byteSentNC"); + json_register_attribute(p, "i", "headerIn"); + json_register_attribute(p, "o", "headerOut"); + json_register_attribute(p, "n", "note"); + json_register_attribute(p, "L", "logId"); + json_register_attribute(p, "e", "env"); + json_register_attribute(p, "V", "serverName"); + json_register_attribute(p, "v", "virtualHost"); + json_register_attribute(p, "p", "port"); + json_register_attribute(p, "P", "threadId"); + json_register_attribute(p, "H", "protocol"); + json_register_attribute(p, "m", "method"); + json_register_attribute(p, "q", "query"); + json_register_attribute(p, "X", "connectionStatus"); + json_register_attribute(p, "C", "cookie"); + json_register_attribute(p, "k", "requestsOnConnection"); + json_register_attribute(p, "r", "request"); + json_register_attribute(p, "D", "elapsedTime"); + json_register_attribute(p, "T", "elapsedTimeS"); + json_register_attribute(p, "U", "path"); + json_register_attribute(p, "s", "statusCode"); + json_register_attribute(p, "R", "handler"); + } + /* reset to default conditions */ ap_log_set_writer_init(ap_default_log_writer_init); ap_log_set_writer(ap_default_log_writer); @@ -1847,6 +1993,7 @@ static void register_hooks(apr_pool_t *p) * before calling APR_REGISTER_OPTIONAL_FN. */ log_hash = apr_hash_make(p); + json_hash = apr_hash_make(p); APR_REGISTER_OPTIONAL_FN(ap_register_log_handler); APR_REGISTER_OPTIONAL_FN(ap_log_set_writer_init); APR_REGISTER_OPTIONAL_FN(ap_log_set_writer); -- 2.20.1