Introduce log_destination=jsonlog
authorMichael Paquier
Mon, 17 Jan 2022 01:16:53 +0000 (10:16 +0900)
committerMichael Paquier
Mon, 17 Jan 2022 01:16:53 +0000 (10:16 +0900)
"jsonlog" is a new value that can be added to log_destination to provide
logs in the JSON format, with its output written to a file, making it
the third type of destination of this kind, after "stderr" and
"csvlog".  The format is convenient to feed logs to other applications.
There is also a plugin external to core that provided this feature using
the hook in elog.c, but this had to overwrite the output of "stderr" to
work, so being able to do both at the same time was not possible.  The
files generated by this log format are suffixed with ".json", and use
the same rotation policies as the other two formats depending on the
backend configuration.

This takes advantage of the refactoring work done previously in ac7c807,
bed6ed38b76f89 and 2d77d83 for the backend parts, and 72b76f7 for the
TAP tests, making the addition of any new file-based format rather
straight-forward.

The documentation is updated to list all the keys and the values that
can exist in this new format.  pg_current_logfile() also required a
refresh for the new option.

Author: Sehrope Sarkuni, Michael Paquier
Reviewed-by: Nathan Bossart, Justin Pryzby
Discussion: https://postgr.es/m/CAH7T-aqswBM6JWe4pDehi1uOiufqe06DJWaU5=X7dDLyqUExHg@mail.gmail.com

12 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/func.sgml
src/backend/postmaster/syslogger.c
src/backend/utils/adt/misc.c
src/backend/utils/error/Makefile
src/backend/utils/error/elog.c
src/backend/utils/error/jsonlog.c [new file with mode: 0644]
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/bin/pg_ctl/t/004_logrotate.pl
src/include/postmaster/syslogger.h
src/include/utils/elog.h

index c0fbf03dd3cedd56318a661871810b4da29d7dcc..4cd9818acf87d37eb0626af7c518b0318afe1d17 100644 (file)
@@ -5931,7 +5931,8 @@ SELECT * FROM parent WHERE key = 2400;
        
         PostgreSQL supports several methods
          for logging server messages, including
-         stderrcsvlog and
+         stderrcsvlog,
+         jsonlog, and
          syslog. On Windows,
          eventlog is also supported. Set this
          parameter to a list of desired log destinations separated by
@@ -5950,25 +5951,35 @@ SELECT * FROM parent WHERE key = 2400;
         CSV-format log output.
        
        
-        When either stderr or
-        csvlog are included, the file
-        current_logfiles is created to record the location
-        of the log file(s) currently in use by the logging collector and the
-        associated logging destination. This provides a convenient way to
-        find the logs currently in use by the instance. Here is an example of
-        this file's content:
+        If jsonlog is included in
+        log_destination, log entries are output in
+        JSON format, which is convenient for loading logs
+        into programs.
+        See  for details.
+         must be enabled to generate
+        JSON-format log output.
+       
+       
+        When either stderr,
+        csvlog or jsonlog are
+        included, the file current_logfiles is created to
+        record the location of the log file(s) currently in use by the logging
+        collector and the associated logging destination. This provides a
+        convenient way to find the logs currently in use by the instance. Here
+        is an example of this file's content:
 
 stderr log/postgresql.log
 csvlog log/postgresql.csv
+jsonlog log/postgresql.json
 
 
         current_logfiles is recreated when a new log file
         is created as an effect of rotation, and
         when log_destination is reloaded.  It is removed when
-        neither stderr
-        nor csvlog are included
-        in log_destination, and when the logging collector is
-        disabled.
+        none of stderr,
+        csvlog or jsonlog are
+        included in log_destination, and when the logging
+        collector is disabled.
        
 
        
@@ -6106,6 +6117,13 @@ local0.*    /var/log/postgresql
         (If log_filename ends in .log, the suffix is
         replaced instead.)
        
+       
+        If JSON-format output is enabled in log_destination,
+        .json will be appended to the timestamped
+        log file name to create the file name for JSON-format output.
+        (If log_filename ends in .log, the suffix is
+        replaced instead.)
+       
        
         This parameter can only be set in the postgresql.conf
         file or on the server command line.
