This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.2.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/9.2.x by this push:
     new 0affc17d0 Add escape json for logging (#8886)
0affc17d0 is described below

commit 0affc17d08700c176206ef3c39c60e0cf1f73db8
Author: Hiroaki Nakamura <[email protected]>
AuthorDate: Fri Jun 10 01:22:56 2022 +0900

    Add escape json for logging (#8886)
    
    * Add escape json for logging
    
    * Modify escape_json to escape DEL (0x7f) correctly
    
    * Modify escape_json for logging
    
    (Copied from 
https://github.com/apache/trafficserver/pull/8886#discussion_r892896951)
    
    Co-authored-by: Walt Karas <[email protected]>
    
    * Fix escape of doublequote in escape_json
    
    * Escape forward slash in escape_json
    
    * Removed a comment-outed line in LogAccess::unmarshal_http_text_json
    
    * Fix slicing for escape_json in LogAccess.cc
    
    * Cast char index to int to avoid char-subscripts warnings
    
    * Add AuTest for json log escape
    
    Co-authored-by: Walt Karas <[email protected]>
    Co-authored-by: Walt Karas <[email protected]>
    (cherry picked from commit d18721884615958f151eee084244c506552c6512)
---
 doc/admin-guide/logging/formatting.en.rst          |   8 +-
 proxy/logging/LogAccess.cc                         | 163 +++++++++++++++++++++
 proxy/logging/LogAccess.h                          |   2 +
 proxy/logging/LogBuffer.cc                         |   8 +-
 proxy/logging/LogBuffer.h                          |   4 +-
 proxy/logging/LogField.cc                          |   9 +-
 proxy/logging/LogField.h                           |   4 +-
 proxy/logging/LogFile.cc                           |   7 +-
 proxy/logging/LogFile.h                            |   9 +-
 proxy/logging/LogFormat.cc                         |   8 +-
 proxy/logging/LogFormat.h                          |   8 +-
 proxy/logging/LogObject.cc                         |   4 +-
 proxy/logging/YamlLogConfigDecoders.cc             |  18 ++-
 tests/gold_tests/logging/gold/field-json-test.gold |   3 +
 tests/gold_tests/logging/log-field-json.test.py    | 109 ++++++++++++++
 15 files changed, 343 insertions(+), 21 deletions(-)

diff --git a/doc/admin-guide/logging/formatting.en.rst 
b/doc/admin-guide/logging/formatting.en.rst
index 328dfb69f..4b982af3d 100644
--- a/doc/admin-guide/logging/formatting.en.rst
+++ b/doc/admin-guide/logging/formatting.en.rst
@@ -39,7 +39,8 @@ fairly simple: every format must contain a ``Format`` 
attribute, which is the
 string defining the contents of each line in the log, and may also contain an
 optional ``Interval`` attribute defining the log aggregation interval for
 any logs which use the format (see :ref:`admin-logging-type-summary` for more
-information).
+information). It may also contain an optional ``escape`` attribute defining the
+escape of log fields. Possible values for ``escape`` are ``json`` or ``none``.
 
 The return value from the ``format`` function is the log format object which
 may then be supplied to the appropriate ``log.*`` functions that define your
@@ -877,3 +878,8 @@ Some examples below ::
   '%<cqup[0:30]>' // the first 30 characters of <cqup>.
   '%<cqup[-10:]>' // the last 10 characters of <cqup>.
   '%<cqup[:-5]>'  // everything except the last 5 characters of <cqup>.
+
+Note when ``escape`` in ``format`` is set to ``json``, the start is the
+position before escaping JSON strings, and escaped values are sliced at
+the length (= end - start). If slicing cuts in the middle of escaped
+characters, the whole character is removed.
diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc
index b776986ba..f14e03c04 100644
--- a/proxy/logging/LogAccess.cc
+++ b/proxy/logging/LogAccess.cc
@@ -643,6 +643,141 @@ LogAccess::unmarshal_str(char **buf, char *dest, int len, 
LogSlice *slice)
   return -1;
 }
 
+namespace
+{
+class EscLookup
+{
+public:
+  static const char NO_ESCAPE{'\0'};
+  static const char LONG_ESCAPE{'\x01'};
+
+  static char
+  result(char c)
+  {
+    return _lu.table[static_cast<unsigned char>(c)];
+  }
+
+private:
+  struct _LUT {
+    _LUT();
+
+    char table[1 << 8];
+  };
+
+  inline static _LUT const _lu;
+};
+
+EscLookup::_LUT::_LUT()
+{
+  for (unsigned i = 0; i < ' '; ++i) {
+    table[i] = LONG_ESCAPE;
+  }
+  for (unsigned i = '\x7f'; i < sizeof(table); ++i) {
+    table[i] = LONG_ESCAPE;
+  }
+
+  // Short escapes.
+  //
+  table[static_cast<int>('\b')] = 'b';
+  table[static_cast<int>('\t')] = 't';
+  table[static_cast<int>('\n')] = 'n';
+  table[static_cast<int>('\f')] = 'f';
+  table[static_cast<int>('\r')] = 'r';
+  table[static_cast<int>('\\')] = '\\';
+  table[static_cast<int>('\"')] = '"';
+  table[static_cast<int>('/')]  = '/';
+}
+
+char
+nibble(int nib)
+{
+  return nib >= 0xa ? 'a' + (nib - 0xa) : '0' + nib;
+}
+
+} // end anonymous namespace
+
+static int
+escape_json(char *dest, const char *buf, int len)
+{
+  int escaped_len = 0;
+
+  for (int i = 0; i < len; i++) {
+    char c  = buf[i];
+    char ec = EscLookup::result(c);
+    if (__builtin_expect(EscLookup::NO_ESCAPE == ec, 1)) {
+      if (dest) {
+        if (escaped_len + 1 > len) {
+          break;
+        }
+        *dest++ = c;
+      }
+      escaped_len++;
+
+    } else if (EscLookup::LONG_ESCAPE == ec) {
+      if (dest) {
+        if (escaped_len + 6 > len) {
+          break;
+        }
+        *dest++ = '\\';
+        *dest++ = 'u';
+        *dest++ = '0';
+        *dest++ = '0';
+        *dest++ = nibble(static_cast<unsigned char>(c) >> 4);
+        *dest++ = nibble(c & 0x0f);
+      }
+      escaped_len += 6;
+
+    } else { // Short escape.
+      if (dest) {
+        if (escaped_len + 2 > len) {
+          break;
+        }
+        *dest++ = '\\';
+        *dest++ = ec;
+      }
+      escaped_len += 2;
+    }
+  } // end for
+  return escaped_len;
+}
+
+int
+LogAccess::unmarshal_str_json(char **buf, char *dest, int len, LogSlice *slice)
+{
+  Debug("log-escape", "unmarshal_str_json start, len=%d, slice=%p", len, 
slice);
+  ink_assert(buf != nullptr);
+  ink_assert(*buf != nullptr);
+  ink_assert(dest != nullptr);
+
+  char *val_buf   = *buf;
+  int val_len     = static_cast<int>(::strlen(val_buf));
+  int escaped_len = escape_json(nullptr, val_buf, val_len);
+
+  *buf += LogAccess::strlen(val_buf); // this is how it was stored
+
+  if (slice && slice->m_enable) {
+    int offset, n;
+
+    n = slice->toStrOffset(escaped_len, &offset);
+    Debug("log-escape", "unmarshal_str_json start, n=%d, offset=%d", n, 
offset);
+    if (n <= 0) {
+      return 0;
+    }
+
+    if (n >= len) {
+      return -1;
+    }
+
+    return escape_json(dest, (val_buf + offset), n);
+  }
+
+  if (escaped_len < len) {
+    escape_json(dest, val_buf, escaped_len);
+    return escaped_len;
+  }
+  return -1;
+}
+
 int
 LogAccess::unmarshal_ttmsf(char **buf, char *dest, int len)
 {
@@ -813,6 +948,34 @@ LogAccess::unmarshal_http_text(char **buf, char *dest, int 
len, LogSlice *slice)
   return res1 + res2 + res3 + 2;
 }
 
+int
+LogAccess::unmarshal_http_text_json(char **buf, char *dest, int len, LogSlice 
*slice)
+{
+  ink_assert(buf != nullptr);
+  ink_assert(*buf != nullptr);
+  ink_assert(dest != nullptr);
+
+  char *p = dest;
+
+  int res1 = unmarshal_str_json(buf, p, len);
+  if (res1 < 0) {
+    return -1;
+  }
+  p += res1;
+  *p++     = ' ';
+  int res2 = unmarshal_str_json(buf, p, len - res1 - 1, slice);
+  if (res2 < 0) {
+    return -1;
+  }
+  p += res2;
+  *p++     = ' ';
+  int res3 = unmarshal_http_version(buf, p, len - res1 - res2 - 2);
+  if (res3 < 0) {
+    return -1;
+  }
+  return res1 + res2 + res3 + 2;
+}
+
 /*-------------------------------------------------------------------------
   LogAccess::unmarshal_http_status
 
diff --git a/proxy/logging/LogAccess.h b/proxy/logging/LogAccess.h
index 5dba8d808..2f2956d6d 100644
--- a/proxy/logging/LogAccess.h
+++ b/proxy/logging/LogAccess.h
@@ -304,12 +304,14 @@ public:
   static int unmarshal_int_to_str(char **buf, char *dest, int len);
   static int unmarshal_int_to_str_hex(char **buf, char *dest, int len);
   static int unmarshal_str(char **buf, char *dest, int len, LogSlice *slice = 
nullptr);
+  static int unmarshal_str_json(char **buf, char *dest, int len, LogSlice 
*slice = nullptr);
   static int unmarshal_ttmsf(char **buf, char *dest, int len);
   static int unmarshal_int_to_date_str(char **buf, char *dest, int len);
   static int unmarshal_int_to_time_str(char **buf, char *dest, int len);
   static int unmarshal_int_to_netscape_str(char **buf, char *dest, int len);
   static int unmarshal_http_version(char **buf, char *dest, int len);
   static int unmarshal_http_text(char **buf, char *dest, int len, LogSlice 
*slice = nullptr);
+  static int unmarshal_http_text_json(char **buf, char *dest, int len, 
LogSlice *slice = nullptr);
   static int unmarshal_http_status(char **buf, char *dest, int len);
   static int unmarshal_ip(char **buf, IpEndpoint *dest);
   static int unmarshal_ip_to_str(char **buf, char *dest, int len);
diff --git a/proxy/logging/LogBuffer.cc b/proxy/logging/LogBuffer.cc
index 8fdfb8c09..0b091e3d5 100644
--- a/proxy/logging/LogBuffer.cc
+++ b/proxy/logging/LogBuffer.cc
@@ -445,7 +445,7 @@ LogBuffer::max_entry_bytes()
 int
 LogBuffer::resolve_custom_entry(LogFieldList *fieldlist, char *printf_str, 
char *read_from, char *write_to, int write_to_len,
                                 long timestamp, long timestamp_usec, unsigned 
buffer_version, LogFieldList *alt_fieldlist,
-                                char *alt_printf_str)
+                                char *alt_printf_str, LogEscapeType 
escape_type)
 {
   if (fieldlist == nullptr || printf_str == nullptr) {
     return 0;
@@ -501,7 +501,7 @@ LogBuffer::resolve_custom_entry(LogFieldList *fieldlist, 
char *printf_str, char
       ++markCount;
       if (field != nullptr) {
         char *to = &write_to[bytes_written];
-        res      = field->unmarshal(&read_from, to, write_to_len - 
bytes_written);
+        res      = field->unmarshal(&read_from, to, write_to_len - 
bytes_written, escape_type);
 
         if (res < 0) {
           SiteThrottledNote("%s", buffer_size_exceeded_msg);
@@ -549,7 +549,7 @@ LogBuffer::resolve_custom_entry(LogFieldList *fieldlist, 
char *printf_str, char
   -------------------------------------------------------------------------*/
 int
 LogBuffer::to_ascii(LogEntryHeader *entry, LogFormatType type, char *buf, int 
buf_len, const char *symbol_str, char *printf_str,
-                    unsigned buffer_version, const char *alt_format)
+                    unsigned buffer_version, const char *alt_format, 
LogEscapeType escape_type)
 {
   ink_assert(entry != nullptr);
   ink_assert(type == LOG_FORMAT_CUSTOM || type == LOG_FORMAT_TEXT);
@@ -642,7 +642,7 @@ LogBuffer::to_ascii(LogEntryHeader *entry, LogFormatType 
type, char *buf, int bu
   }
 
   int ret = resolve_custom_entry(fieldlist, printf_str, read_from, write_to, 
buf_len, entry->timestamp, entry->timestamp_usec,
-                                 buffer_version, alt_fieldlist, 
alt_printf_str);
+                                 buffer_version, alt_fieldlist, 
alt_printf_str, escape_type);
 
   delete alt_fieldlist;
   ats_free(alt_printf_str);
