---
 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

Reply via email to