@@ -7467,6 +7485,187 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         
       
     
+     
+     Using JSON-Format Log Output
+
+     
+      Including jsonlog in the
+      log_destination list provides a convenient way to
+      import log files into many different programs. This option emits log
+      lines in (JSON) format.
+     
+
+     
+      String fields with null values are excluded from output.
+      Additional fields may be added in the future. User applications that
+      process jsonlog output should ignore unknown fields.
+     
+
+     
+      Each log line is serialized as a JSON object as of the following
+      set of keys with their values.
+     
+
+     
+      Keys and values of JSON log entries
+      
+       
+        
+         Key name
+         Type
+         Description
+        
+       
+       
+        
+         timestamp
+         string
+         Time stamp with milliseconds
+        
+        
+         user
+         string
+         User name
+        
+        
+         dbname
+         string
+         Database name
+        
+        
+         pid
+         number
+         Process ID
+        
+        
+         remote_host
+         string
+         Client host
+        
+        
+         remote_port
+         number
+         Client port
+        
+        
+         session_id
+         string
+         Session ID
+        
+        
+         line_num
+         number
+         Per-session line number
+        
+        
+         ps
+         string
+         Current ps display
+        
+        
+         session_start
+         string
+         Session start time
+        
+        
+         vxid
+         string
+         Virtual transaction ID
+        
+        
+         txid
+         string
+         Regular transaction ID
+        
+        
+         error_severity
+         string
+         Error severity
+        
+        
+         state_code
+         string
+         SQLSTATE code
+        
+        
+         message
+         string
+         Error message
+        
+        
+         detail
+         string
+         Error message detail
+        
+        
+         hint
+         string
+         Error message hint
+        
+        
+         internal_query
+         string
+         Internal query that led to the error
+        
+        
+         internal_position
+         number
+         Cursor index into internal query
+        
+        
+         context
+         string
+         Error context
+        
+        
+         statement
+         string
+         Client-supplied query string
+        
+        
+         cursor_position
+         string
+         Cursor index into query string
+        
+        
+         func_name
+         string
+         Error location function name
+        
+        
+         file_name
+         string
+         File name of error location
+        
+        
+         file_line_num
+         number
+         File line number of the error location
+        
+        
+         application_name
+         string
+         Client application name
+        
+        
+         backend_type
+         string
+         Type of backend
+        
+        
+         leader_pid
+         number
+         Process ID of leader for active parallel workers
+        
+        
+         query_id
+         number
+         Query ID
+        
+       
+      
+     
+    
 
    
     Process Title
index 391d01bcf3d1d61f24c85e55b39db2872c6a4364..a270f89dfe92f5522ca36d651dab5d1dafd64e08 100644 (file)
@@ -22446,10 +22446,12 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
         format, pg_current_logfile without an argument
         returns the path of the file having the first format found in the
         ordered list: stderr,
-        csvlog.  NULL is returned
-        if no log file has any of these formats.
+        csvlogjsonlog.
+        NULL is returned if no log file has any of these
+        formats.
         To request information about a specific log file format, supply
-        either csvlog or stderr as the
+        either csvlogjsonlog or
+        stderr as the
         value of the optional parameter. The result is NULL
         if the log format requested is not configured in
         .
index 2256f072aaaf98366f79ac1c193bcbade27cbe48..25e2131e31180b8e6a573b31399692a08e780103 100644 (file)
@@ -86,9 +86,11 @@ static bool pipe_eof_seen = false;
 static bool rotation_disabled = false;
 static FILE *syslogFile = NULL;
 static FILE *csvlogFile = NULL;
+static FILE *jsonlogFile = NULL;
 NON_EXEC_STATIC pg_time_t first_syslogger_file_time = 0;
 static char *last_sys_file_name = NULL;
 static char *last_csv_file_name = NULL;
+static char *last_json_file_name = NULL;
 
 /*
  * Buffers for saving partial messages from different backends.
@@ -281,6 +283,8 @@ SysLoggerMain(int argc, char *argv[])
    last_sys_file_name = logfile_getname(first_syslogger_file_time, NULL);
    if (csvlogFile != NULL)
        last_csv_file_name = logfile_getname(first_syslogger_file_time, ".csv");
+   if (jsonlogFile != NULL)
+       last_json_file_name = logfile_getname(first_syslogger_file_time, ".json");
 
    /* remember active logfile parameters */
    currentLogDir = pstrdup(Log_directory);
@@ -367,6 +371,14 @@ SysLoggerMain(int argc, char *argv[])
                (csvlogFile != NULL))
                rotation_requested = true;
 
