Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mI3vZ-00044z-LA for pgsql-hackers@arkaria.postgresql.org; Mon, 23 Aug 2021 06:59:26 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1mI3vY-000855-Fx for pgsql-hackers@arkaria.postgresql.org; Mon, 23 Aug 2021 06:59:24 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mI3vY-00084w-4K for pgsql-hackers@lists.postgresql.org; Mon, 23 Aug 2021 06:59:24 +0000 Received: from mail-wr1-x430.google.com ([2a00:1450:4864:20::430]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1mI3vS-0007mA-7O for pgsql-hackers@lists.postgresql.org; Mon, 23 Aug 2021 06:59:22 +0000 Received: by mail-wr1-x430.google.com with SMTP id f5so24598189wrm.13 for ; Sun, 22 Aug 2021 23:59:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aiven.io; s=google; h=from:to:cc:subject:date:message-id:organization:in-reply-to :references:mime-version:content-transfer-encoding; bh=D0cv9awr6e9tkO5q6LXBIyPecB54adTx5fFZk7leCcg=; b=Y3hfyYWlo8WukA/IDO0JjZ7nEl7aF+SnwYnC/iJSybBXWeS/qIndziSSzrDR/iFOby 94sFxMOqNOqKwGA5bp6G3N6syG5gC2BirtG9p85P/1zMTqyb7FmJ3j9kVm0V2JhqketP o1LZikjgO5XkpZFvcPk0XlERwiIgPcrfzI5v8= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:organization :in-reply-to:references:mime-version:content-transfer-encoding; bh=D0cv9awr6e9tkO5q6LXBIyPecB54adTx5fFZk7leCcg=; b=seenebzRvOGJtL7XLkYpS5/B/H3eipgFsuTz9IeFVTBrpfYCtxJsiyHowQGzgUsIqG DHcqE/dimaPMm3q0XrXjt8iBsNi2tWLRpxCo59SB+stIZFEVq2aYScpZeCgXOMMl50Rc TAI6+sY+DJLVYFWBDaCSvk8ln9O9VCtYO+/RgoWQ3gm1JBdrRe4zjWPKaA17otNYsPiY 8J3+szWaamwJtnQ5khQVUdnNE4VCdRNzEGfH6aGluSfV83+Kq5EhjDpesnWWuJuzgSbO 4Lki08hoHoB3Scx7AARgS7EcKQ3jMbkgs+k5nppdfq9WBvnsp4wvdhD7hCunx613MSCt KCAQ== X-Gm-Message-State: AOAM533zUAn2NDEBYjhIM+YlOEEQdLWOr6zVrQ9b97s7SMdL2zoN06v5 6tmefL+E4EwHx21GAsyATmLbkw== X-Google-Smtp-Source: ABdhPJwSmm85XZwCp3lZfbppVWlP/K7Wudrkspl/eYvyfwevVms/HeEp/ayJkvPUApUJvUNukP8T8g== X-Received: by 2002:adf:e10c:: with SMTP id t12mr11847305wrz.36.1629701955870; Sun, 22 Aug 2021 23:59:15 -0700 (PDT) Received: from aivenronan.localnet (static-176-158-121-96.ftth.abo.bbox.fr. [176.158.121.96]) by smtp.gmail.com with ESMTPSA id p5sm15342167wrd.25.2021.08.22.23.59.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 22 Aug 2021 23:59:15 -0700 (PDT) From: Ronan Dunklau To: Alvaro Herrera Cc: pgsql-hackers@lists.postgresql.org Subject: Re: Proposal: More structured logging Date: Mon, 23 Aug 2021 08:58:50 +0200 Message-ID: <3358218.yRJqF8fxcQ@aivenronan> Organization: aiven In-Reply-To: <1745789.A5U81WbCk6@aivenronan> References: <202108191304.u7g5wwuepwpe@alvherre.pgsql> <1745789.A5U81WbCk6@aivenronan> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart3875947.UqfILvNHoF" Content-Transfer-Encoding: 7Bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --nextPart3875947.UqfILvNHoF Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="iso-8859-1" Le vendredi 20 ao=FBt 2021, 11:31:21 CEST Ronan Dunklau a =E9crit : > Le jeudi 19 ao=FBt 2021, 15:04:30 CEST Alvaro Herrera a =E9crit : > > On 2021-Aug-13, Ronan Dunklau wrote: > > > ereport(NOTICE, > > >=20 > > > (errmsg("My log message")), > > > (errtag("EMITTER", "MYEXTENSION")), > > > (errtag("MSG-ID", "%d", error_message_id)) > > >=20 > > > ); > >=20 > > Interesting idea. I agree this would be useful. > >=20 > > > Please find attached a very small POC patch to better demonstrate wha= t I > > > propose. > >=20 > > Seems like a good start. I think a further step towards a committable > > patch would include a test module that uses the new tagging > > functionality. >=20 > Please find attached the original patch + a new one adding a test module. > The test module exposes a function for generating logs with tags, and a l= og > hook which format the tags in the DETAIL field for easy regression testin= g. >=20 > Next step would be to emit those tags in the CSV logs. I'm not sure what > representation they should have though: a nested csv in it's own column ?= A > key =3D> value pairs list similar to hstore ? A json object ? I opted for a JSON representation in the CSV logs, please find attached v3= =20 which is the same thing as v2 with another patch for CSV log output. >=20 > Also we should probably make this available to the client too, but once > again the format of the tag field needs to be defined. Any opinion ? =2D-=20 Ronan Dunklau --nextPart3875947.UqfILvNHoF Content-Disposition: attachment; filename="v3-0001-Add-ErrorTag-support.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="UTF-8"; name="v3-0001-Add-ErrorTag-support.patch" From e5af5d1a07e65926eee90e0d18443a673d1fcba8 Mon Sep 17 00:00:00 2001 From: Ronan Dunklau Date: Fri, 13 Aug 2021 15:03:18 +0200 Subject: [PATCH v3 1/3] Add ErrorTag support --- src/backend/utils/error/elog.c | 48 ++++++++++++++++++++++++++++++++++ src/include/utils/elog.h | 10 +++++++ 2 files changed, 58 insertions(+) diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index a3e1c59a82..5b9b1b8a72 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -465,6 +465,7 @@ errstart(int elevel, const char *domain) edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION; /* errno is saved here so that error parameter eval can't change it */ edata->saved_errno = errno; + edata->tags = NIL; /* * Any allocations for this error state level should go into ErrorContext @@ -516,6 +517,7 @@ errfinish(const char *filename, int lineno, const char *funcname) int elevel; MemoryContext oldcontext; ErrorContextCallback *econtext; + ListCell *lc; recursion_depth++; CHECK_STACK_DEPTH(); @@ -621,7 +623,18 @@ errfinish(const char *filename, int lineno, const char *funcname) pfree(edata->constraint_name); if (edata->internalquery) pfree(edata->internalquery); + /* Every tag should have been palloc'ed */ + if (edata->tags != NIL) + { + foreach(lc, edata->tags) + { + ErrorTag *tag = (ErrorTag *) lfirst(lc); + pfree(tag->tagvalue); + pfree(tag); + } + pfree(edata->tags); + } errordata_stack_depth--; /* Exit error-handling context */ @@ -1192,6 +1205,41 @@ errhint_plural(const char *fmt_singular, const char *fmt_plural, return 0; /* return value does not matter */ } +int +errtag(const char *tag, const char *fmt_value,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + ErrorTag *etag; + MemoryContext oldcontext; + StringInfoData buf; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + etag = palloc(sizeof(ErrorTag)); + etag->tagname = tag; + initStringInfo(&buf); + for (;;) + { + va_list args; + int needed; + + errno = edata->saved_errno; + va_start(args, fmt_value); + needed = appendStringInfoVA(&buf, fmt_value, args); + va_end(args); + if (needed == 0) + break; + enlargeStringInfo(&buf, needed); + } + etag->tagvalue = pstrdup(buf.data); + edata->tags = lappend(edata->tags, etag); + pfree(buf.data); + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; +} + /* * errcontext_msg --- add a context error message text to the current error diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index f53607e12e..1c490d1b11 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -15,6 +15,7 @@ #define ELOG_H #include +#include "nodes/pg_list.h" /* Error level codes */ #define DEBUG5 10 /* Debugging messages, in categories of @@ -193,6 +194,8 @@ extern int errhint(const char *fmt,...) pg_attribute_printf(1, 2); extern int errhint_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...) pg_attribute_printf(1, 4) pg_attribute_printf(2, 4); +extern int errtag(const char *tag, const char *fmt_value,...) pg_attribute_printf(2, 3); + /* * errcontext() is typically called in error context callback functions, not * within an ereport() invocation. The callback function can be in a different @@ -395,11 +398,18 @@ typedef struct ErrorData int internalpos; /* cursor index into internalquery */ char *internalquery; /* text of internally-generated query */ int saved_errno; /* errno at entry */ + List *tags; /* List of error tags */ /* context containing associated non-constant strings */ struct MemoryContextData *assoc_context; } ErrorData; +typedef struct ErrorTag +{ + const char *tagname; + char *tagvalue; +} ErrorTag; + extern void EmitErrorReport(void); extern ErrorData *CopyErrorData(void); extern void FreeErrorData(ErrorData *edata); -- 2.32.0 --nextPart3875947.UqfILvNHoF Content-Disposition: attachment; filename="v3-0002-Add-test-module-for-the-new-tag-functionality.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="UTF-8"; name="v3-0002-Add-test-module-for-the-new-tag-functionality.patch" From 2cee45f3a6273141c177dff7d869c66e6fad01e4 Mon Sep 17 00:00:00 2001 From: Ronan Dunklau Date: Fri, 20 Aug 2021 10:46:20 +0200 Subject: [PATCH v3 2/3] Add test module for the new tag functionality. This test module consists of an extension exposing a function which just logs things, and a hook which adds the tags to the detail field in order to be tested in "regular" output. --- src/test/modules/test_logging/Makefile | 20 +++ .../test_logging/expected/test_logging.out | 18 +++ .../modules/test_logging/sql/test_logging.sql | 5 + .../test_logging/test_logging--1.0.sql | 4 + src/test/modules/test_logging/test_logging.c | 121 ++++++++++++++++++ .../modules/test_logging/test_logging.control | 5 + 6 files changed, 173 insertions(+) create mode 100644 src/test/modules/test_logging/Makefile create mode 100644 src/test/modules/test_logging/expected/test_logging.out create mode 100644 src/test/modules/test_logging/sql/test_logging.sql create mode 100644 src/test/modules/test_logging/test_logging--1.0.sql create mode 100644 src/test/modules/test_logging/test_logging.c create mode 100644 src/test/modules/test_logging/test_logging.control diff --git a/src/test/modules/test_logging/Makefile b/src/test/modules/test_logging/Makefile new file mode 100644 index 0000000000..1191886e43 --- /dev/null +++ b/src/test/modules/test_logging/Makefile @@ -0,0 +1,20 @@ +# src/test/modules/test_logging/Makefile + +MODULES = test_logging + +EXTENSION = test_logging +DATA = test_logging--1.0.sql +PGFILEDESC = "test_logging - simple log hook test" + +REGRESS = test_logging + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_logging +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_logging/expected/test_logging.out b/src/test/modules/test_logging/expected/test_logging.out new file mode 100644 index 0000000000..41423da535 --- /dev/null +++ b/src/test/modules/test_logging/expected/test_logging.out @@ -0,0 +1,18 @@ +CREATE EXTENSION test_logging; +SET log_min_messages = NOTICE; +SELECT test_logging(18, 'This is a test message', NULL); +NOTICE: This is a test message +DETAIL: NB TAGS: 0 TAGS: + test_logging +-------------- + +(1 row) + +SELECT test_logging(18, 'This is a test message', '{"tag1": "value1", "tag2": "value2"}'); +NOTICE: This is a test message +DETAIL: NB TAGS: 2 TAGS: tag1: value1 tag2: value2 + test_logging +-------------- + +(1 row) + diff --git a/src/test/modules/test_logging/sql/test_logging.sql b/src/test/modules/test_logging/sql/test_logging.sql new file mode 100644 index 0000000000..bb964b4b63 --- /dev/null +++ b/src/test/modules/test_logging/sql/test_logging.sql @@ -0,0 +1,5 @@ +CREATE EXTENSION test_logging; +SET log_min_messages = NOTICE; +SELECT test_logging(18, 'This is a test message', NULL); +SELECT test_logging(18, 'This is a test message', '{"tag1": "value1", "tag2": "value2"}'); + diff --git a/src/test/modules/test_logging/test_logging--1.0.sql b/src/test/modules/test_logging/test_logging--1.0.sql new file mode 100644 index 0000000000..663af2db8f --- /dev/null +++ b/src/test/modules/test_logging/test_logging--1.0.sql @@ -0,0 +1,4 @@ +CREATE FUNCTION test_logging(level int, message text, tags jsonb) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/src/test/modules/test_logging/test_logging.c b/src/test/modules/test_logging/test_logging.c new file mode 100644 index 0000000000..f92eff5150 --- /dev/null +++ b/src/test/modules/test_logging/test_logging.c @@ -0,0 +1,121 @@ +/*-------------------------------------------------------------------------- + * + * test_logging.c + * Test logging functions + * + * Copyright (c) 2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_logging/test_logging.c + * + * ------------------------------------------------------------------------- + */ + +#include +#include "postgres.h" + +#include "fmgr.h" +#include "utils/builtins.h" +#include "utils/elog.h" +#include "utils/jsonb.h" + +PG_MODULE_MAGIC; + +static emit_log_hook_type prev_log_hook = NULL; + +void _PG_init(void); +void _PG_fini(void); + +static void move_tags_to_detail(ErrorData *edata); + +static char * +jsonb_value_to_tagvalue(JsonbValue *v) +{ + switch (v->type) + { + case jbvNull: + return ""; + case jbvString: + return pnstrdup(v->val.string.val, v->val.string.len); + default: + elog(ERROR, "jsonb type not allowed here: %d", (int) v->type); + } +} + +static void +jsonb_tags_to_key_value_text_pairs(Jsonb *tags, List **keys, List **values) +{ + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken r; + bool skipNested = false; + + it = JsonbIteratorInit(&tags->root); + *keys = *values = NIL; + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + if (r == WJB_KEY) + { + *keys = lappend(*keys, pnstrdup(v.val.string.val, v.val.string.len)); + r = JsonbIteratorNext(&it, &v, skipNested); + Assert(r != WJB_DONE); + *values = lappend(*values, jsonb_value_to_tagvalue(&v)); + } + } +} + +PG_FUNCTION_INFO_V1(test_logging); +Datum +test_logging(PG_FUNCTION_ARGS) +{ + int level = PG_GETARG_INT32(0); + char *message = ""; + List *keys = NIL, + *values = NIL; + ListCell *lk, + *lv; + + if (!PG_ARGISNULL(1)) + { + message = text_to_cstring(PG_GETARG_TEXT_PP(1)); + } + if (!PG_ARGISNULL(2)) + { + jsonb_tags_to_key_value_text_pairs(PG_GETARG_JSONB_P(2), &keys, &values); + } + ereport(level, + (errmsg("%s", message), + ({ + forboth(lk, keys, lv, values) + { + (errtag(lfirst(lk), "%s", (char *) lfirst(lv))); + }}) + )); + + PG_RETURN_VOID(); +} + +void +move_tags_to_detail(ErrorData *edata) +{ + StringInfoData buf; + ListCell *lc; + + initStringInfo(&buf); + foreach(lc, edata->tags) + { + ErrorTag *tag = (ErrorTag *) lfirst(lc); + + appendStringInfo(&buf, "%s: %s ", tag->tagname, tag->tagvalue); + } + errdetail("NB TAGS: %d TAGS: %s", list_length(edata->tags), buf.data); + pfree(buf.data); +} + +void +_PG_init(void) +{ + prev_log_hook = emit_log_hook; + emit_log_hook = move_tags_to_detail; +} diff --git a/src/test/modules/test_logging/test_logging.control b/src/test/modules/test_logging/test_logging.control new file mode 100644 index 0000000000..b1596b4b1d --- /dev/null +++ b/src/test/modules/test_logging/test_logging.control @@ -0,0 +1,5 @@ +# test_logging extension +comment = 'test_logging - simple extension for emitting logs' +default_version = '1.0' +module_pathname = '$libdir/test_logging' +relocatable = true -- 2.32.0 --nextPart3875947.UqfILvNHoF Content-Disposition: attachment; filename="v3-0003-Output-error-tags-in-CSV-logs.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="UTF-8"; name="v3-0003-Output-error-tags-in-CSV-logs.patch" From be7fe7bbca9740c390a2a96b985c28d964bd1ef8 Mon Sep 17 00:00:00 2001 From: Ronan Dunklau Date: Fri, 13 Aug 2021 15:03:32 +0200 Subject: [PATCH v3 3/3] Output error tags in CSV logs --- doc/src/sgml/config.sgml | 4 +++- src/backend/utils/error/elog.c | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 2c31c35a6b..4ef59b32e9 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7338,7 +7338,8 @@ log_line_prefix = '%m [%p] %q%u@%d/%a ' location of the error in the PostgreSQL source code (if log_error_verbosity is set to verbose), application name, backend type, process ID of parallel group leader, - and query id. + query id, + and optional tags added by the logger as JSON. Here is a sample table definition for storing CSV-format log output: @@ -7370,6 +7371,7 @@ CREATE TABLE postgres_log backend_type text, leader_pid integer, query_id bigint, + tags jsonb PRIMARY KEY (session_id, session_line_num) ); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 5b9b1b8a72..4737054b87 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -80,6 +80,7 @@ #include "storage/proc.h" #include "tcop/tcopprot.h" #include "utils/guc.h" +#include "utils/json.h" #include "utils/memutils.h" #include "utils/ps_status.h" @@ -3013,10 +3014,32 @@ write_csvlog(ErrorData *edata) appendStringInfo(&buf, "%d", leader->pid); } appendStringInfoChar(&buf, ','); - /* query id */ appendStringInfo(&buf, "%lld", (long long) pgstat_get_my_query_id()); + appendStringInfoChar(&buf, ','); + if (edata->tags != NIL) + { + StringInfoData tagbuf; + ListCell *lc; + bool first = true; + initStringInfo(&tagbuf); + appendStringInfoChar(&tagbuf, '{'); + foreach(lc, edata->tags) + { + ErrorTag *etag = lfirst(lc); + + if (!first) + appendStringInfoChar(&tagbuf, ','); + escape_json(&tagbuf, etag->tagname); + appendStringInfoChar(&tagbuf, ':'); + escape_json(&tagbuf, etag->tagvalue); + first = false; + } + appendStringInfoChar(&tagbuf, '}'); + appendCSVLiteral(&buf, tagbuf.data); + pfree(tagbuf.data); + } appendStringInfoChar(&buf, '\n'); /* If in the syslogger process, try to write messages direct to file */ -- 2.32.0 --nextPart3875947.UqfILvNHoF--