Mailing List Archive

[PATCH 3/3] mod_log_config: Add JSON logger
---
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