+           /*
+            * Force a rotation if JSONLOG output was just turned on or off
+            * and we need to open or close jsonlogFile accordingly.
+            */
+           if (((Log_destination & LOG_DESTINATION_JSONLOG) != 0) !=
+               (jsonlogFile != NULL))
+               rotation_requested = true;
+
            /*
             * If rotation time parameter changed, reset next rotation time,
             * but don't immediately force a rotation.
@@ -417,6 +429,12 @@ SysLoggerMain(int argc, char *argv[])
                rotation_requested = true;
                size_rotation_for |= LOG_DESTINATION_CSVLOG;
            }
+           if (jsonlogFile != NULL &&
+               ftell(jsonlogFile) >= Log_RotationSize * 1024L)
+           {
+               rotation_requested = true;
+               size_rotation_for |= LOG_DESTINATION_JSONLOG;
+           }
        }
 
        if (rotation_requested)
@@ -426,7 +444,9 @@ SysLoggerMain(int argc, char *argv[])
             * was sent by pg_rotate_logfile() or "pg_ctl logrotate".
             */
            if (!time_based_rotation && size_rotation_for == 0)
-               size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG;
+               size_rotation_for = LOG_DESTINATION_STDERR |
+                   LOG_DESTINATION_CSVLOG |
+                   LOG_DESTINATION_JSONLOG;
            logfile_rotate(time_based_rotation, size_rotation_for);
        }
 
@@ -632,6 +652,20 @@ SysLogger_Start(void)
        pfree(filename);
    }
 
+   /*
+    * Likewise for the initial JSON log file, if that's enabled.  (Note that
+    * we open syslogFile even when only JSON output is nominally enabled,
+    * since some code paths will write to syslogFile anyway.)
+    */
+   if (Log_destination & LOG_DESTINATION_JSONLOG)
+   {
+       filename = logfile_getname(first_syslogger_file_time, ".json");
+
+       jsonlogFile = logfile_open(filename, "a", false);
+
+       pfree(filename);
+   }
+
 #ifdef EXEC_BACKEND
    switch ((sysloggerPid = syslogger_forkexec()))
 #else
@@ -729,6 +763,11 @@ SysLogger_Start(void)
                fclose(csvlogFile);
                csvlogFile = NULL;
            }
+           if (jsonlogFile != NULL)
+           {
+               fclose(jsonlogFile);
+               jsonlogFile = NULL;
+           }
            return (int) sysloggerPid;
    }
 
@@ -805,6 +844,7 @@ syslogger_forkexec(void)
    int         ac = 0;
    char        filenobuf[32];
    char        csvfilenobuf[32];
+   char        jsonfilenobuf[32];
 
    av[ac++] = "postgres";
    av[ac++] = "--forklog";
@@ -817,6 +857,9 @@ syslogger_forkexec(void)
    snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%d",
             syslogger_fdget(csvlogFile));
    av[ac++] = csvfilenobuf;
+   snprintf(jsonfilenobuf, sizeof(jsonfilenobuf), "%d",
+            syslogger_fdget(jsonlogFile));
+   av[ac++] = jsonfilenobuf;
 
    av[ac] = NULL;
    Assert(ac < lengthof(av));
