diff options
Diffstat (limited to 'src/backend/utils')
-rw-r--r-- | src/backend/utils/adt/misc.c | 6 | ||||
-rw-r--r-- | src/backend/utils/error/Makefile | 3 | ||||
-rw-r--r-- | src/backend/utils/error/elog.c | 18 | ||||
-rw-r--r-- | src/backend/utils/error/jsonlog.c | 303 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 4 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 13 |
6 files changed, 337 insertions, 10 deletions
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index fe4f180b6f1..e79eb6b4788 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -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"); diff --git a/src/backend/utils/error/Makefile b/src/backend/utils/error/Makefile index ef770dd2f2a..65ba61fb3c1 100644 --- a/src/backend/utils/error/Makefile +++ b/src/backend/utils/error/Makefile @@ -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 diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 4db41ba564c..7402696986b 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -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 index 00000000000..843641c865f --- /dev/null +++ b/src/backend/utils/error/jsonlog.c @@ -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); +} diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index effb9d03a03..4c94f09c645 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -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; diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a1acd46b611..817d5f53246 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -432,14 +432,15 @@ # - 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: |