diff --git a/proxy/logging/LogBuffer.h b/proxy/logging/LogBuffer.h
index a6e168b2f..aa911cd76 100644
--- a/proxy/logging/LogBuffer.h
+++ b/proxy/logging/LogBuffer.h
@@ -189,10 +189,10 @@ public:
   // static functions
   static size_t max_entry_bytes();
   static int to_ascii(LogEntryHeader *entry, LogFormatType type, char *buf, 
int max_len, const char *symbol_str, char *printf_str,
-                      unsigned buffer_version, const char *alt_format = 
nullptr);
+                      unsigned buffer_version, const char *alt_format = 
nullptr, LogEscapeType escape_type = LOG_ESCAPE_NONE);
   static int resolve_custom_entry(LogFieldList *fieldlist, char *printf_str, 
char *read_from, char *write_to, int write_to_len,
                                   long timestamp, long timestamp_us, unsigned 
buffer_version, LogFieldList *alt_fieldlist = nullptr,
-                                  char *alt_printf_str = nullptr);
+                                  char *alt_printf_str = nullptr, 
LogEscapeType escape_type = LOG_ESCAPE_NONE);
 
   static void
   destroy(LogBuffer *&lb)
diff --git a/proxy/logging/LogField.cc b/proxy/logging/LogField.cc
index 34d23dd18..0a396ed29 100644
--- a/proxy/logging/LogField.cc
+++ b/proxy/logging/LogField.cc
@@ -592,12 +592,19 @@ LogField::marshal_agg(char *buf)
   string that represents the ASCII value of the field.
   -------------------------------------------------------------------------*/
 unsigned