@@ -834,7 +877,7 @@ syslogger_parseArgs(int argc, char *argv[])
 {
    int         fd;
 
-   Assert(argc == 5);
+   Assert(argc == 6);
    argv += 3;
 
    /*
@@ -848,6 +891,8 @@ syslogger_parseArgs(int argc, char *argv[])
    syslogFile = syslogger_fdopen(fd);
    fd = atoi(*argv++);
    csvlogFile = syslogger_fdopen(fd);
+   fd = atoi(*argv++);
+   jsonlogFile = syslogger_fdopen(fd);
 }
 #endif                         /* EXEC_BACKEND */
 
@@ -896,7 +941,9 @@ process_pipe_input(char *logbuffer, int *bytes_in_logbuffer)
 
        /* Do we have a valid header? */
        memcpy(&p, cursor, offsetof(PipeProtoHeader, data));
-       dest_flags = p.flags & (PIPE_PROTO_DEST_STDERR | PIPE_PROTO_DEST_CSVLOG);
+       dest_flags = p.flags & (PIPE_PROTO_DEST_STDERR |
+                               PIPE_PROTO_DEST_CSVLOG |
+                               PIPE_PROTO_DEST_JSONLOG);
        if (p.nuls[0] == '\0' && p.nuls[1] == '\0' &&
            p.len > 0 && p.len <= PIPE_MAX_PAYLOAD &&
            p.pid != 0 &&
@@ -918,6 +965,8 @@ process_pipe_input(char *logbuffer, int *bytes_in_logbuffer)
                dest = LOG_DESTINATION_STDERR;
            else if ((p.flags & PIPE_PROTO_DEST_CSVLOG) != 0)
                dest = LOG_DESTINATION_CSVLOG;
+           else if ((p.flags & PIPE_PROTO_DEST_JSONLOG) != 0)
+               dest = LOG_DESTINATION_JSONLOG;
            else
            {
                /* this should never happen as of the header validation */
@@ -1097,19 +1146,24 @@ write_syslogger_file(const char *buffer, int count, int destination)
    FILE       *logfile;
 
    /*
-    * If we're told to write to csvlogFile, but it's not open, dump the data
-    * to syslogFile (which is always open) instead.  This can happen if CSV
-    * output is enabled after postmaster start and we've been unable to open
-    * csvlogFile.  There are also race conditions during a parameter change
-    * whereby backends might send us CSV output before we open csvlogFile or
-    * after we close it.  Writing CSV-formatted output to the regular log
-    * file isn't great, but it beats dropping log output on the floor.
+    * If we're told to write to a structured log file, but it's not open,
+    * dump the data to syslogFile (which is always open) instead.  This can
+    * happen if structured output is enabled after postmaster start and we've
+    * been unable to open logFile.  There are also race conditions during a
+    * parameter change whereby backends might send us structured output
+    * before we open the logFile or after we close it.  Writing formatted
+    * output to the regular log file isn't great, but it beats dropping log
+    * output on the floor.
     *
-    * Think not to improve this by trying to open csvlogFile on-the-fly.  Any
+    * Think not to improve this by trying to open logFile on-the-fly.  Any
     * failure in that would lead to recursion.
     */
-   logfile = (destination == LOG_DESTINATION_CSVLOG &&
-              csvlogFile != NULL) ? csvlogFile : syslogFile;
+   if ((destination & LOG_DESTINATION_CSVLOG) && csvlogFile != NULL)
+       logfile = csvlogFile;
+   else if ((destination & LOG_DESTINATION_JSONLOG) && jsonlogFile != NULL)
+       logfile = jsonlogFile;
+   else
+       logfile = syslogFile;
 
    rc = fwrite(buffer, 1, count, logfile);
 
@@ -1180,7 +1234,8 @@ pipeThread(void *arg)
        if (Log_RotationSize > 0)
        {
            if (ftell(syslogFile) >= Log_RotationSize * 1024L ||
-               (csvlogFile != NULL && ftell(csvlogFile) >= Log_RotationSize * 1024L))
+               (csvlogFile != NULL && ftell(csvlogFile) >= Log_RotationSize * 1024L) ||
+               (jsonlogFile != NULL && ftell(jsonlogFile) >= Log_RotationSize * 1024L))
                SetLatch(MyLatch);
        }
        LeaveCriticalSection(&sysloggerSection);
@@ -1292,6 +1347,8 @@ logfile_rotate_dest(bool time_based_rotation, int size_rotation_for,
        logFileExt = NULL;
    else if (target_dest == LOG_DESTINATION_CSVLOG)
        logFileExt = ".csv";
+   else if (target_dest == LOG_DESTINATION_JSONLOG)
+       logFileExt = ".json";
    else
    {
        /* cannot happen */
@@ -1379,6 +1436,12 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
                             &csvlogFile))
        return;
 
+   /* file rotation for jsonlog */
+   if (!logfile_rotate_dest(time_based_rotation, size_rotation_for, fntime,
+                            LOG_DESTINATION_JSONLOG, &last_json_file_name,
+                            &jsonlogFile))
+       return;
+
    update_metainfo_datafile();
 
    set_next_rotation_time();
@@ -1465,7 +1528,8 @@ update_metainfo_datafile(void)
    mode_t      oumask;
 
    if (!(Log_destination & LOG_DESTINATION_STDERR) &&
-       !(Log_destination & LOG_DESTINATION_CSVLOG))
+       !(Log_destination & LOG_DESTINATION_CSVLOG) &&
+       !(Log_destination & LOG_DESTINATION_JSONLOG))
    {
        if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
            ereport(LOG,
@@ -1523,6 +1587,19 @@ update_metainfo_datafile(void)
            return;
        }
    }
+
+   if (last_json_file_name && (Log_destination & LOG_DESTINATION_JSONLOG))
+   {
+       if (fprintf(fh, "jsonlog %s\n", last_json_file_name) < 0)
+       {
+           ereport(LOG,
+                   (errcode_for_file_access(),
+                    errmsg("could not write file \"%s\": %m",
+                           LOG_METAINFO_DATAFILE_TMP)));
+           fclose(fh);
+           return;
+       }
+   }
    fclose(fh);
 
    if (rename(LOG_METAINFO_DATAFILE_TMP, LOG_METAINFO_DATAFILE) != 0)
index fe4f180b6f16c55b8df8d485ea7b73406a3e7bed..e79eb6b4788a907e074eb9350e38f4abe7f3f756 100644 (file)
@@ -843,11 +843,13 @@ pg_current_logfile(PG_FUNCTION_ARGS)
    {
        logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
-       if (strcmp(logfmt, "stderr") != 0 && strcmp(logfmt, "csvlog") != 0)
+       if (strcmp(logfmt, "stderr") != 0 &&
+           strcmp(logfmt, "csvlog") != 0 &&
+           strcmp(logfmt, "jsonlog") != 0)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                     errmsg("log format \"%s\" is not supported", logfmt),
-                    errhint("The supported log formats are \"stderr\" and \"csvlog\".")));
+                    errhint("The supported log formats are \"stderr\", \"csvlog\", and \"jsonlog\".")));
    }
 
    fd = AllocateFile(LOG_METAINFO_DATAFILE, "r");
