├── .gitignore ├── .travis.yml ├── Dockerfile.tmpl ├── LICENSE ├── Makefile ├── README.md ├── conf.add ├── docker-compose.yml ├── errlevel.c ├── errlevel.gperf ├── expected ├── basic.out └── basic96.out ├── main.sql ├── pg_logging--0.1--0.2.sql ├── pg_logging.c ├── pg_logging.control ├── pg_logging.h ├── pl_funcs.c ├── run_tests.sh └── sql ├── basic.sql └── basic96.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.so 3 | *.o 4 | *.pyc 5 | /results 6 | regression.diffs 7 | regression.out 8 | *.plist 9 | Dockerfile 10 | pg_logging--0.2.sql 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: required 5 | dist: trusty 6 | 7 | language: c 8 | 9 | services: 10 | - docker 11 | 12 | install: 13 | - sed -e 's/${CHECK_CODE}/'${CHECK_CODE}/g -e 's/${PG_VERSION}/'${PG_VERSION}/g Dockerfile.tmpl > Dockerfile 14 | - docker-compose build 15 | 16 | script: 17 | - docker-compose run tests 18 | 19 | env: 20 | - PG_VERSION=9.6 CHECK_CODE=clang 21 | - PG_VERSION=9.6 CHECK_CODE=false 22 | - PG_VERSION=10 CHECK_CODE=clang 23 | - PG_VERSION=10 CHECK_CODE=false 24 | - PG_VERSION=11 CHECK_CODE=clang 25 | - PG_VERSION=11 CHECK_CODE=false 26 | -------------------------------------------------------------------------------- /Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM postgres:${PG_VERSION}-alpine 2 | 3 | ENV LANG=C.UTF-8 PGDATA=/pg/data 4 | 5 | RUN if [ "${CHECK_CODE}" = "clang" ] ; then \ 6 | echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ 7 | apk --no-cache add clang-analyzer make musl-dev gcc openssl-dev; \ 8 | fi 9 | 10 | RUN if [ "${CHECK_CODE}" = "false" ] ; then \ 11 | echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ 12 | apk --no-cache add curl python3 gcc make musl-dev openssl-dev;\ 13 | fi 14 | 15 | RUN mkdir -p ${PGDATA} && \ 16 | mkdir /pg/src && \ 17 | chown postgres:postgres ${PGDATA} && \ 18 | chmod a+rwx /usr/local/lib/postgresql && \ 19 | chmod a+rwx /usr/local/share/postgresql/extension && \ 20 | mkdir -p /usr/local/share/doc/postgresql/contrib && \ 21 | chmod a+rwx /usr/local/share/doc/postgresql/contrib 22 | 23 | ADD . /pg/src 24 | WORKDIR /pg/src 25 | RUN chmod -R go+rwX /pg/src 26 | USER postgres 27 | ENTRYPOINT PGDATA=${PGDATA} CHECK_CODE=${CHECK_CODE} bash run_tests.sh 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pg_logging is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. 2 | 3 | Copyright (c) 2014-2018, Postgres Professional 4 | Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group 5 | Portions Copyright (c) 1994, The Regents of the University of California 6 | 7 | Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. 8 | 9 | IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # contrib/pg_logging/Makefile 2 | 3 | MODULE_big = pg_logging 4 | OBJS= pg_logging.o errlevel.o pl_funcs.o $(WIN32RES) 5 | 6 | EXTENSION = pg_logging 7 | EXTVERSION = 0.2 8 | PGFILEDESC = "PostgreSQL logging interface" 9 | 10 | DATA = $(EXTENSION)--0.1--0.2.sql 11 | DATA_built = $(EXTENSION)--$(EXTVERSION).sql 12 | 13 | ifndef PG_CONFIG 14 | PG_CONFIG = pg_config 15 | endif 16 | VNUM := $(shell $(PG_CONFIG) --version | awk '{print $$2}') 17 | 18 | ifeq ($(VNUM),$(filter 9.6%,$(VNUM))) 19 | REGRESS = basic96 20 | else 21 | REGRESS = basic 22 | endif 23 | EXTRA_REGRESS_OPTS=--temp-config=$(CURDIR)/conf.add 24 | 25 | PGXS := $(shell $(PG_CONFIG) --pgxs) 26 | include $(PGXS) 27 | 28 | num: 29 | echo $(VNUM) 30 | 31 | install: num 32 | 33 | errlevel.c: 34 | gperf errlevel.gperf --null-strings --global-table --output-file=errlevel.c --word-array-name=errlevel_wordlist 35 | sed -i.bak -e 's/static struct ErrorLevel errlevel_wordlist/struct ErrorLevel errlevel_wordlist/g' errlevel.c 36 | rm errlevel.c.bak 37 | 38 | $(EXTENSION)--$(EXTVERSION).sql: main.sql 39 | cat $^ > $@ 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/postgrespro/pg_logging.svg?branch=master)](https://travis-ci.org/postgrespro/pg_logging) 2 | [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/pg_logging/master/LICENSE) 3 | 4 | pg_logging 5 | ================= 6 | 7 | PostgreSQL logging interface. 8 | 9 | Installation 10 | ------------- 11 | 12 | # make sure that directory with pg_config in PATH or specify it with PG_CONFIG variable 13 | make install 14 | 15 | # in postgresql.conf add: 16 | shared_preload_libraries = 'pg_logging' 17 | 18 | # install the extension 19 | > CREATE EXTENSION pg_logging; 20 | 21 | Available functions 22 | -------------------- 23 | 24 | get_log( 25 | flush bool default true 26 | ) 27 | 28 | get_log( 29 | from_position int 30 | ) 31 | 32 | This function is used to fetch the logged information. The information is 33 | similar to the data that postgres writes to log files. 34 | 35 | `flush` means that fetched data will not be returned on next query calls. By 36 | default it's true. 37 | 38 | `from_position` is used as fail-safe case, when a client specifies until 39 | which position it already has data. This position should be equal to 40 | `position` field from `log_item`. If there was wraparound there is a chance 41 | that the position will be invalid and query will raise an error. In this case 42 | the client should use `get_log(flush bool)` function (and possibly increase 43 | the ring buffer size). 44 | 45 | Logs are stored in the ring buffer which means that non fetched data will 46 | be rewritten in the buffer wraparounds. Since reading position should be 47 | accordingly moved on each rewrite it could slower down the database. 48 | 49 | `get_log` function returns rows of `log_item` type. `log_item` is specified as: 50 | 51 | create type log_item as ( 52 | log_time timestamp with time zone, 53 | level int, 54 | pid int, 55 | line_num bigint, /* log line number */ 56 | appname text, 57 | start_time timestamp with time zone, /* backend start time */ 58 | datid oid, /* database id */ 59 | errno int, 60 | errcode int, 61 | errstate text, 62 | message text, 63 | detail text, 64 | detail_log text, 65 | hint text, 66 | context text, 67 | context_domain text, 68 | domain text, 69 | internalpos int, 70 | internalquery text, 71 | userid oid, 72 | remote_host text, 73 | command_tag text, 74 | vxid text, /* virtual transaction id */ 75 | txid bigint, /* transaction id */ 76 | query text, 77 | query_pos int, 78 | position int 79 | ); 80 | 81 | `error_level` type 82 | ------------------- 83 | 84 | The extension installs special type called `error_level` which can be used to 85 | get textual representation of `level` field from `log_item`. To do that 86 | just add something like `level::error_level` to the columns list in your query. 87 | 88 | 89 | Options 90 | --------- 91 | 92 | pg_logging.buffer_size (10240) - size of internal ring buffer in kilobytes. 93 | pg_logging.enabled (on) - enables or disables the logging. 94 | pg_logging.ignore_statements (off) - skip statements lines if `log_statement=all` 95 | pg_logging.set_query_fields (on) - set query and query_pos fields. 96 | -------------------------------------------------------------------------------- /conf.add: -------------------------------------------------------------------------------- 1 | shared_preload_libraries = 'pg_logging' 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | build: . 3 | -------------------------------------------------------------------------------- /errlevel.c: -------------------------------------------------------------------------------- 1 | /* ANSI-C code produced by gperf version 3.1 */ 2 | /* Command-line: gperf --null-strings --global-table --output-file=errlevel.c --word-array-name=errlevel_wordlist errlevel.gperf */ 3 | /* Computed positions: -k'$' */ 4 | 5 | #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ 6 | && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ 7 | && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ 8 | && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ 9 | && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ 10 | && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ 11 | && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ 12 | && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ 13 | && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ 14 | && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ 15 | && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ 16 | && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ 17 | && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ 18 | && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ 19 | && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ 20 | && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ 21 | && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ 22 | && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ 23 | && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ 24 | && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ 25 | && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ 26 | && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ 27 | && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) 28 | /* The character set is not based on ISO-646. */ 29 | #error "gperf generated tables don't work with this execution character set. Please report a bug to ." 30 | #endif 31 | 32 | #line 1 "errlevel.gperf" 33 | 34 | #include 35 | #include "pg_logging.h" 36 | #line 8 "errlevel.gperf" 37 | struct ErrorLevel; 38 | 39 | #define TOTAL_KEYWORDS 13 40 | #define MIN_WORD_LENGTH 3 41 | #define MAX_WORD_LENGTH 15 42 | #define MIN_HASH_VALUE 3 43 | #define MAX_HASH_VALUE 21 44 | /* maximum key range = 19, duplicates = 0 */ 45 | 46 | #ifdef __GNUC__ 47 | __inline 48 | #else 49 | #ifdef __cplusplus 50 | inline 51 | #endif 52 | #endif 53 | static unsigned int 54 | hash (register const char *str, register size_t len) 55 | { 56 | static unsigned char asso_values[] = 57 | { 58 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 59 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 60 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 61 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 62 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 63 | 3, 15, 10, 5, 22, 22, 22, 22, 22, 22, 64 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 65 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 66 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 67 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 15, 68 | 22, 0, 22, 0, 22, 22, 22, 22, 5, 22, 69 | 22, 0, 22, 22, 0, 22, 22, 22, 22, 22, 70 | 22, 0, 22, 22, 22, 22, 22, 22, 22, 22, 71 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 72 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 73 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 74 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 75 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 76 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 77 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 78 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 79 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 80 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 81 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 82 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 83 | 22, 22, 22, 22, 22, 22 84 | }; 85 | return len + asso_values[(unsigned char)str[len - 1]]; 86 | } 87 | 88 | struct ErrorLevel errlevel_wordlist[] = 89 | { 90 | {(char*)0}, {(char*)0}, {(char*)0}, 91 | #line 15 "errlevel.gperf" 92 | {"log", 15}, 93 | #line 17 "errlevel.gperf" 94 | {"info", 17}, 95 | #line 20 "errlevel.gperf" 96 | {"error", 20}, 97 | #line 18 "errlevel.gperf" 98 | {"notice", 18}, 99 | #line 19 "errlevel.gperf" 100 | {"warning", 19}, 101 | {(char*)0}, 102 | #line 13 "errlevel.gperf" 103 | {"debug2", 13}, 104 | #line 21 "errlevel.gperf" 105 | {"fatal", 21}, 106 | #line 10 "errlevel.gperf" 107 | {"debug5", 10}, 108 | {(char*)0}, {(char*)0}, 109 | #line 14 "errlevel.gperf" 110 | {"debug1", 14}, 111 | #line 16 "errlevel.gperf" 112 | {"log_server_only", 16}, 113 | #line 11 "errlevel.gperf" 114 | {"debug4", 11}, 115 | {(char*)0}, {(char*)0}, {(char*)0}, 116 | #line 22 "errlevel.gperf" 117 | {"panic", 22}, 118 | #line 12 "errlevel.gperf" 119 | {"debug3", 12} 120 | }; 121 | 122 | struct ErrorLevel * 123 | get_errlevel (register const char *str, register size_t len) 124 | { 125 | if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) 126 | { 127 | register unsigned int key = hash (str, len); 128 | 129 | if (key <= MAX_HASH_VALUE) 130 | { 131 | register const char *s = errlevel_wordlist[key].text; 132 | 133 | if (s && *str == *s && !strcmp (str + 1, s + 1)) 134 | return &errlevel_wordlist[key]; 135 | } 136 | } 137 | return 0; 138 | } 139 | #line 23 "errlevel.gperf" 140 | 141 | -------------------------------------------------------------------------------- /errlevel.gperf: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include "pg_logging.h" 4 | %} 5 | %struct-type 6 | %define slot-name text 7 | %define lookup-function-name get_errlevel 8 | struct ErrorLevel; 9 | %% 10 | debug5, 10 11 | debug4, 11 12 | debug3, 12 13 | debug2, 13 14 | debug1, 14 15 | log, 15 16 | log_server_only, 16 17 | info, 17 18 | notice, 18 19 | warning, 19 20 | error, 20 21 | fatal, 21 22 | panic, 22 23 | %% 24 | -------------------------------------------------------------------------------- /expected/basic.out: -------------------------------------------------------------------------------- 1 | create schema logging; 2 | create extension pg_logging schema logging; 3 | set log_statement=none; 4 | set pg_logging.buffer_size = 2000; 5 | select logging.flush_log(); 6 | flush_log 7 | ----------- 8 | 9 | (1 row) 10 | 11 | create view logs as 12 | select level, appname, errcode, 13 | message, detail, detail_log, hint, context, 14 | errstate, internalpos, internalquery, remote_host, command_tag, 15 | query, query_pos 16 | from logging.get_log(); 17 | create view logs2 as 18 | select level::logging.error_level, appname, errcode, 19 | message, detail, detail_log, hint, context, 20 | errstate, internalpos, internalquery, remote_host, command_tag, 21 | query, query_pos 22 | from logging.get_log(); 23 | select * from logs; 24 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 25 | -------+---------+---------+---------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+-------+----------- 26 | | | | | | | | | | | | | | | 27 | (1 row) 28 | 29 | select 1/0; 30 | ERROR: division by zero 31 | select 'aaaaa'::int; 32 | ERROR: invalid input syntax for integer: "aaaaa" 33 | LINE 1: select 'aaaaa'::int; 34 | ^ 35 | select * from logs; 36 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 37 | -------+------------------+----------+-------------------------------------------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+----------------------+----------- 38 | 20 | pg_regress/basic | 33816706 | division by zero | | | | | 22012 | 0 | | [local] | SELECT | select 1/0; | 0 39 | 20 | pg_regress/basic | 33685634 | invalid input syntax for integer: "aaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select 'aaaaa'::int; | 8 40 | (2 rows) 41 | 42 | select repeat('aaaaaaaaa', 20)::int; 43 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 44 | select repeat('aaaaaaaaa', 20)::int; 45 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 46 | select repeat('aaaaaaaaa', 20)::int; 47 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 48 | select repeat('aaaaaaaaa', 20)::int; 49 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 50 | select * from logs; 51 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 52 | -------+------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+--------------------------------------+----------- 53 | 20 | pg_regress/basic | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 54 | 20 | pg_regress/basic | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 55 | 20 | pg_regress/basic | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 56 | 20 | pg_regress/basic | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 57 | (4 rows) 58 | 59 | select logging.test_ereport('error', 'one', 'two', 'three'); 60 | ERROR: one 61 | DETAIL: two 62 | HINT: three 63 | select * from logs2; 64 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 65 | -------+------------------+---------+---------+--------+------------+-------+---------+----------+-------------+---------------+-------------+-------------+--------------------------------------------------------------+----------- 66 | error | pg_regress/basic | 1088 | one | two | | three | | 0A000 | 0 | | [local] | SELECT | select logging.test_ereport('error', 'one', 'two', 'three'); | 0 67 | (1 row) 68 | 69 | /* cleanup */ 70 | select logging.flush_log(); 71 | flush_log 72 | ----------- 73 | 74 | (1 row) 75 | 76 | show pg_logging.minlevel; 77 | pg_logging.minlevel 78 | --------------------- 79 | none 80 | (1 row) 81 | 82 | set pg_logging.minlevel = error; 83 | select logging.test_ereport('error', 'notice1', 'detail', 'hint'); 84 | ERROR: notice1 85 | DETAIL: detail 86 | HINT: hint 87 | select logging.test_ereport('error', 'notice2', 'detail', 'hint'); 88 | ERROR: notice2 89 | DETAIL: detail 90 | HINT: hint 91 | select logging.test_ereport('error', 'notice3', 'detail', 'hint'); 92 | ERROR: notice3 93 | DETAIL: detail 94 | HINT: hint 95 | select level, message, position from logging.get_log(false); 96 | level | message | position 97 | -------+---------+---------- 98 | 20 | notice1 | 0 99 | 20 | notice2 | 272 100 | 20 | notice3 | 544 101 | (3 rows) 102 | 103 | select level, message, position from logging.get_log(272); 104 | level | message | position 105 | -------+---------+---------- 106 | 20 | notice2 | 272 107 | 20 | notice3 | 544 108 | (2 rows) 109 | 110 | select level, message, position from logging.get_log(false); 111 | level | message | position 112 | -------+---------+---------- 113 | 20 | notice2 | 272 114 | 20 | notice3 | 544 115 | (2 rows) 116 | 117 | select level, message, position from logging.get_log(544); 118 | level | message | position 119 | -------+---------+---------- 120 | 20 | notice3 | 544 121 | (1 row) 122 | 123 | select level, message, position from logging.get_log(false); 124 | level | message | position 125 | -------+---------+---------- 126 | 20 | notice3 | 544 127 | (1 row) 128 | 129 | select level, message, position from logging.get_log(1000); 130 | ERROR: nothing with specified position was found 131 | select level, message, position from logging.get_log(false); 132 | level | message | position 133 | -------+-------------------------------------------+---------- 134 | 20 | notice3 | 544 135 | 20 | nothing with specified position was found | 816 136 | (2 rows) 137 | 138 | reset log_statement; 139 | drop extension pg_logging cascade; 140 | NOTICE: drop cascades to 2 other objects 141 | DETAIL: drop cascades to view logs 142 | drop cascades to view logs2 143 | -------------------------------------------------------------------------------- /expected/basic96.out: -------------------------------------------------------------------------------- 1 | create schema logging; 2 | create extension pg_logging schema logging; 3 | set log_statement=none; 4 | set pg_logging.buffer_size = 2000; 5 | select logging.flush_log(); 6 | flush_log 7 | ----------- 8 | 9 | (1 row) 10 | 11 | create view logs as 12 | select level, appname, errcode, 13 | message, detail, detail_log, hint, context, 14 | errstate, internalpos, internalquery, remote_host, command_tag, 15 | query, query_pos 16 | from logging.get_log(); 17 | create view logs2 as 18 | select level::logging.error_level, appname, errcode, 19 | message, detail, detail_log, hint, context, 20 | errstate, internalpos, internalquery, remote_host, command_tag, 21 | query, query_pos 22 | from logging.get_log(); 23 | select * from logs; 24 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 25 | -------+---------+---------+---------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+-------+----------- 26 | | | | | | | | | | | | | | | 27 | (1 row) 28 | 29 | select 1/0; 30 | ERROR: division by zero 31 | select 'aaaaa'::int; 32 | ERROR: invalid input syntax for integer: "aaaaa" 33 | LINE 1: select 'aaaaa'::int; 34 | ^ 35 | select * from logs; 36 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 37 | -------+------------+----------+-------------------------------------------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+----------------------+----------- 38 | 20 | pg_regress | 33816706 | division by zero | | | | | 22012 | 0 | | [local] | SELECT | select 1/0; | 0 39 | 20 | pg_regress | 33685634 | invalid input syntax for integer: "aaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select 'aaaaa'::int; | 8 40 | (2 rows) 41 | 42 | select repeat('aaaaaaaaa', 20)::int; 43 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 44 | select repeat('aaaaaaaaa', 20)::int; 45 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 46 | select repeat('aaaaaaaaa', 20)::int; 47 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 48 | select repeat('aaaaaaaaa', 20)::int; 49 | ERROR: invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 50 | select * from logs; 51 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 52 | -------+------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------+------+---------+----------+-------------+---------------+-------------+-------------+--------------------------------------+----------- 53 | 20 | pg_regress | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 54 | 20 | pg_regress | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 55 | 20 | pg_regress | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 56 | 20 | pg_regress | 33685634 | invalid input syntax for integer: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | | | | | 22P02 | 0 | | [local] | SELECT | select repeat('aaaaaaaaa', 20)::int; | 0 57 | (4 rows) 58 | 59 | select logging.test_ereport('error', 'one', 'two', 'three'); 60 | ERROR: one 61 | DETAIL: two 62 | HINT: three 63 | select * from logs2; 64 | level | appname | errcode | message | detail | detail_log | hint | context | errstate | internalpos | internalquery | remote_host | command_tag | query | query_pos 65 | -------+------------+---------+---------+--------+------------+-------+---------+----------+-------------+---------------+-------------+-------------+--------------------------------------------------------------+----------- 66 | error | pg_regress | 1088 | one | two | | three | | 0A000 | 0 | | [local] | SELECT | select logging.test_ereport('error', 'one', 'two', 'three'); | 0 67 | (1 row) 68 | 69 | /* cleanup */ 70 | select logging.flush_log(); 71 | flush_log 72 | ----------- 73 | 74 | (1 row) 75 | 76 | show pg_logging.minlevel; 77 | pg_logging.minlevel 78 | --------------------- 79 | none 80 | (1 row) 81 | 82 | set pg_logging.minlevel = error; 83 | select logging.test_ereport('error', 'notice1', 'detail', 'hint'); 84 | ERROR: notice1 85 | DETAIL: detail 86 | HINT: hint 87 | select logging.test_ereport('error', 'notice2', 'detail', 'hint'); 88 | ERROR: notice2 89 | DETAIL: detail 90 | HINT: hint 91 | select logging.test_ereport('error', 'notice3', 'detail', 'hint'); 92 | ERROR: notice3 93 | DETAIL: detail 94 | HINT: hint 95 | select level, message, position from logging.get_log(false); 96 | level | message | position 97 | -------+---------+---------- 98 | 20 | notice1 | 0 99 | 20 | notice2 | 268 100 | 20 | notice3 | 536 101 | (3 rows) 102 | 103 | select level, message, position from logging.get_log(268); 104 | level | message | position 105 | -------+---------+---------- 106 | 20 | notice2 | 268 107 | 20 | notice3 | 536 108 | (2 rows) 109 | 110 | select level, message, position from logging.get_log(false); 111 | level | message | position 112 | -------+---------+---------- 113 | 20 | notice2 | 268 114 | 20 | notice3 | 536 115 | (2 rows) 116 | 117 | select level, message, position from logging.get_log(536); 118 | level | message | position 119 | -------+---------+---------- 120 | 20 | notice3 | 536 121 | (1 row) 122 | 123 | select level, message, position from logging.get_log(false); 124 | level | message | position 125 | -------+---------+---------- 126 | 20 | notice3 | 536 127 | (1 row) 128 | 129 | select level, message, position from logging.get_log(1000); 130 | ERROR: nothing with specified position was found 131 | select level, message, position from logging.get_log(false); 132 | level | message | position 133 | -------+-------------------------------------------+---------- 134 | 20 | notice3 | 536 135 | 20 | nothing with specified position was found | 804 136 | (2 rows) 137 | 138 | reset log_statement; 139 | drop extension pg_logging cascade; 140 | NOTICE: drop cascades to 2 other objects 141 | DETAIL: drop cascades to view logs 142 | drop cascades to view logs2 143 | -------------------------------------------------------------------------------- /main.sql: -------------------------------------------------------------------------------- 1 | /* create special type for error levels */ 2 | create type error_level; 3 | 4 | create or replace function errlevel_out(error_level) 5 | returns cstring as 'MODULE_PATHNAME' 6 | language c strict immutable parallel safe; 7 | 8 | create or replace function errlevel_in(cstring) 9 | returns error_level as 'MODULE_PATHNAME' 10 | language c strict immutable parallel safe; 11 | 12 | create type error_level ( 13 | input = errlevel_in, 14 | output = errlevel_out, 15 | internallength = 4, 16 | passedbyvalue, 17 | alignment = int 18 | ); 19 | 20 | create cast (int AS error_level) without function as assignment; 21 | 22 | /* make sure this type is correlated with enum in pg_logging.h */ 23 | create type log_item as ( 24 | log_time timestamp with time zone, 25 | level int, 26 | pid int, 27 | line_num bigint, /* log line number */ 28 | appname text, 29 | start_time timestamp with time zone, /* backend start time */ 30 | datid oid, /* database id */ 31 | errno int, 32 | errcode int, 33 | errstate text, 34 | message text, 35 | detail text, 36 | detail_log text, 37 | hint text, 38 | context text, 39 | context_domain text, 40 | domain text, 41 | internalpos int, 42 | internalquery text, 43 | userid oid, 44 | remote_host text, 45 | command_tag text, 46 | vxid text, /* virtual transaction id */ 47 | txid bigint, /* transaction id */ 48 | query text, 49 | query_pos int, 50 | position int /* position in logs buffer */ 51 | ); 52 | 53 | create or replace function get_log( 54 | flush bool default true 55 | ) 56 | returns log_item as 'MODULE_PATHNAME', 'get_logged_data_flush' 57 | language c; 58 | 59 | create or replace function get_log( 60 | from_position int 61 | ) 62 | returns log_item as 'MODULE_PATHNAME', 'get_logged_data_from' 63 | language c; 64 | 65 | create or replace function flush_log() 66 | returns void as 'MODULE_PATHNAME', 'flush_logged_data' 67 | language c; 68 | 69 | create or replace function test_ereport( 70 | elevel error_level, 71 | message cstring, 72 | detail cstring, 73 | hint cstring 74 | ) 75 | returns void as 'MODULE_PATHNAME', 'test_ereport' 76 | language c; 77 | -------------------------------------------------------------------------------- /pg_logging--0.1--0.2.sql: -------------------------------------------------------------------------------- 1 | drop function get_log(bool); 2 | drop type log_item; 3 | 4 | /* make sure this type is correlated with enum in pg_logging.h */ 5 | create type log_item as ( 6 | log_time timestamp with time zone, 7 | level int, 8 | pid int, 9 | line_num bigint, /* log line number */ 10 | appname text, 11 | start_time timestamp with time zone, /* backend start time */ 12 | datid oid, /* database id */ 13 | errno int, 14 | errcode int, 15 | errstate text, 16 | message text, 17 | detail text, 18 | detail_log text, 19 | hint text, 20 | context text, 21 | context_domain text, 22 | domain text, 23 | internalpos int, 24 | internalquery text, 25 | userid oid, 26 | remote_host text, 27 | command_tag text, 28 | vxid text, /* virtual transaction id */ 29 | txid bigint, /* transaction id */ 30 | query text, 31 | query_pos int, 32 | position int /* position in logs buffer */ 33 | ); 34 | 35 | create function get_log( 36 | flush bool default true, 37 | ) 38 | returns log_item as 'MODULE_PATHNAME', 'get_logged_data' 39 | language c; 40 | 41 | create function get_log( 42 | from_position int 43 | ) 44 | returns log_item as 'MODULE_PATHNAME', 'get_logged_data_from' 45 | language c; 46 | -------------------------------------------------------------------------------- /pg_logging.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pg_logging.c 3 | * PostgreSQL logging interface. 4 | * 5 | * Copyright (c) 2018, Postgres Professional 6 | */ 7 | #include "postgres.h" 8 | #include "access/xact.h" 9 | #include "fmgr.h" 10 | #include "libpq/libpq-be.h" 11 | #include "miscadmin.h" 12 | #include "postmaster/autovacuum.h" 13 | #include "storage/dsm.h" 14 | #include "storage/ipc.h" 15 | #include "storage/shm_mq.h" 16 | #include "storage/shm_toc.h" 17 | #include "string.h" 18 | #include "tcop/tcopprot.h" 19 | #include "utils/builtins.h" 20 | #include "utils/guc.h" 21 | #include "utils/ps_status.h" 22 | 23 | #include "pg_logging.h" 24 | 25 | PG_MODULE_MAGIC; 26 | 27 | void _PG_init(void); 28 | void _PG_fini(void); 29 | 30 | /* global variables */ 31 | int buffer_size_setting = 0; 32 | shm_toc *toc = NULL; 33 | LoggingShmemHdr *hdr = NULL; 34 | bool shmem_initialized = false; 35 | bool buffer_increase_suggested = false; 36 | 37 | static emit_log_hook_type pg_logging_log_hook_next = NULL; 38 | static shmem_startup_hook_type pg_logging_shmem_hook_next = NULL; 39 | 40 | static void pg_logging_log_hook(ErrorData *edata); 41 | static void pg_logging_shmem_hook(void); 42 | 43 | #if PG_VERSION_NUM < 100000 44 | #define USE_STATIC_TRANCHE 45 | LWLockPadded *lwlock_array[1] = {NULL}; 46 | static LWLockTranche LoggingLWLockTranche = {"pg_logging", lwlock_array, sizeof(LWLockPadded)}; 47 | #endif 48 | 49 | enum { 50 | WRAP_NONE, 51 | WRAP_PARTIAL, 52 | WRAP_FULL, 53 | }; 54 | 55 | #define safe_strlen(s) ((s) ? strlen(s) : 0) 56 | 57 | static void 58 | buffer_size_assign_hook(int newval, void *extra) 59 | { 60 | if (!shmem_initialized) 61 | return; 62 | 63 | newval = newval * 1024; 64 | if (newval > hdr->buffer_size_initial) 65 | elog(ERROR, "buffer size cannot be increased"); 66 | 67 | reset_counters_in_shmem(newval); 68 | } 69 | 70 | static const struct config_enum_entry level_options[] = { 71 | {"none", 0, true}, 72 | {"debug", DEBUG2, true}, 73 | {"debug5", DEBUG5, false}, 74 | {"debug4", DEBUG4, false}, 75 | {"debug3", DEBUG3, false}, 76 | {"debug2", DEBUG2, false}, 77 | {"debug1", DEBUG1, false}, 78 | {"log", LOG, false}, 79 | {"info", INFO, true}, 80 | {"notice", NOTICE, false}, 81 | {"warning", WARNING, false}, 82 | {"error", ERROR, false}, 83 | {"fatal", FATAL, false}, 84 | {"panic", PANIC, false}, 85 | {NULL, 0, false} 86 | }; 87 | 88 | static void 89 | setup_gucs(bool basic) 90 | { 91 | if (basic) 92 | { 93 | DefineCustomIntVariable( 94 | "pg_logging.buffer_size", 95 | "Sets size of the ring buffer used to keep logs", NULL, 96 | &buffer_size_setting, 97 | 1024 * 10, /* 10MB */ 98 | 1024, 99 | INT_MAX, 100 | PGC_SUSET, 101 | GUC_UNIT_KB, 102 | NULL, buffer_size_assign_hook, NULL 103 | ); 104 | } 105 | else 106 | { 107 | DefineCustomBoolVariable( 108 | "pg_logging.enabled", 109 | "Enable ring buffer for logs", NULL, 110 | &hdr->logging_enabled, 111 | true, 112 | PGC_SUSET, 113 | 0, NULL, NULL, NULL 114 | ); 115 | 116 | DefineCustomBoolVariable( 117 | "pg_logging.ignore_statements", 118 | "Skip the lines generated by \"log_statement=all\"", NULL, 119 | &hdr->ignore_statements, 120 | false, 121 | PGC_SUSET, 122 | 0, NULL, NULL, NULL 123 | ); 124 | 125 | DefineCustomBoolVariable( 126 | "pg_logging.set_query_fields", 127 | "Set query and query_pos fields", NULL, 128 | &hdr->set_query_fields, 129 | true, 130 | PGC_SUSET, 131 | 0, NULL, NULL, NULL 132 | ); 133 | 134 | DefineCustomEnumVariable( 135 | "pg_logging.minlevel", 136 | "Set minimal log level to catch", 137 | NULL, 138 | &hdr->minlevel, 139 | 0, 140 | level_options, 141 | PGC_SUSET, 142 | 0, NULL, NULL, NULL 143 | ); 144 | } 145 | } 146 | 147 | static void 148 | install_hooks(void) 149 | { 150 | pg_logging_log_hook_next = emit_log_hook; 151 | emit_log_hook = pg_logging_log_hook; 152 | pg_logging_shmem_hook_next = shmem_startup_hook; 153 | shmem_startup_hook = pg_logging_shmem_hook; 154 | } 155 | 156 | static void 157 | uninstall_hooks(void) 158 | { 159 | emit_log_hook = pg_logging_log_hook_next; 160 | shmem_startup_hook = pg_logging_shmem_hook_next; 161 | } 162 | 163 | static char * 164 | add_block(char *data, const char *block, int bytes_cp) 165 | { 166 | uint32 endpos = data - hdr->data; 167 | 168 | Assert(bytes_cp < hdr->buffer_size); 169 | 170 | if (bytes_cp) 171 | { 172 | if (bytes_cp < hdr->buffer_size - endpos) 173 | { 174 | /* enough place to put */ 175 | memcpy(data, block, bytes_cp); 176 | endpos += bytes_cp; 177 | if (endpos == hdr->buffer_size) 178 | endpos = 0; 179 | } 180 | else 181 | { 182 | /* should add by two parts */ 183 | int size1 = hdr->buffer_size - endpos; 184 | int size2 = bytes_cp - size1; 185 | 186 | memcpy(data, block, size1); 187 | memcpy(hdr->data, (char *) block + size1, size2); 188 | endpos = size2; 189 | } 190 | } 191 | 192 | return hdr->data + endpos; 193 | } 194 | 195 | static int 196 | push_reading_position(int readpos, bool *wrapped) 197 | { 198 | CollectedItem *item; 199 | 200 | *wrapped = false; 201 | if (readpos + ITEM_HDR_LEN > hdr->buffer_size) 202 | { 203 | readpos = 0; 204 | *wrapped = true; 205 | 206 | #ifdef CHECK_DATA 207 | Assert(((CollectedItem *) hdr->data)->magic == PG_ITEM_MAGIC); 208 | #endif 209 | 210 | } 211 | else 212 | { 213 | int tail; 214 | 215 | item = (CollectedItem *) (hdr->data + readpos); 216 | #ifdef CHECK_DATA 217 | Assert(item->magic == PG_ITEM_MAGIC); 218 | #endif 219 | readpos += item->totallen; 220 | tail = readpos - hdr->buffer_size; 221 | if (tail > 0) { 222 | readpos = tail; 223 | *wrapped = true; 224 | } 225 | } 226 | if (!buffer_increase_suggested) 227 | { 228 | fprintf(stderr, "CONSIDER INCREASING PG_LOGGING BUFFER"); 229 | buffer_increase_suggested = true; 230 | } 231 | 232 | return readpos; 233 | } 234 | 235 | static void 236 | copy_error_data_to_shmem(ErrorData *edata) 237 | { 238 | #define ADD_STRING(totallen, string_len, string) \ 239 | (totallen) += ((string_len) = safe_strlen(string)) 240 | 241 | static bool log_in_process = false; 242 | static uint64 log_line_number = 0; 243 | char *data; 244 | int wrap = WRAP_NONE; 245 | CollectedItem item; 246 | uint32 endpos, 247 | curpos, 248 | savedpos; 249 | const char *psdisp = NULL, 250 | *remote_host = NULL; 251 | char vxidbuf[128]; 252 | 253 | /* don't allow recursive logs or quit if logs are disabled */ 254 | if (log_in_process || !hdr->logging_enabled) 255 | return; 256 | 257 | if (hdr->ignore_statements && edata->hide_stmt) 258 | return; 259 | 260 | if (hdr->minlevel && edata->elevel < hdr->minlevel) 261 | return; 262 | 263 | log_in_process = true; 264 | 265 | /* calculate length */ 266 | #ifdef CHECK_DATA 267 | item.magic = PG_ITEM_MAGIC; 268 | #endif 269 | item.logtime = GetCurrentTimestamp(); 270 | item.totallen = ITEM_HDR_LEN; 271 | item.elevel = edata->elevel; 272 | item.saved_errno = edata->saved_errno; 273 | item.sqlerrcode = edata->sqlerrcode; 274 | item.ppid = MyProcPid; 275 | item.database_id = MyDatabaseId; 276 | item.internalpos = edata->internalpos; 277 | item.log_line_number = ++log_line_number; 278 | item.remote_host_len = 0; 279 | item.command_tag_len = 0; 280 | item.session_start_time = 0; 281 | 282 | if (MyBackendId != InvalidBackendId && !IsAutoVacuumLauncherProcess() && 283 | !IsAutoVacuumWorkerProcess()) 284 | item.user_id = GetSessionUserId(); 285 | else 286 | item.user_id = InvalidOid; 287 | 288 | /* transaction */ 289 | item.txid = GetTopTransactionIdIfAny(); 290 | item.vxid_len = 0; 291 | 292 | if (MyProc != NULL && MyProc->backendId != InvalidBackendId) 293 | { 294 | #ifdef XID_FMT 295 | snprintf(vxidbuf, sizeof(vxidbuf) - 1, "%d/" XID_FMT, 296 | MyProc->backendId, MyProc->lxid); 297 | #else 298 | snprintf(vxidbuf, sizeof(vxidbuf) - 1, "%d/%u", 299 | MyProc->backendId, MyProc->lxid); 300 | #endif 301 | item.totallen += (item.vxid_len = strlen(vxidbuf)); 302 | } 303 | 304 | if (MyProcPort) 305 | { 306 | int displen = 0; 307 | 308 | /* command tag */ 309 | psdisp = get_ps_display(&displen); 310 | item.command_tag_len = displen; 311 | item.totallen += item.command_tag_len; 312 | 313 | /* remote host */ 314 | remote_host = MyProcPort->remote_host; 315 | ADD_STRING(item.totallen, item.remote_host_len, remote_host); 316 | 317 | /* session start time */ 318 | item.session_start_time = MyProcPort->SessionStartTime; 319 | } 320 | 321 | item.query_pos = 0; 322 | item.query_len = 0; 323 | if (hdr->set_query_fields && debug_query_string != NULL) 324 | { 325 | item.query_pos = edata->cursorpos; 326 | ADD_STRING(item.totallen, item.query_len, debug_query_string); 327 | } 328 | 329 | ADD_STRING(item.totallen, item.message_len, edata->message); 330 | ADD_STRING(item.totallen, item.detail_len, edata->detail); 331 | ADD_STRING(item.totallen, item.detail_log_len, edata->detail_log); 332 | ADD_STRING(item.totallen, item.hint_len, edata->hint); 333 | ADD_STRING(item.totallen, item.context_len, edata->context); 334 | ADD_STRING(item.totallen, item.domain_len, edata->domain); 335 | ADD_STRING(item.totallen, item.context_domain_len, edata->context_domain); 336 | ADD_STRING(item.totallen, item.appname_len, application_name); 337 | ADD_STRING(item.totallen, item.internalquery_len, edata->internalquery); 338 | ADD_STRING(item.totallen, item.errstate_len, unpack_sql_state(edata->sqlerrcode)); 339 | item.totallen = INTALIGN(item.totallen); 340 | 341 | /* 342 | * Find the place to put the block. 343 | * First check the header, it there is not enough place for the header 344 | * move to the beginning. 345 | * Then check the place with the data and calculate the position 346 | * according to data length. 347 | * 348 | * We use reading position here from written data and have to use lock. 349 | * Also reading position will be moved forward if needed. 350 | */ 351 | HDR_LOCK(); 352 | savedpos = curpos = hdr->endpos; 353 | 354 | if (savedpos + ITEM_HDR_LEN > hdr->buffer_size) 355 | { 356 | savedpos = 0; 357 | endpos = item.totallen; 358 | wrap = WRAP_FULL; 359 | } 360 | else 361 | { 362 | endpos = savedpos + item.totallen; 363 | if (endpos >= hdr->buffer_size) 364 | { 365 | endpos = endpos - hdr->buffer_size; 366 | wrap = WRAP_PARTIAL; 367 | } 368 | } 369 | hdr->endpos = endpos; 370 | 371 | if (wrap) 372 | { 373 | bool rwrap = false; 374 | 375 | hdr->wraparound = true; 376 | if (wrap == WRAP_PARTIAL && savedpos < hdr->readpos) 377 | { 378 | while (!rwrap) 379 | hdr->readpos = push_reading_position(hdr->readpos, &rwrap); 380 | } 381 | 382 | rwrap = false; 383 | while (hdr->readpos < endpos) 384 | hdr->readpos = push_reading_position(hdr->readpos, &rwrap); 385 | Assert(!rwrap); 386 | } 387 | else if (hdr->wraparound) 388 | { 389 | bool rwrap = false; 390 | 391 | while (hdr->readpos < endpos) 392 | { 393 | hdr->readpos = push_reading_position(hdr->readpos, &rwrap); 394 | if (rwrap) 395 | { 396 | hdr->wraparound = false; 397 | break; 398 | } 399 | } 400 | } 401 | HDR_RELEASE(); 402 | 403 | if (hdr->endpos >= savedpos && hdr->endpos < endpos) 404 | { 405 | /* should not happen if the buffer is large enough */ 406 | log_in_process = false; 407 | elog(LOG, "pg_logging buffer overflow"); 408 | } 409 | else 410 | { 411 | data = hdr->data + savedpos; 412 | Assert(data < (hdr->data + hdr->buffer_size)); 413 | memcpy(data, &item, ITEM_HDR_LEN); 414 | data += ITEM_HDR_LEN; 415 | 416 | /* ordering is important, look pl_funcs.c !!! */ 417 | data = add_block(data, edata->message, item.message_len); 418 | data = add_block(data, edata->detail, item.detail_len); 419 | data = add_block(data, edata->detail_log, item.detail_log_len); 420 | data = add_block(data, edata->hint, item.hint_len); 421 | data = add_block(data, edata->context, item.context_len); 422 | data = add_block(data, edata->domain, item.domain_len); 423 | data = add_block(data, edata->context_domain, item.context_domain_len); 424 | data = add_block(data, edata->internalquery, item.internalquery_len); 425 | data = add_block(data, unpack_sql_state(edata->sqlerrcode), item.errstate_len); 426 | data = add_block(data, application_name, item.appname_len); 427 | data = add_block(data, remote_host, item.remote_host_len); 428 | data = add_block(data, psdisp, item.command_tag_len); 429 | data = add_block(data, vxidbuf, item.vxid_len); 430 | add_block(data, debug_query_string, item.query_len); 431 | log_in_process = false; 432 | } 433 | } 434 | 435 | static void 436 | pg_logging_log_hook(ErrorData *edata) 437 | { 438 | if (pg_logging_log_hook_next) 439 | pg_logging_log_hook_next(edata); 440 | 441 | if (shmem_initialized && MyProc != NULL && !proc_exit_inprogress) 442 | copy_error_data_to_shmem(edata); 443 | } 444 | 445 | static Size 446 | pg_logging_shmem_size(int bufsize) 447 | { 448 | shm_toc_estimator e; 449 | Size size; 450 | 451 | Assert(bufsize != 0); 452 | shm_toc_initialize_estimator(&e); 453 | shm_toc_estimate_chunk(&e, sizeof(LoggingShmemHdr)); 454 | shm_toc_estimate_chunk(&e, bufsize); 455 | shm_toc_estimate_keys(&e, 2); 456 | size = shm_toc_estimate(&e); 457 | 458 | return size; 459 | } 460 | 461 | static void 462 | pg_logging_shmem_hook(void) 463 | { 464 | bool found; 465 | Size bufsize = INTALIGN(buffer_size_setting * 1024); 466 | Size segsize = pg_logging_shmem_size(bufsize); 467 | void *addr; 468 | 469 | addr = ShmemInitStruct("pg_logging", segsize, &found); 470 | if (!found) 471 | { 472 | int tranche_id = LWLockNewTrancheId(); 473 | 474 | toc = shm_toc_create(PG_LOGGING_MAGIC, addr, segsize); 475 | 476 | hdr = shm_toc_allocate(toc, sizeof(LoggingShmemHdr)); 477 | hdr->buffer_size = bufsize; 478 | hdr->buffer_size_initial = bufsize; 479 | hdr->endpos = 0; 480 | hdr->readpos = 0; 481 | 482 | /* initialize buffer lwlock */ 483 | #ifdef USE_STATIC_TRANCHE 484 | lwlock_array[0] = &hdr->hdr_lock; 485 | LWLockRegisterTranche(tranche_id, &LoggingLWLockTranche); 486 | #else 487 | LWLockRegisterTranche(tranche_id, "pg_logging tranche"); 488 | #endif 489 | LWLockInitialize(&hdr->hdr_lock.lock, tranche_id); 490 | 491 | shm_toc_insert(toc, 0, hdr); 492 | hdr->data = shm_toc_allocate(toc, hdr->buffer_size); 493 | shm_toc_insert(toc, 1, hdr->data); 494 | 495 | setup_gucs(false); 496 | } 497 | else 498 | { 499 | toc = shm_toc_attach(PG_LOGGING_MAGIC, addr); 500 | #if PG_VERSION_NUM >= 100000 501 | hdr = shm_toc_lookup(toc, 0, false); 502 | #else 503 | hdr = shm_toc_lookup(toc, 0); 504 | #endif 505 | } 506 | 507 | shmem_initialized = true; 508 | 509 | if (pg_logging_shmem_hook_next) 510 | pg_logging_shmem_hook_next(); 511 | 512 | elog(LOG, "pg_logging initialized"); 513 | } 514 | 515 | /* 516 | * Module load callback 517 | */ 518 | void 519 | _PG_init(void) 520 | { 521 | Size bufsize; 522 | Size segsize; 523 | 524 | if (!process_shared_preload_libraries_in_progress) 525 | { 526 | ereport(ERROR, 527 | (errmsg("pg_logging module must be initialized in postmaster."), 528 | errhint("add pg_logging to shared_preload_libraries parameter in postgresql.conf"))); 529 | } 530 | 531 | setup_gucs(true); 532 | install_hooks(); 533 | 534 | bufsize = INTALIGN(buffer_size_setting * 1024); 535 | segsize = pg_logging_shmem_size(bufsize); 536 | 537 | RequestAddinShmemSpace(segsize); 538 | } 539 | 540 | /* 541 | * Module unload callback 542 | */ 543 | void 544 | _PG_fini(void) 545 | { 546 | uninstall_hooks(); 547 | } 548 | -------------------------------------------------------------------------------- /pg_logging.control: -------------------------------------------------------------------------------- 1 | comment = 'PostgreSQL logging interface' 2 | default_version = '0.2' 3 | module_pathname = '$libdir/pg_logging' 4 | relocatable = true 5 | -------------------------------------------------------------------------------- /pg_logging.h: -------------------------------------------------------------------------------- 1 | #ifndef PG_LOGGING_H 2 | #define PG_LOGGING_H 3 | 4 | #include "postgres.h" 5 | #include "pg_config.h" 6 | #include "storage/lwlock.h" 7 | #include "utils/timestamp.h" 8 | 9 | #define CHECK_DATA 10 | 11 | #if PG_VERSION_NUM < 90600 12 | #error "pg_logging support only postgres starting from 9.6" 13 | #endif 14 | 15 | typedef enum ItemObjectType { 16 | IOT_NONE, 17 | IOT_TABLE, 18 | IOT_COLUMN, 19 | IOT_DATATYPE, 20 | IOT_CONSTRAINT 21 | } ItemObjectType; 22 | 23 | /* CollectedItem contains offsets in saved block */ 24 | typedef struct CollectedItem 25 | { 26 | #ifdef CHECK_DATA 27 | int magic; 28 | #endif 29 | int totallen; /* size of this block */ 30 | 31 | TimestampTz logtime; 32 | TimestampTz session_start_time; 33 | 34 | int elevel; /* error level */ 35 | int saved_errno; /* errno at entry */ 36 | int sqlerrcode; /* encoded ERRSTATE */ 37 | 38 | /* text lengths in data block */ 39 | int message_len; 40 | int detail_len; /* errdetail */ 41 | int detail_log_len; 42 | int hint_len; /* errhint */ 43 | int context_len; 44 | int domain_len; 45 | int context_domain_len; 46 | int command_tag_len; 47 | int remote_host_len; 48 | int errstate_len; 49 | 50 | /* query data */ 51 | int query_len; 52 | int query_pos; 53 | int internalpos; 54 | int internalquery_len; 55 | 56 | /* extra data */ 57 | int ppid; 58 | int appname_len; 59 | Oid database_id; 60 | int user_id; 61 | uint64 log_line_number; 62 | 63 | /* transaction info */ 64 | int vxid_len; 65 | TransactionId txid; 66 | 67 | /* texts are contained here */ 68 | char data[FLEXIBLE_ARRAY_MEMBER]; 69 | } CollectedItem; 70 | 71 | #define ITEM_HDR_LEN (offsetof(CollectedItem, data)) 72 | 73 | typedef struct LoggingShmemHdr 74 | { 75 | char *data; 76 | volatile uint32 readpos; 77 | volatile uint32 endpos; 78 | int buffer_size; /* total size of buffer */ 79 | int buffer_size_initial; /* initial size of buffer */ 80 | LWLockPadded hdr_lock; 81 | bool wraparound; 82 | 83 | /* gucs */ 84 | bool logging_enabled; 85 | bool ignore_statements; 86 | bool set_query_fields; 87 | int minlevel; 88 | } LoggingShmemHdr; 89 | 90 | #define HDR_LOCK() ( LWLockAcquire(&hdr->hdr_lock.lock, LW_EXCLUSIVE) ) 91 | #define HDR_RELEASE() ( LWLockRelease(&hdr->hdr_lock.lock) ) 92 | 93 | struct ErrorLevel { 94 | char *text; 95 | int code; 96 | }; 97 | 98 | #define PG_LOGGING_MAGIC 0xAABBCCDD 99 | #define PG_ITEM_MAGIC 0x06054AB5 100 | 101 | // view attributes, make sure it's equal to log_item type from sql 102 | enum { 103 | Anum_pg_logging_logtime = 1, 104 | Anum_pg_logging_level, 105 | Anum_pg_logging_pid, 106 | Anum_pg_logging_line_num, 107 | Anum_pg_logging_appname, 108 | Anum_pg_logging_start_time, 109 | Anum_pg_logging_datid, 110 | Anum_pg_logging_errno, 111 | Anum_pg_logging_errcode, 112 | Anum_pg_logging_errstate, 113 | Anum_pg_logging_message, 114 | Anum_pg_logging_detail, 115 | Anum_pg_logging_detail_log, 116 | Anum_pg_logging_hint, 117 | Anum_pg_logging_context, 118 | Anum_pg_logging_context_domain, 119 | Anum_pg_logging_domain, 120 | Anum_pg_logging_internalpos, 121 | Anum_pg_logging_internalquery, 122 | Anum_pg_logging_userid, 123 | Anum_pg_logging_remote_host, 124 | Anum_pg_logging_command_tag, 125 | Anum_pg_logging_vxid, 126 | Anum_pg_logging_txid, 127 | Anum_pg_logging_query, 128 | Anum_pg_logging_query_pos, 129 | Anum_pg_logging_position, 130 | 131 | Natts_pg_logging_data 132 | }; 133 | 134 | extern struct ErrorLevel errlevel_wordlist[]; 135 | extern LoggingShmemHdr *hdr; 136 | 137 | void reset_counters_in_shmem(int buffer_size); 138 | struct ErrorLevel *get_errlevel (register const char *str, register size_t len); 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /pl_funcs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pg_logging.c 3 | * PostgreSQL logging interface. 4 | * 5 | * Copyright (c) 2018, Postgres Professional 6 | */ 7 | #include "postgres.h" 8 | #include "funcapi.h" 9 | #include "utils/builtins.h" 10 | #include "access/htup_details.h" 11 | 12 | #if PG_VERSION_NUM >= 110000 13 | #include "catalog/pg_type_d.h" 14 | #else 15 | #include "catalog/pg_type.h" 16 | #endif 17 | 18 | #include "pg_logging.h" 19 | 20 | PG_FUNCTION_INFO_V1( get_logged_data_flush ); 21 | PG_FUNCTION_INFO_V1( get_logged_data_from ); 22 | PG_FUNCTION_INFO_V1( flush_logged_data ); 23 | PG_FUNCTION_INFO_V1( test_ereport ); 24 | PG_FUNCTION_INFO_V1( errlevel_in ); 25 | PG_FUNCTION_INFO_V1( errlevel_out ); 26 | PG_FUNCTION_INFO_V1( errlevel_eq ); 27 | 28 | typedef struct { 29 | uint32 until; 30 | uint32 reading_pos; 31 | bool wraparound; 32 | bool flush; 33 | int from; 34 | bool found; 35 | } logged_data_ctx; 36 | 37 | static char * 38 | get_errlevel_name(int code) 39 | { 40 | int i; 41 | for (i = 0; i <= 21 /* MAX_HASH_VALUE */; i++) 42 | { 43 | struct ErrorLevel *el = &errlevel_wordlist[i]; 44 | if (el->text != NULL && el->code == code) 45 | return el->text; 46 | } 47 | elog(ERROR, "Invalid error level name"); 48 | } 49 | 50 | void 51 | reset_counters_in_shmem(int buffer_size) 52 | { 53 | HDR_LOCK(); 54 | hdr->endpos = 0; 55 | hdr->readpos = 0; 56 | hdr->wraparound = false; 57 | if (buffer_size > 0) 58 | hdr->buffer_size = buffer_size; 59 | HDR_RELEASE(); 60 | } 61 | 62 | Datum 63 | flush_logged_data(PG_FUNCTION_ARGS) 64 | { 65 | reset_counters_in_shmem(0); 66 | PG_RETURN_VOID(); 67 | } 68 | 69 | enum call_type 70 | { 71 | ct_flush, 72 | ct_from 73 | }; 74 | 75 | static Datum 76 | get_logged_data(PG_FUNCTION_ARGS, enum call_type ctype) 77 | { 78 | MemoryContext old_mcxt; 79 | FuncCallContext *funccxt; 80 | logged_data_ctx *usercxt; 81 | 82 | if (SRF_IS_FIRSTCALL()) 83 | { 84 | TupleDesc tupdesc; 85 | 86 | funccxt = SRF_FIRSTCALL_INIT(); 87 | 88 | old_mcxt = MemoryContextSwitchTo(funccxt->multi_call_memory_ctx); 89 | 90 | HDR_LOCK(); 91 | usercxt = (logged_data_ctx *) palloc(sizeof(logged_data_ctx)); 92 | usercxt->until = hdr->endpos; 93 | usercxt->reading_pos = hdr->readpos; 94 | usercxt->wraparound = hdr->wraparound; 95 | usercxt->flush = false; 96 | usercxt->from = -1; 97 | 98 | switch (ctype) 99 | { 100 | case ct_flush: 101 | usercxt->flush = PG_GETARG_BOOL(0); 102 | break; 103 | case ct_from: 104 | usercxt->from = PG_GETARG_INT32(0); 105 | break; 106 | } 107 | usercxt->found = false; 108 | 109 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 110 | elog(ERROR, "return type must be a row type"); 111 | 112 | funccxt->tuple_desc = BlessTupleDesc(tupdesc); 113 | funccxt->user_fctx = (void *) usercxt; 114 | 115 | MemoryContextSwitchTo(old_mcxt); 116 | } 117 | 118 | funccxt = SRF_PERCALL_SETUP(); 119 | usercxt = (logged_data_ctx *) funccxt->user_fctx; 120 | 121 | pg_read_barrier(); 122 | 123 | while ((!usercxt->wraparound && usercxt->reading_pos < usercxt->until) || 124 | (usercxt->wraparound && usercxt->reading_pos > usercxt->until)) 125 | { 126 | CollectedItem *item; 127 | char *data; 128 | HeapTuple htup; 129 | Datum values[Natts_pg_logging_data]; 130 | bool isnull[Natts_pg_logging_data]; 131 | bool skip = false; 132 | int curpos; 133 | 134 | if (usercxt->reading_pos + ITEM_HDR_LEN > hdr->buffer_size) 135 | { 136 | usercxt->reading_pos = 0; 137 | usercxt->wraparound = false; 138 | continue; 139 | } 140 | 141 | if (usercxt->from > 0) 142 | { 143 | if (usercxt->reading_pos != usercxt->from) 144 | skip = true; 145 | else 146 | { 147 | usercxt->found = true; 148 | usercxt->from = -1; 149 | 150 | /* next time this position will be first */ 151 | hdr->readpos = usercxt->reading_pos; 152 | hdr->wraparound = usercxt->wraparound; 153 | } 154 | } 155 | 156 | data = (char *) (hdr->data + usercxt->reading_pos); 157 | curpos = usercxt->reading_pos; 158 | AssertPointerAlignment(data, 4); 159 | 160 | /* 161 | * careful here, we point to the buffer first, then allocate a 162 | * block using information from buffer 163 | */ 164 | item = (CollectedItem *) data; 165 | Assert(item->totallen < hdr->buffer_size); 166 | 167 | /* Make calculations like below but without copying */ 168 | if (skip) 169 | { 170 | if (usercxt->reading_pos + item->totallen >= hdr->buffer_size) 171 | { 172 | /* two parts */ 173 | usercxt->wraparound = false; 174 | usercxt->reading_pos += item->totallen; 175 | usercxt->reading_pos = usercxt->reading_pos - hdr->buffer_size; 176 | } 177 | else 178 | { 179 | /* one part */ 180 | usercxt->reading_pos += item->totallen; 181 | } 182 | 183 | continue; 184 | } 185 | 186 | item = (CollectedItem *) palloc0(item->totallen); 187 | memcpy(item, data, offsetof(CollectedItem, data)); 188 | data += ITEM_HDR_LEN; 189 | 190 | if (usercxt->reading_pos + item->totallen >= hdr->buffer_size) 191 | { 192 | /* two parts */ 193 | int taillen = hdr->buffer_size - usercxt->reading_pos - ITEM_HDR_LEN; 194 | memcpy(item->data, data, taillen); 195 | usercxt->wraparound = false; 196 | usercxt->reading_pos += item->totallen; 197 | usercxt->reading_pos = usercxt->reading_pos - hdr->buffer_size; 198 | memcpy(item->data + taillen, hdr->data, usercxt->reading_pos); 199 | } 200 | else 201 | { 202 | /* one part */ 203 | memcpy(item->data, data, item->totallen - ITEM_HDR_LEN); 204 | usercxt->reading_pos += item->totallen; 205 | } 206 | 207 | MemSet(values, 0, sizeof(values)); 208 | MemSet(isnull, 0, sizeof(isnull)); 209 | 210 | values[Anum_pg_logging_logtime - 1] = TimestampTzGetDatum(item->logtime); 211 | 212 | if (item->session_start_time) 213 | values[Anum_pg_logging_start_time - 1] = TimestampTzGetDatum(item->session_start_time); 214 | else 215 | isnull[Anum_pg_logging_start_time - 1] = true; 216 | 217 | values[Anum_pg_logging_level - 1] = Int32GetDatum(item->elevel); 218 | values[Anum_pg_logging_errno - 1] = Int32GetDatum(item->saved_errno); 219 | values[Anum_pg_logging_errcode - 1] = Int32GetDatum(item->sqlerrcode); 220 | values[Anum_pg_logging_datid - 1] = Int32GetDatum(item->database_id); 221 | values[Anum_pg_logging_pid - 1] = Int32GetDatum(item->ppid); 222 | values[Anum_pg_logging_line_num - 1] = Int64GetDatum(item->log_line_number); 223 | values[Anum_pg_logging_internalpos - 1] = Int32GetDatum(item->internalpos); 224 | values[Anum_pg_logging_query_pos - 1] = Int32GetDatum(item->query_pos); 225 | values[Anum_pg_logging_position - 1] = Int32GetDatum(curpos); 226 | 227 | if (TransactionIdIsValid(item->txid)) 228 | values[Anum_pg_logging_txid - 1] = TransactionIdGetDatum(item->txid); 229 | else 230 | isnull[Anum_pg_logging_txid - 1] = true; 231 | 232 | if (OidIsValid(item->user_id)) 233 | values[Anum_pg_logging_userid - 1] = ObjectIdGetDatum(item->user_id); 234 | else 235 | isnull[Anum_pg_logging_userid - 1] = true; 236 | 237 | data = item->data; 238 | #define EXTRACT_VAL_TO(attnum, len) \ 239 | do { \ 240 | if (len) { \ 241 | text *ct = cstring_to_text_with_len(data, len); \ 242 | values[(attnum) - 1] = PointerGetDatum(ct); \ 243 | data += (len); \ 244 | } \ 245 | else isnull[(attnum) - 1] = true; \ 246 | } while (0); 247 | 248 | /* ordering is important, look pg_logging.c !! */ 249 | EXTRACT_VAL_TO(Anum_pg_logging_message, item->message_len); 250 | EXTRACT_VAL_TO(Anum_pg_logging_detail, item->detail_len); 251 | EXTRACT_VAL_TO(Anum_pg_logging_detail_log, item->detail_log_len); 252 | EXTRACT_VAL_TO(Anum_pg_logging_hint, item->hint_len); 253 | EXTRACT_VAL_TO(Anum_pg_logging_context, item->context_len); 254 | EXTRACT_VAL_TO(Anum_pg_logging_domain, item->domain_len); 255 | EXTRACT_VAL_TO(Anum_pg_logging_context_domain, item->context_domain_len); 256 | EXTRACT_VAL_TO(Anum_pg_logging_internalquery, item->internalquery_len); 257 | EXTRACT_VAL_TO(Anum_pg_logging_errstate, item->errstate_len); 258 | EXTRACT_VAL_TO(Anum_pg_logging_appname, item->appname_len); 259 | EXTRACT_VAL_TO(Anum_pg_logging_remote_host, item->remote_host_len); 260 | EXTRACT_VAL_TO(Anum_pg_logging_command_tag, item->command_tag_len); 261 | EXTRACT_VAL_TO(Anum_pg_logging_vxid, item->vxid_len); 262 | EXTRACT_VAL_TO(Anum_pg_logging_query, item->query_len); 263 | 264 | /* form output tuple */ 265 | htup = heap_form_tuple(funccxt->tuple_desc, values, isnull); 266 | pfree(item); 267 | 268 | SRF_RETURN_NEXT(funccxt, HeapTupleGetDatum(htup)); 269 | } 270 | 271 | if (ctype == ct_from && !usercxt->found) 272 | { 273 | HDR_RELEASE(); 274 | elog(ERROR, "nothing with specified position was found"); 275 | } 276 | 277 | if (usercxt->flush) 278 | { 279 | hdr->readpos = usercxt->reading_pos; 280 | hdr->wraparound = false; 281 | } 282 | 283 | HDR_RELEASE(); 284 | SRF_RETURN_DONE(funccxt); 285 | } 286 | 287 | Datum 288 | get_logged_data_flush(PG_FUNCTION_ARGS) 289 | { 290 | return get_logged_data(fcinfo, ct_flush); 291 | } 292 | 293 | Datum 294 | get_logged_data_from(PG_FUNCTION_ARGS) 295 | { 296 | return get_logged_data(fcinfo, ct_from); 297 | } 298 | 299 | Datum 300 | test_ereport(PG_FUNCTION_ARGS) 301 | { 302 | ereport(PG_GETARG_INT32(0), 303 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 304 | errmsg("%s", PG_GETARG_CSTRING(1)), 305 | errdetail("%s", PG_GETARG_CSTRING(2)), 306 | errhint("%s", PG_GETARG_CSTRING(3)))); 307 | PG_RETURN_VOID(); 308 | } 309 | 310 | Datum 311 | errlevel_out(PG_FUNCTION_ARGS) 312 | { 313 | PG_RETURN_CSTRING(get_errlevel_name(PG_GETARG_INT32(0))); 314 | } 315 | 316 | static char * 317 | lowerstr(char *str) 318 | { 319 | int i; 320 | char *res = pstrdup(str); 321 | 322 | for (i = 0; str[i]; i++) 323 | { 324 | res[i] = tolower(str[i]); 325 | } 326 | 327 | return res; 328 | } 329 | 330 | Datum 331 | errlevel_in(PG_FUNCTION_ARGS) 332 | { 333 | char *str = lowerstr(PG_GETARG_CSTRING(0)); 334 | int len = strlen(str); 335 | struct ErrorLevel *el; 336 | 337 | if (len == 0) 338 | elog(ERROR, "Empty status name"); 339 | 340 | el = get_errlevel(str, len); 341 | if (!el) 342 | elog(ERROR, "Unknown level name: %s", str); 343 | 344 | PG_RETURN_INT32(el->code); 345 | } 346 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is a main testing script for: 4 | # * regression tests 5 | # * testgres-based tests 6 | # * cmocka-based tests 7 | # Copyright (c) 2017, Postgres Professional 8 | 9 | set -eux 10 | 11 | echo CHECK_CODE=$CHECK_CODE 12 | 13 | status=0 14 | 15 | # perform code analysis if necessary 16 | if [ "$CHECK_CODE" = "clang" ]; then 17 | scan-build --status-bugs make USE_PGXS=1 || status=$? 18 | exit $status 19 | fi 20 | 21 | # don't forget to "make clean" 22 | make USE_PGXS=1 clean 23 | 24 | # initialize database 25 | initdb 26 | 27 | # build extension 28 | make USE_PGXS=1 install 29 | 30 | # check build 31 | status=$? 32 | if [ $status -ne 0 ]; then exit $status; fi 33 | 34 | # add pg_pathman to shared_preload_libraries and restart cluster 'test' 35 | echo "shared_preload_libraries = 'pg_logging'" >> $PGDATA/postgresql.conf 36 | echo "port = 55435" >> $PGDATA/postgresql.conf 37 | pg_ctl start -l /tmp/postgres.log -w 38 | 39 | # check startup 40 | status=$? 41 | if [ $status -ne 0 ]; then cat /tmp/postgres.log; fi 42 | 43 | # run regression tests 44 | export PG_REGRESS_DIFF_OPTS="-w -U3" # for alpine's diff (BusyBox) 45 | PGPORT=55435 make USE_PGXS=1 installcheck || status=$? 46 | 47 | # show diff if it exists 48 | if test -f regression.diffs; then cat regression.diffs; fi 49 | 50 | exit $status 51 | -------------------------------------------------------------------------------- /sql/basic.sql: -------------------------------------------------------------------------------- 1 | create schema logging; 2 | create extension pg_logging schema logging; 3 | 4 | set log_statement=none; 5 | set pg_logging.buffer_size = 2000; 6 | 7 | select logging.flush_log(); 8 | 9 | create view logs as 10 | select level, appname, errcode, 11 | message, detail, detail_log, hint, context, 12 | errstate, internalpos, internalquery, remote_host, command_tag, 13 | query, query_pos 14 | from logging.get_log(); 15 | 16 | create view logs2 as 17 | select level::logging.error_level, appname, errcode, 18 | message, detail, detail_log, hint, context, 19 | errstate, internalpos, internalquery, remote_host, command_tag, 20 | query, query_pos 21 | from logging.get_log(); 22 | 23 | select * from logs; 24 | 25 | select 1/0; 26 | select 'aaaaa'::int; 27 | select * from logs; 28 | 29 | select repeat('aaaaaaaaa', 20)::int; 30 | select repeat('aaaaaaaaa', 20)::int; 31 | select repeat('aaaaaaaaa', 20)::int; 32 | select repeat('aaaaaaaaa', 20)::int; 33 | select * from logs; 34 | 35 | select logging.test_ereport('error', 'one', 'two', 'three'); 36 | select * from logs2; 37 | 38 | /* cleanup */ 39 | select logging.flush_log(); 40 | 41 | show pg_logging.minlevel; 42 | set pg_logging.minlevel = error; 43 | 44 | select logging.test_ereport('error', 'notice1', 'detail', 'hint'); 45 | select logging.test_ereport('error', 'notice2', 'detail', 'hint'); 46 | select logging.test_ereport('error', 'notice3', 'detail', 'hint'); 47 | select level, message, position from logging.get_log(false); 48 | select level, message, position from logging.get_log(272); 49 | select level, message, position from logging.get_log(false); 50 | select level, message, position from logging.get_log(544); 51 | select level, message, position from logging.get_log(false); 52 | select level, message, position from logging.get_log(1000); 53 | select level, message, position from logging.get_log(false); 54 | 55 | reset log_statement; 56 | drop extension pg_logging cascade; 57 | -------------------------------------------------------------------------------- /sql/basic96.sql: -------------------------------------------------------------------------------- 1 | create schema logging; 2 | create extension pg_logging schema logging; 3 | 4 | set log_statement=none; 5 | set pg_logging.buffer_size = 2000; 6 | 7 | select logging.flush_log(); 8 | 9 | create view logs as 10 | select level, appname, errcode, 11 | message, detail, detail_log, hint, context, 12 | errstate, internalpos, internalquery, remote_host, command_tag, 13 | query, query_pos 14 | from logging.get_log(); 15 | 16 | create view logs2 as 17 | select level::logging.error_level, appname, errcode, 18 | message, detail, detail_log, hint, context, 19 | errstate, internalpos, internalquery, remote_host, command_tag, 20 | query, query_pos 21 | from logging.get_log(); 22 | 23 | select * from logs; 24 | 25 | select 1/0; 26 | select 'aaaaa'::int; 27 | select * from logs; 28 | 29 | select repeat('aaaaaaaaa', 20)::int; 30 | select repeat('aaaaaaaaa', 20)::int; 31 | select repeat('aaaaaaaaa', 20)::int; 32 | select repeat('aaaaaaaaa', 20)::int; 33 | select * from logs; 34 | 35 | select logging.test_ereport('error', 'one', 'two', 'three'); 36 | select * from logs2; 37 | 38 | /* cleanup */ 39 | select logging.flush_log(); 40 | 41 | show pg_logging.minlevel; 42 | set pg_logging.minlevel = error; 43 | 44 | select logging.test_ereport('error', 'notice1', 'detail', 'hint'); 45 | select logging.test_ereport('error', 'notice2', 'detail', 'hint'); 46 | select logging.test_ereport('error', 'notice3', 'detail', 'hint'); 47 | select level, message, position from logging.get_log(false); 48 | select level, message, position from logging.get_log(268); 49 | select level, message, position from logging.get_log(false); 50 | select level, message, position from logging.get_log(536); 51 | select level, message, position from logging.get_log(false); 52 | select level, message, position from logging.get_log(1000); 53 | select level, message, position from logging.get_log(false); 54 | 55 | reset log_statement; 56 | drop extension pg_logging cascade; 57 | --------------------------------------------------------------------------------