-LogField::unmarshal(char **buf, char *dest, int len)
+LogField::unmarshal(char **buf, char *dest, int len, LogEscapeType escape_type)
 {
   if (!m_alias_map) {
     if (m_unmarshal_func == 
reinterpret_cast<UnmarshalFunc>(LogAccess::unmarshal_str) ||
         m_unmarshal_func == 
reinterpret_cast<UnmarshalFunc>(LogAccess::unmarshal_http_text)) {
       UnmarshalFuncWithSlice func = 
reinterpret_cast<UnmarshalFuncWithSlice>(m_unmarshal_func);
+      if (escape_type == LOG_ESCAPE_JSON) {
+        if (m_unmarshal_func == 
reinterpret_cast<UnmarshalFunc>(LogAccess::unmarshal_str)) {
+          func = 
reinterpret_cast<UnmarshalFuncWithSlice>(LogAccess::unmarshal_str_json);
+        } else if (m_unmarshal_func == 
reinterpret_cast<UnmarshalFunc>(LogAccess::unmarshal_http_text)) {
+          func = 
reinterpret_cast<UnmarshalFuncWithSlice>(LogAccess::unmarshal_http_text_json);
+        }
+      }
       return (*func)(buf, dest, len, &m_slice);
     }
     return (*m_unmarshal_func)(buf, dest, len);
diff --git a/proxy/logging/LogField.h b/proxy/logging/LogField.h
index cc4361453..3d6b1348a 100644
--- a/proxy/logging/LogField.h
+++ b/proxy/logging/LogField.h
@@ -32,6 +32,8 @@
 #include "LogFieldAliasMap.h"
 #include "Milestones.h"
 
+enum LogEscapeType { LOG_ESCAPE_NONE, LOG_ESCAPE_JSON };
+
 class LogAccess;
 
 struct LogSlice {
@@ -133,7 +135,7 @@ public:
   unsigned marshal_len(LogAccess *lad);
   unsigned marshal(LogAccess *lad, char *buf);
   unsigned marshal_agg(char *buf);
-  unsigned unmarshal(char **buf, char *dest, int len);
+  unsigned unmarshal(char **buf, char *dest, int len, LogEscapeType 
escape_type = LOG_ESCAPE_NONE);
   void display(FILE *fd = stdout);
   bool operator==(LogField &rhs);
   void updateField(LogAccess *lad, char *val, int len);
diff --git a/proxy/logging/LogFile.cc b/proxy/logging/LogFile.cc
index acf956c81..c575a08d2 100644
--- a/proxy/logging/LogFile.cc
+++ b/proxy/logging/LogFile.cc
@@ -63,9 +63,10 @@
   -------------------------------------------------------------------------*/
 
 LogFile::LogFile(const char *name, const char *header, LogFileFormat format, 
uint64_t signature, size_t ascii_buffer_size,
-                 size_t max_line_size, int pipe_buffer_size)
+                 size_t max_line_size, int pipe_buffer_size, LogEscapeType 
escape_type)
   : m_file_format(format),
     m_name(ats_strdup(name)),
+    m_escape_type(escape_type),
     m_header(ats_strdup(header)),
     m_signature(signature),
     m_max_line_size(max_line_size),
@@ -83,7 +84,7 @@ LogFile::LogFile(const char *name, const char *header, 
LogFileFormat format, uin
   m_fd                = -1;
   m_ascii_buffer_size = (ascii_buffer_size < max_line_size ? max_line_size : 
ascii_buffer_size);
 
-  Debug("log-file", "exiting LogFile constructor, m_name=%s, this=%p", m_name, 
this);
+  Debug("log-file", "exiting LogFile constructor, m_name=%s, this=%p, 
escape_type=%d", m_name, this, escape_type);
 }
 
 /*-------------------------------------------------------------------------
@@ -627,7 +628,7 @@ LogFile::write_ascii_logbuffer3(LogBufferHeader 
*buffer_header, const char *alt_
       }
 
       int bytes = LogBuffer::to_ascii(entry_header, format_type, 
&ascii_buffer[fmt_buf_bytes], m_max_line_size - 1, fieldlist_str,
-                                      printf_str, buffer_header->version, 
alt_format);
+                                      printf_str, buffer_header->version, 
alt_format, get_escape_type());
 
       if (bytes > 0) {
         fmt_buf_bytes += bytes;
diff --git a/proxy/logging/LogFile.h b/proxy/logging/LogFile.h
index 7a7618265..2d603d5e1 100644
--- a/proxy/logging/LogFile.h
+++ b/proxy/logging/LogFile.h
@@ -43,7 +43,7 @@ class LogFile : public LogBufferSink, public RefCountObj
 {
 public:
   LogFile(const char *name, const char *header, LogFileFormat format, uint64_t 
signature, size_t ascii_buffer_size = 4 * 9216,
-          size_t max_line_size = 9216, int pipe_buffer_size = 0);
+          size_t max_line_size = 9216, int pipe_buffer_size = 0, LogEscapeType 
escape_type = LOG_ESCAPE_NONE);
   LogFile(const LogFile &);
   ~LogFile() override;
 
@@ -83,6 +83,12 @@ public:
     return m_file_format;
   }
 
+  LogEscapeType
+  get_escape_type() const
+  {
+    return m_escape_type;
+  }
+
   const char *
   get_format_name() const
   {
@@ -120,6 +126,7 @@ public:
 
 private:
   char *m_name;
+  LogEscapeType m_escape_type;
 
 public:
   BaseLogFile *m_log; // BaseLogFile backs the actual file on disk
diff --git a/proxy/logging/LogFormat.cc b/proxy/logging/LogFormat.cc
index c1f190189..c3e8f8142 100644
--- a/proxy/logging/LogFormat.cc
+++ b/proxy/logging/LogFormat.cc
@@ -177,7 +177,7 @@ LogFormat::init_variables(const char *name, const char 
*fieldlist_str, const cha
   form %<symbol>.
   -------------------------------------------------------------------------*/
 
-LogFormat::LogFormat(const char *name, const char *format_str, unsigned 
interval_sec)
+LogFormat::LogFormat(const char *name, const char *format_str, unsigned 
interval_sec, LogEscapeType escape_type)
   : m_interval_sec(0),
     m_interval_next(0),
     m_agg_marshal_space(nullptr),
@@ -189,7 +189,8 @@ LogFormat::LogFormat(const char *name, const char 
*format_str, unsigned interval
     m_field_count(0),
     m_printf_str(nullptr),
     m_aggregate(false),
-    m_format_str(nullptr)
+    m_format_str(nullptr),
+    m_escape_type(escape_type)
 {
   setup(name, format_str, interval_sec);
 
@@ -218,7 +219,8 @@ LogFormat::LogFormat(const LogFormat &rhs)
     m_printf_str(nullptr),
     m_aggregate(false),
     m_format_str(nullptr),
-    m_format_type(rhs.m_format_type)
+    m_format_type(rhs.m_format_type),
+    m_escape_type(rhs.m_escape_type)
 {
   if (m_valid) {
     if (m_format_type == LOG_FORMAT_TEXT) {
diff --git a/proxy/logging/LogFormat.h b/proxy/logging/LogFormat.h
index 60c3a4235..8d6cfca1b 100644
--- a/proxy/logging/LogFormat.h
+++ b/proxy/logging/LogFormat.h
@@ -52,7 +52,7 @@ enum LogFileFormat {
 class LogFormat : public RefCountObj
 {
 public:
-  LogFormat(const char *name, const char *format_str, unsigned interval_sec = 
0);
+  LogFormat(const char *name, const char *format_str, unsigned interval_sec = 
0, LogEscapeType escape_type = LOG_ESCAPE_NONE);
   LogFormat(const char *name, const char *fieldlist_str, const char 
*printf_str, unsigned interval_sec = 0);
   LogFormat(const LogFormat &rhs);
 
@@ -95,6 +95,11 @@ public:
   {
     return m_format_type;
   }
+  LogEscapeType
+  escape_type() const
+  {
+    return m_escape_type;
+  }
   char *
   printf_str() const
   {
@@ -158,6 +163,7 @@ private:
   bool m_aggregate;
   char *m_format_str;
   LogFormatType m_format_type;
+  LogEscapeType m_escape_type;
 
 public:
   LINK(LogFormat, link);
diff --git a/proxy/logging/LogObject.cc b/proxy/logging/LogObject.cc
index e8d45c62c..f9a4e2ce0 100644
--- a/proxy/logging/LogObject.cc
+++ b/proxy/logging/LogObject.cc
@@ -122,8 +122,8 @@ LogObject::LogObject(LogConfig *cfg, const LogFormat 
*format, const char *log_di
   // compute_signature is a static function
   m_signature = compute_signature(m_format, m_basename, m_flags);
 
-  m_logFile =
-    new LogFile(m_filename, header, file_format, m_signature, 
cfg->ascii_buffer_size, cfg->max_line_size, m_pipe_buffer_size);
+  m_logFile = new LogFile(m_filename, header, file_format, m_signature, 
cfg->ascii_buffer_size, cfg->max_line_size,
+                          m_pipe_buffer_size, format->escape_type());
 
   if (m_reopen_after_rolling) {
     m_logFile->open_file();
diff --git a/proxy/logging/YamlLogConfigDecoders.cc 
b/proxy/logging/YamlLogConfigDecoders.cc
index fca2817f5..328f2752a 100644
--- a/proxy/logging/YamlLogConfigDecoders.cc
+++ b/proxy/logging/YamlLogConfigDecoders.cc
@@ -28,7 +28,7 @@
 #include <algorithm>
 #include <memory>
 
-std::set<std::string> valid_log_format_keys = {"name", "format", "interval"};
+std::set<std::string> valid_log_format_keys = {"name", "format", "interval", 
"escape"};
 std::set<std::string> valid_log_filter_keys = {"name", "action", "condition"};
 
 namespace YAML
@@ -69,7 +69,21 @@ convert<std::unique_ptr<LogFormat>>::decode(const Node 
&node, std::unique_ptr<Lo
     interval = node["interval"].as<unsigned>();
   }
 
-  logFormat.reset(new LogFormat(name.c_str(), format.c_str(), interval));
+  // escape type
+  LogEscapeType escape_type = LOG_ESCAPE_NONE; // default value
+  if (node["escape"]) {
+    std::string escape = node["escape"].as<std::string>();
+    if (!strncasecmp(escape.c_str(), "json", 4)) {
+      escape_type = LOG_ESCAPE_JSON;
+    } else if (!strncasecmp(escape.c_str(), "none", 4)) {
+      escape_type = LOG_ESCAPE_NONE;
+    } else {
+      throw YAML::ParserException(node.Mark(), "invalid 'escape' argument '" + 
escape + "' for format name '" + name + "'");
+    }
+    Note("'escape' attribute for LogFormat object is; %s", escape.c_str());
+  }
+
+  logFormat.reset(new LogFormat(name.c_str(), format.c_str(), interval, 
escape_type));
 
   return true;
 }
diff --git a/tests/gold_tests/logging/gold/field-json-test.gold 
b/tests/gold_tests/logging/gold/field-json-test.gold
new file mode 100644
index 000000000..8bdc8c20e
--- /dev/null
+++ b/tests/gold_tests/logging/gold/field-json-test.gold
@@ -0,0 +1,3 @@
+{"foo":"ab\td\/ef","foo-slice":"\td"}
+{"foo":"ab\u001fd\/ef","foo-slice":"\u001fd"}
+{"foo":"abc\u007fde","foo-slice":"c"}
diff --git a/tests/gold_tests/logging/log-field-json.test.py 
b/tests/gold_tests/logging/log-field-json.test.py
new file mode 100644
index 000000000..cd2110bb7
--- /dev/null
+++ b/tests/gold_tests/logging/log-field-json.test.py
@@ -0,0 +1,109 @@
+'''
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+
+Test.Summary = '''
+Test log fields.
+'''
+
+ts = Test.MakeATSProcess("ts", enable_cache=False)
+server = Test.MakeOriginServer("server")
+
+request_header = {'timestamp': 100, "headers": "GET /test-1 HTTP/1.1\r\nHost: 
test-1\r\n\r\n", "body": ""}
+response_header = {
+    'timestamp': 100,
+    "headers": "HTTP/1.1 200 OK\r\nTest: 1\r\nContent-Type: 
application/json\r\nConnection: close\r\nContent-Type: 
application/json\r\n\r\n",
+    "body": "Test 1"}
+server.addResponse("sessionlog.json", request_header, response_header)
+server.addResponse("sessionlog.json",
+                   {'timestamp': 101,
+                    "headers": "GET /test-2 HTTP/1.1\r\nHost: test-2\r\n\r\n",
+                    "body": ""},
+                   {'timestamp': 101,
+                       "headers": "HTTP/1.1 200 OK\r\nTest: 2\r\nContent-Type: 
application/jason\r\nConnection: close\r\nContent-Type: 
application/json\r\n\r\n",
+                       "body": "Test 2"})
+server.addResponse("sessionlog.json",
+                   {'timestamp': 102,
+                    "headers": "GET /test-3 HTTP/1.1\r\nHost: test-3\r\n\r\n",
+                    "body": ""},
+                   {'timestamp': 102,
+                       "headers": "HTTP/1.1 200 OK\r\nTest: 3\r\nConnection: 
close\r\nContent-Type: application/json\r\n\r\n",
+                       "body": "Test 3"})
+
+nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
+
+ts.Disk.records_config.update({
+    'proxy.config.net.connections_throttle': 100,
+    'proxy.config.dns.nameservers': f"127.0.0.1:{nameserver.Variables.Port}",
+    'proxy.config.dns.resolv_conf': 'NULL'
+})
+# setup some config file for this server
+ts.Disk.remap_config.AddLine(
+    'map / http://localhost:{}/'.format(server.Variables.Port)
+)
+
+ts.Disk.logging_yaml.AddLines(
+    '''
+logging:
+  formats:
+    - name: custom
+      escape: json
+      format: '{"foo":"%<{Foo}cqh>","foo-slice":"%<{Foo}cqh[2:-3]>"}'
+  logs:
+    - filename: field-json-test
+      format: custom
+'''.split("\n")
+)
+
+# #########################################################################
+# at the end of the different test run a custom log file should exist
+# Because of this we expect the testruns to pass the real test is if the
+# customlog file exists and passes the format check
+Test.Disk.File(os.path.join(ts.Variables.LOGDIR, 'field-json-test.log'),
+               exists=True, content='gold/field-json-test.gold')
+
+# first test is a miss for default
+tr = Test.AddTestRun()
+# Wait for the micro server
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(nameserver)
+# Delay on readiness of our ssl ports
+tr.Processes.Default.StartBefore(Test.Processes.ts)
+
+tr.Processes.Default.Command = 'curl --verbose --header "Host: test-1" 
--header "Foo: ab\td/ef" http://localhost:{0}/test-1' .format(
+    ts.Variables.port)
+tr.Processes.Default.ReturnCode = 0
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = 'curl --verbose --header "Host: test-2" 
--header "Foo: ab\x1fd/ef" http://localhost:{0}/test-2' .format(
+    ts.Variables.port)
+tr.Processes.Default.ReturnCode = 0
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = 'curl --verbose --header "Host: test-3" 
--header "Foo: abc\x7fde" http://localhost:{0}/test-3' .format(
+    ts.Variables.port)
+tr.Processes.Default.ReturnCode = 0
+
+# Wait for log file to appear, then wait one extra second to make sure TS is 
done writing it.
+test_run = Test.AddTestRun()
+test_run.Processes.Default.Command = (
+    os.path.join(Test.Variables.AtsTestToolsDir, 'condwait') + ' 60 1 -f ' +
+    os.path.join(ts.Variables.LOGDIR, 'field-json-test.log')
+)
+test_run.Processes.Default.ReturnCode = 0

Reply via email to