index ef770dd2f2a09e9c819ae3b14bc2a37190853789..65ba61fb3c1df567fe0d37f5d87abd4959470ab1 100644 (file)
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = \
    assert.o \
    csvlog.o \
-   elog.o
+   elog.o \
+   jsonlog.o
 
 include $(top_srcdir)/src/backend/common.mk
index 4db41ba564c8aa688a1fad72369ed24895169a96..7402696986be3e51068ae790fa3399561447e68d 100644 (file)
@@ -2984,6 +2984,22 @@ send_message_to_server_log(ErrorData *edata)
            fallback_to_stderr = true;
    }
 
+   /* Write to JSON log, if enabled */
+   if (Log_destination & LOG_DESTINATION_JSONLOG)
+   {
+       /*
+        * Send JSON data if it's safe to do so (syslogger doesn't need the
+        * pipe).  If this is not possible, fallback to an entry written to
+        * stderr.
+        */
+       if (redirection_done || MyBackendType == B_LOGGER)
+       {
+           write_jsonlog(edata);
+       }
+       else
+           fallback_to_stderr = true;
+   }
+
    /*
     * Write to stderr, if enabled or if required because of a previous
     * limitation.
@@ -3059,6 +3075,8 @@ write_pipe_chunks(char *data, int len, int dest)
        p.proto.flags |= PIPE_PROTO_DEST_STDERR;
    else if (dest == LOG_DESTINATION_CSVLOG)
        p.proto.flags |= PIPE_PROTO_DEST_CSVLOG;
+   else if (dest == LOG_DESTINATION_JSONLOG)
+       p.proto.flags |= PIPE_PROTO_DEST_JSONLOG;
 
    /* write all but the last chunk */
    while (len > PIPE_MAX_PAYLOAD)
diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c
new file mode 100644 (file)
index 0000000..843641c
--- /dev/null
@@ -0,0 +1,303 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonlog.c
+ *   JSON logging
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of Californi
+ *
+ *
+ * IDENTIFICATION
+ *   src/backend/utils/error/jsonlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "libpq/libpq.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/syslogger.h"
+#include "storage/lock.h"
+#include "storage/proc.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_status.h"
+#include "utils/elog.h"
+#include "utils/guc.h"
+#include "utils/json.h"
+#include "utils/ps_status.h"
+
+static void appendJSONKeyValueFmt(StringInfo buf, const char *key,
+                                 bool escape_key,
+                                 const char *fmt,...) pg_attribute_printf(4, 5);
+
+/*
+ * appendJSONKeyValue
+ *
+ * Append to a StringInfo a comma followed by a JSON key and a value.
+ * The key is always escaped.  The value can be escaped optionally, that
+ * is dependent on the data type of the key.
+ */
+static void
+appendJSONKeyValue(StringInfo buf, const char *key, const char *value,
+                  bool escape_value)
+{
+   Assert(key != NULL);
+
+   if (value == NULL)
+       return;
+
+   appendStringInfoChar(buf, ',');
+   escape_json(buf, key);
+   appendStringInfoChar(buf, ':');
+
+   if (escape_value)
+       escape_json(buf, value);
+   else
+       appendStringInfoString(buf, value);
+}
+
+
+/*
+ * appendJSONKeyValueFmt
+ *
+ * Evaluate the fmt string and then invoke appendJSONKeyValue() as the
+ * value of the JSON property.  Both the key and value will be escaped by
+ * appendJSONKeyValue().
+ */
+static void
+appendJSONKeyValueFmt(StringInfo buf, const char *key,
+                     bool escape_key, const char *fmt,...)
+{
+   int         save_errno = errno;
+   size_t      len = 128;      /* initial assumption about buffer size */
+   char       *value;
+
+   for (;;)
+   {
+       va_list     args;
+       size_t      newlen;
+
+       /* Allocate result buffer */
+       value = (char *) palloc(len);
+
+       /* Try to format the data. */
+       errno = save_errno;
+       va_start(args, fmt);
+       newlen = pvsnprintf(value, len, fmt, args);
+       va_end(args);
+
+       if (newlen < len)
+           break;              /* success */
+
+       /* Release buffer and loop around to try again with larger len. */
+       pfree(value);
+       len = newlen;
+   }
+
+   appendJSONKeyValue(buf, key, value, escape_key);
+
+   /* Clean up */
+   pfree(value);
+}
+
+/*
+ * Write logs in json format.
+ */
+void
+write_jsonlog(ErrorData *edata)
+{
+   StringInfoData buf;
+   char       *start_time;
+   char       *log_time;
+
+   /* static counter for line numbers */
+   static long log_line_number = 0;
+
+   /* Has the counter been reset in the current process? */
+   static int  log_my_pid = 0;
+
+   /*
+    * This is one of the few places where we'd rather not inherit a static
+    * variable's value from the postmaster.  But since we will, reset it when
+    * MyProcPid changes.
+    */
+   if (log_my_pid != MyProcPid)
+   {
+       log_line_number = 0;
+       log_my_pid = MyProcPid;
+       reset_formatted_start_time();
+   }
+   log_line_number++;
+
+   initStringInfo(&buf);
+
+   /* Initialize string */
+   appendStringInfoChar(&buf, '{');
+
+   /* timestamp with milliseconds */
+   log_time = get_formatted_log_time();
+
+   /*
+    * First property does not use appendJSONKeyValue as it does not have
+    * comma prefix.
+    */
+   escape_json(&buf, "timestamp");
+   appendStringInfoChar(&buf, ':');
+   escape_json(&buf, log_time);
+
+   /* username */
+   if (MyProcPort)
+       appendJSONKeyValue(&buf, "user", MyProcPort->user_name, true);
+
+   /* database name */
+   if (MyProcPort)
+       appendJSONKeyValue(&buf, "dbname", MyProcPort->database_name, true);
+
+   /* Process ID */
+   if (MyProcPid != 0)
+       appendJSONKeyValueFmt(&buf, "pid", false, "%d", MyProcPid);
+
+   /* Remote host and port */
+   if (MyProcPort && MyProcPort->remote_host)
+   {
+       appendJSONKeyValue(&buf, "remote_host", MyProcPort->remote_host, true);
+       if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
+           appendJSONKeyValue(&buf, "remote_port", MyProcPort->remote_port, false);
+   }
+
+   /* Session id */
+   appendJSONKeyValueFmt(&buf, "session_id", true, "%lx.%x",
+                         (long) MyStartTime, MyProcPid);
+
+   /* Line number */
+   appendJSONKeyValueFmt(&buf, "line_num", false, "%ld", log_line_number);
+
+   /* PS display */
+   if (MyProcPort)
+   {
+       StringInfoData msgbuf;
+       const char *psdisp;
+       int         displen;
+
+       initStringInfo(&msgbuf);
+       psdisp = get_ps_display(&displen);
+       appendBinaryStringInfo(&msgbuf, psdisp, displen);
+       appendJSONKeyValue(&buf, "ps", msgbuf.data, true);
+
+       pfree(msgbuf.data);
+   }
+
+   /* session start timestamp */
+   start_time = get_formatted_start_time();
+   appendJSONKeyValue(&buf, "session_start", start_time, true);
+
+   /* Virtual transaction id */
+   /* keep VXID format in sync with lockfuncs.c */
+   if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
+       appendJSONKeyValueFmt(&buf, "vxid", true, "%d/%u", MyProc->backendId,
+                             MyProc->lxid);
+
+   /* Transaction id */
+   appendJSONKeyValueFmt(&buf, "txid", false, "%u",
+                         GetTopTransactionIdIfAny());
+
+   /* Error severity */
+   if (edata->elevel)
+       appendJSONKeyValue(&buf, "error_severity",
+                          (char *) error_severity(edata->elevel), true);
+
+   /* SQL state code */
+   if (edata->sqlerrcode)
+       appendJSONKeyValue(&buf, "state_code",
+                          unpack_sql_state(edata->sqlerrcode), true);
+
+   /* errmessage */
+   appendJSONKeyValue(&buf, "message", edata->message, true);
+
+   /* errdetail or error_detail log */
+   if (edata->detail_log)
+       appendJSONKeyValue(&buf, "detail", edata->detail_log, true);
+   else
+       appendJSONKeyValue(&buf, "detail", edata->detail, true);
+
+   /* errhint */
+   if (edata->hint)
+       appendJSONKeyValue(&buf, "hint", edata->hint, true);
+
+   /* internal query */
+   if (edata->internalquery)
+       appendJSONKeyValue(&buf, "internal_query", edata->internalquery,
+                          true);
+
+   /* if printed internal query, print internal pos too */
+   if (edata->internalpos > 0 && edata->internalquery != NULL)
+       appendJSONKeyValueFmt(&buf, "internal_position", false, "%u",
+                             edata->internalpos);
+
+   /* errcontext */
+   if (edata->context && !edata->hide_ctx)
+       appendJSONKeyValue(&buf, "context", edata->context, true);
+
+   /* user query --- only reported if not disabled by the caller */
+   if (check_log_of_query(edata))
+   {
+       appendJSONKeyValue(&buf, "statement", debug_query_string, true);
+       if (edata->cursorpos > 0)
+           appendJSONKeyValueFmt(&buf, "cursor_position", false, "%d",
+                                 edata->cursorpos);
+   }
+
+   /* file error location */
+   if (Log_error_verbosity >= PGERROR_VERBOSE)
+   {
+       if (edata->funcname)
+           appendJSONKeyValue(&buf, "func_name", edata->funcname, true);
+       if (edata->filename)
+       {
+           appendJSONKeyValue(&buf, "file_name", edata->filename, true);
+           appendJSONKeyValueFmt(&buf, "file_line_num", false, "%d",
+                                 edata->lineno);
+       }
+   }
+
+   /* Application name */
+   if (application_name && application_name[0] != '\0')
+       appendJSONKeyValue(&buf, "application_name", application_name, true);
+
+   /* backend type */
+   appendJSONKeyValue(&buf, "backend_type", get_backend_type_for_log(), true);
+
+   /* leader PID */
+   if (MyProc)
+   {
+       PGPROC     *leader = MyProc->lockGroupLeader;
+
+       /*
+        * Show the leader only for active parallel workers.  This leaves out
+        * the leader of a parallel group.
+        */
+       if (leader && leader->pid != MyProcPid)
+           appendJSONKeyValueFmt(&buf, "leader_pid", false, "%d",
+                                 leader->pid);
+   }
+
+   /* query id */
+   appendJSONKeyValueFmt(&buf, "query_id", false, "%lld",
+                         (long long) pgstat_get_my_query_id());
+
+   /* Finish string */
+   appendStringInfoChar(&buf, '}');
+   appendStringInfoChar(&buf, '\n');
+
+   /* If in the syslogger process, try to write messages direct to file */
+   if (MyBackendType == B_LOGGER)
+       write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_JSONLOG);
+   else
+       write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_JSONLOG);
+
+   pfree(buf.data);
+}
index effb9d03a037190e61e385ad3d27a1060a67916e..4c94f09c645132d918827fd0a3eee56b5e5bf0bf 100644 (file)
@@ -4276,7 +4276,7 @@ static struct config_string ConfigureNamesString[] =
        {"log_destination", PGC_SIGHUP, LOGGING_WHERE,
            gettext_noop("Sets the destination for server log output."),
            gettext_noop("Valid values are combinations of \"stderr\", "
-                        "\"syslog\", \"csvlog\", and \"eventlog\", "
+                        "\"syslog\", \"csvlog\", \"jsonlog\" and \"eventlog\", "
                         "depending on the platform."),
            GUC_LIST_INPUT
        },
@@ -11752,6 +11752,8 @@ check_log_destination(char **newval, void **extra, GucSource source)
            newlogdest |= LOG_DESTINATION_STDERR;
        else if (pg_strcasecmp(tok, "csvlog") == 0)
            newlogdest |= LOG_DESTINATION_CSVLOG;
+       else if (pg_strcasecmp(tok, "jsonlog") == 0)
+           newlogdest |= LOG_DESTINATION_JSONLOG;
 #ifdef HAVE_SYSLOG
        else if (pg_strcasecmp(tok, "syslog") == 0)
            newlogdest |= LOG_DESTINATION_SYSLOG;
index a1acd46b6118de189bb4943b144bb93cdbcab713..817d5f5324671b0f46b9b3d9d050f1995622564a 100644 (file)
 # - Where to Log -
 
 #log_destination = 'stderr'        # Valid values are combinations of
-                   # stderr, csvlog, syslog, and eventlog,
-                   # depending on platform.  csvlog
-                   # requires logging_collector to be on.
+                   # stderr, csvlog, jsonlog, syslog, and
+                   # eventlog, depending on platform.
+                   # csvlog and jsonlog require
+                   # logging_collector to be on.
 
 # This is used when logging to stderr:
-#logging_collector = off       # Enable capturing of stderr and csvlog
-                   # into log files. Required to be on for
-                   # csvlogs.
+#logging_collector = off       # Enable capturing of stderr, jsonlog
+                   # and csvlog into log files. Required
+                   # to be on for csvlogs and jsonlogs.
                    # (change requires restart)
 
 # These are only used if logging_collector is on:
index e04331bfef1561814a5573d24f9eb1b107fbc535..de6028760d55a90cd89c67c182575f783f8e3003 100644 (file)
@@ -6,7 +6,7 @@ use warnings;
 
 use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
-use Test::More tests => 10;
+use Test::More tests => 14;
 use Time::HiRes qw(usleep);
 
 # Extract the file name of a $format from the contents of
@@ -65,7 +65,7 @@ $node->init();
 $node->append_conf(
    'postgresql.conf', qq(
 logging_collector = on
-log_destination = 'stderr, csvlog'
+log_destination = 'stderr, csvlog, jsonlog'
 # these ensure stability of test results:
 log_rotation_age = 0
 lc_messages = 'C'
@@ -96,11 +96,13 @@ note "current_logfiles = $current_logfiles";
 like(
    $current_logfiles,
    qr|^stderr log/postgresql-.*log
-csvlog log/postgresql-.*csv$|,
+csvlog log/postgresql-.*csv
+jsonlog log/postgresql-.*json$|,
    'current_logfiles is sane');
 
-check_log_pattern('stderr', $current_logfiles, 'division by zero', $node);
-check_log_pattern('csvlog', $current_logfiles, 'division by zero', $node);
+check_log_pattern('stderr',  $current_logfiles, 'division by zero', $node);
+check_log_pattern('csvlog',  $current_logfiles, 'division by zero', $node);
+check_log_pattern('jsonlog', $current_logfiles, 'division by zero', $node);
 
 # Sleep 2 seconds and ask for log rotation; this should result in
 # output into a different log file name.
@@ -122,13 +124,15 @@ note "now current_logfiles = $new_current_logfiles";
 like(
    $new_current_logfiles,
    qr|^stderr log/postgresql-.*log
-csvlog log/postgresql-.*csv$|,
+csvlog log/postgresql-.*csv
+jsonlog log/postgresql-.*json$|,
    'new current_logfiles is sane');
 
 # Verify that log output gets to this file, too
 $node->psql('postgres', 'fee fi fo fum');
 
-check_log_pattern('stderr', $new_current_logfiles, 'syntax error', $node);
-check_log_pattern('csvlog', $new_current_logfiles, 'syntax error', $node);
+check_log_pattern('stderr',  $new_current_logfiles, 'syntax error', $node);
+check_log_pattern('csvlog',  $new_current_logfiles, 'syntax error', $node);
+check_log_pattern('jsonlog', $new_current_logfiles, 'syntax error', $node);
 
 $node->stop();
index 2df68a196e81e257222b35107b492714605f2df8..1ca326e52e3e2ab2465a74ac8a3a2e9673cc6731 100644 (file)
@@ -64,6 +64,7 @@ typedef union
 /* log destinations */
 #define PIPE_PROTO_DEST_STDERR 0x10
 #define PIPE_PROTO_DEST_CSVLOG 0x20
+#define PIPE_PROTO_DEST_JSONLOG    0x40
 
 /* GUC options */
 extern bool Logging_collector;
index 5bc38663cb35070b2f15ca4f1984a5174714ad88..3eb8de39661e64d81b2c3a8b406468469fbbac0e 100644 (file)
@@ -436,6 +436,7 @@ extern bool syslog_split_messages;
 #define LOG_DESTINATION_SYSLOG  2
 #define LOG_DESTINATION_EVENTLOG 4
 #define LOG_DESTINATION_CSVLOG  8
+#define LOG_DESTINATION_JSONLOG    16
 
 /* Other exported functions */
 extern void DebugFileOpen(void);
@@ -453,6 +454,7 @@ extern void write_pipe_chunks(char *data, int len, int dest);
 
 /* Destination-specific functions */
 extern void write_csvlog(ErrorData *edata);
+extern void write_jsonlog(ErrorData *edata);
 
 #ifdef HAVE_SYSLOG
 extern void set_syslog_parameters(const char *ident, int facility);