diff --git a/configure.ac b/configure.ac index 81cb724..d08a103 100644 --- a/configure.ac +++ b/configure.ac @@ -17,10 +17,10 @@ AC_DEFUN([CHECK_DEBUG], [ if test "$enable_debug" = "yes" then - CPPFLAGS="$CPPFLAGS -g -D_DEBUG" + CPPFLAGS="$CPPFLAGS -DENABLE_NLS -g -D_DEBUG" AC_MSG_RESULT([yes]) else - CPPFLAGS="$CPPFLAGS -g -O2" + CPPFLAGS="$CPPFLAGS -DENABLE_NLS -g -O2" AC_MSG_RESULT([no]) fi diff --git a/include/audit_event.h b/include/audit_event.h index 00e9900..a1fe0ce 100644 --- a/include/audit_event.h +++ b/include/audit_event.h @@ -6,6 +6,11 @@ extern "C" { */ // Revised to use C++ conventions - struct xxx instead of typedef struct xxx{ ... } xxx; +struct AuditError { + int sqlerrcode; /* encoded ERRSTATE */ + const char* message; /* primary error message (translated) */ +}; + struct AuditEvent { int64 statementId; /* Simple counter */ @@ -29,6 +34,8 @@ struct AuditEvent const char *className; /* Class of item being logged */ List *objectList; /* List of objects, e.g. in a DROP statement */ bool useLastInList; /* True if last element in list is the object name */ + + List* errorList; }; /* diff --git a/include/audit_handler.h b/include/audit_handler.h index a0e3cc1..93dbded 100644 --- a/include/audit_handler.h +++ b/include/audit_handler.h @@ -27,6 +27,9 @@ #include "pgsql_inc.h" #include "audit_event.h" +#include +#include + #define AUDIT_PROTOCOL_VERSION "1.0" /* @@ -51,6 +54,11 @@ class IWriter { virtual void close() = 0; }; +struct ProcError { + int sqlerrcode; /* encoded ERRSTATE */ + std::string message; /* primary error message (translated) */ +}; + struct PostgreSQL_proc { pid_t pid; const char *db_name; @@ -62,9 +70,12 @@ struct PostgreSQL_proc { const char *appname; unsigned int query_id; bool connected; + bool initialized; + int auth_status; + std::vector error_list; PostgreSQL_proc() - : pid(getpid()), + : pid(), db_name(""), user(""), priv_user(""), @@ -73,7 +84,9 @@ struct PostgreSQL_proc { os_user(""), appname(""), query_id(0), - connected(false) + connected(false), + initialized(), + auth_status() { } }; @@ -91,7 +104,7 @@ class Audit_formatter { * * @return -1 on a failure */ - virtual ssize_t event_format(PostgreSQL_proc * proc, AuditEventStackItem *pItem, IWriter *writer) = 0; + virtual ssize_t event_format(const PostgreSQL_proc *proc, AuditEventStackItem *pItem, IWriter *writer) = 0; /** * Format a message when handler is started @@ -110,7 +123,7 @@ class Audit_formatter { * Format a generic message * @return -1 on a failure */ - virtual ssize_t command_format(PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer) = 0; + virtual ssize_t command_format(const PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer) = 0; }; @@ -141,9 +154,9 @@ class Audit_json_formatter : public Audit_formatter { } } - virtual ssize_t event_format(PostgreSQL_proc * proc, AuditEventStackItem *pItem, IWriter *writer); + virtual ssize_t event_format(const PostgreSQL_proc *proc, AuditEventStackItem *pItem, IWriter *writer); virtual ssize_t start_msg_format(IWriter *writer); - virtual ssize_t command_format(PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer); + virtual ssize_t command_format(const PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer); /** * Utility method used to compile a regex program. @@ -228,12 +241,13 @@ class Audit_handler { */ static void stop_all(); - Audit_handler() : + Audit_handler(const PostgreSQL_proc& proc) : m_initialized(false), m_enabled(false), m_formatter(NULL), m_failed(false), m_log_io_errors(true), + m_proc(proc), m_handler_type("unknown") { } @@ -249,10 +263,9 @@ class Audit_handler { * destruction of this object) * @return 0 on success */ - int init(Audit_formatter *formatter, PostgreSQL_proc *proc) + int init(Audit_formatter *formatter) { m_formatter = formatter; - m_proc = *proc; if (m_initialized) { // elog(LOG, "pid = %d, %s Audit_handler::init - initialized!", getpid(), m_handler_type); @@ -304,7 +317,6 @@ class Audit_handler { /** * Allow updating the proc info. */ - void set_proc(const PostgreSQL_proc& proc) { m_proc = proc; } protected: virtual void handler_start(); @@ -320,7 +332,7 @@ class Audit_handler { bool m_failed; bool m_log_io_errors; time_t m_last_retry_sec_ts; - PostgreSQL_proc m_proc; + const PostgreSQL_proc& m_proc; inline void set_failed() { @@ -353,8 +365,8 @@ class Audit_handler { */ class Audit_io_handler: public Audit_handler, public IWriter { public: - Audit_io_handler() - : m_io_dest(NULL), m_io_type(NULL) + Audit_io_handler(const PostgreSQL_proc& proc) + : Audit_handler(proc), m_io_dest(NULL), m_io_type(NULL) { set_handler_type("io"); } @@ -389,8 +401,8 @@ class Audit_io_handler: public Audit_handler, public IWriter { class Audit_file_handler: public Audit_io_handler { public: - Audit_file_handler() : - m_sync_period(0), m_bufsize(0), m_log_file(NULL), m_sync_counter(0) + Audit_file_handler(const PostgreSQL_proc& proc) : + Audit_io_handler(proc) ,m_sync_period(0), m_bufsize(0), m_log_file(NULL), m_sync_counter(0) { m_io_type = "file"; set_handler_type("file"); @@ -440,8 +452,8 @@ class Audit_file_handler: public Audit_io_handler { class Audit_unix_socket_handler: public Audit_io_handler { public: - Audit_unix_socket_handler() : - m_connect_timeout(1), m_fd(-1) + Audit_unix_socket_handler(const PostgreSQL_proc& proc) : + Audit_io_handler(proc) ,m_connect_timeout(1), m_fd(-1) { m_io_type = "socket"; set_handler_type("socket"); diff --git a/include/pgsql_inc.h b/include/pgsql_inc.h index d75dedb..ebef857 100644 --- a/include/pgsql_inc.h +++ b/include/pgsql_inc.h @@ -26,6 +26,7 @@ extern "C" { #include "miscadmin.h" #include "libpq/auth.h" #include "nodes/nodes.h" +#include "nodes/pg_list.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" diff --git a/src/Makefile.pg.in b/src/Makefile.pg.in index 95047d9..1d4cd8e 100644 --- a/src/Makefile.pg.in +++ b/src/Makefile.pg.in @@ -15,6 +15,7 @@ REGRESS = audit REGRESS_OPTS = --temp-config=$(top_srcdir)/contrib/postgresql-audit/audit.conf PG_CPPFLAGS += -Werror -I../include -I../yajl/include -I../pcre -fPIC \ + -DENABLE_NLS \ -DPOSTGRESQL_AUDIT_PLUGIN_REVISION='"@POSTGRESQL_AUDIT_PLUGIN_REVISION@"' \ -DPOSTGRESQL_AUDIT_PLUGIN_VERSION='"@POSTGRESQL_AUDIT_PLUGIN_VERSION@"' diff --git a/src/audit.cc b/src/audit.cc index 6921983..7b9e99e 100644 --- a/src/audit.cc +++ b/src/audit.cc @@ -67,10 +67,11 @@ static const char *commandTagToString(const char *cmdTag); #endif // Various variables +static PostgreSQL_proc g_proc; // Possible audit handlers -static Audit_file_handler json_file_handler; -static Audit_unix_socket_handler json_unix_socket_handler; +static Audit_file_handler json_file_handler(g_proc); +static Audit_unix_socket_handler json_unix_socket_handler(g_proc); // formatters static Audit_json_formatter json_formatter; @@ -486,11 +487,11 @@ stack_free(void *stackFree) /* Check if this item matches the item to be freed */ if (nextItem == (AuditEventStackItem *) stackFree) { - AUDIT_DEBUG_LOG("found requested freed item in stack, id: [%d]", nextItem->stackId); + AUDIT_DEBUG_LOG("found requested freed item in stack, id: [%zd]", nextItem->stackId); --stackTotal; - AUDIT_DEBUG_LOG("stackTotal: [%d]", stackTotal); + AUDIT_DEBUG_LOG("stackTotal: [%zd]", stackTotal); /* Move top of stack to the item after the freed item */ auditEventStack = nextItem->next; @@ -569,7 +570,7 @@ stack_push() stackItem->contextAudit = contextAudit; stackItem->stackId = ++stackTotal; ++Counter; - AUDIT_DEBUG_LOG("generated stack item Id: %d, counter: [%d]", stackItem->stackId, Counter); + AUDIT_DEBUG_LOG("generated stack item Id: %zd, counter: [%zd]", stackItem->stackId, Counter); #if PG_VERSION_NUM >= 90500 /* @@ -607,7 +608,7 @@ stack_push() static void stack_pop(int64 stackId) { - AUDIT_DEBUG_LOG("stack_pop, stackId: %d", stackId); + AUDIT_DEBUG_LOG("stack_pop, stackId: %zd", stackId); /* Make sure what we want to delete is at the top of the stack */ if (auditEventStack != NULL && auditEventStack->stackId == stackId) @@ -629,7 +630,7 @@ stack_pop(int64 stackId) static void stack_valid(int64 stackId) { - AUDIT_DEBUG_LOG("stack_valid, stackId: %d", stackId); + AUDIT_DEBUG_LOG("stack_valid, stackId: %zd", stackId); AuditEventStackItem *nextItem = auditEventStack; @@ -951,6 +952,7 @@ log_audit_event(AuditEventStackItem *stackItem) // log data to file and/or socket if (! skip) { + g_proc.query_id++; Audit_handler::log_audit_all(stackItem); } @@ -1536,13 +1538,14 @@ static bool initHandlers(Port *port, PostgreSQL_proc *proc) AUDIT_DEBUG_LOG("initHandlers"); // store minimal information early in order to be able to log failed login message + proc->pid = getpid(); if (port) { proc->db_name = port->database_name; proc->user = port->user_name; } - int res = json_file_handler.init(&json_formatter, proc); + int res = json_file_handler.init(&json_formatter); if (res != 0) { // best guess at error code @@ -1550,7 +1553,7 @@ static bool initHandlers(Port *port, PostgreSQL_proc *proc) return false; } - res = json_unix_socket_handler.init(&json_formatter, proc); + res = json_unix_socket_handler.init(&json_formatter); if (res != 0) { AUDIT_ERROR_LOG("unable to init json socket handler. res: %d. Aborting.", res); @@ -1572,9 +1575,7 @@ audit_ClientAuthentication_hook(Port *port, int status) { AUDIT_DEBUG_LOG("audit_ClientAuthentication_hook"); - PostgreSQL_proc proc; - - if (! initHandlers(port, &proc)) + if (! initHandlers(port, &g_proc)) { AUDIT_ERROR_LOG("initHandlers failed"); } @@ -1586,12 +1587,7 @@ audit_ClientAuthentication_hook(Port *port, int status) (*next_ClientAuthentication_hook)(port, status); } - updateProc(& proc, port); - - // update the stored info - json_file_handler.set_proc(proc); - json_unix_socket_handler.set_proc(proc); - + updateProc(&g_proc, port); // Audit_handler::m_audit_handler_list elements were already inited by _PG_init, we don't need to redo that. @@ -1604,6 +1600,8 @@ audit_ClientAuthentication_hook(Port *port, int status) // forked, not just those serving clients, which is not at all what we want. json_file_handler.set_enable(json_file_handler_enable); json_unix_socket_handler.set_enable(json_unix_socket_handler_enable); + g_proc.initialized = true; + g_proc.auth_status = status; // Looks like we need to check the user ourselves to get the failed login event. Oid role = get_role_oid(port->user_name, true); @@ -1620,8 +1618,12 @@ audit_ClientAuthentication_hook(Port *port, int status) // log connection - set up info in each handler // this sets proc.m_connected to true in each handler's // PostgreSQL_proc member. + g_proc.connected = true; + g_proc.query_id++; Audit_handler::log_audit_connect(); } + + AUDIT_DEBUG_LOG("audit_ClientAuthentication_hook: status[%d]", status); } /* @@ -2768,34 +2770,46 @@ audit_ExecutorEnd_hook(QueryDesc *queryDesc) static void audit_emit_log_hook(ErrorData *edata) { - // printf("%s\n", "audit_emit_log_hook"); - - if (edata == NULL || edata->filename == NULL) + if (edata == NULL || internalStatement || edata->elevel < WARNING) { + if (next_emit_log_hook) + (*next_emit_log_hook)(edata); return; } - if (internalStatement) + /* + * - auditEventStack exists + * - an error during execution of a valid query + */ + if (auditEventStack) { - return; + AuditError* error = (AuditError*)palloc(sizeof(*error)); + error->sqlerrcode = edata->sqlerrcode; + error->message = edata->message; + auditEventStack->auditEvent.errorList = lappend(auditEventStack->auditEvent.errorList, error); + + // logging an ERROR is throwig an exception + // the query will not be processed futher and no further hooks will be called + // the event needs to be generated here + if (edata->elevel >= ERROR) + log_audit_event(auditEventStack); } /* - * In order to identify errors which represents invalid statements, verify the following: - * 1. error level == ERROR - * 2. filename like 'parse*' or 'scan*' (globs of filenames in src/backend/parser directory) - * 3. debug_query_string != NULL (this global varibale holds current statement) + * - auditEventStack does not exist + * - debug_query_string exists + * - there is a query, but there was an error already before the query started executing */ - if ( ( edata->elevel == ERROR) && - ( (strstr(edata->filename,"parse_") == edata->filename) || - (strstr(edata->filename,"scan") == edata->filename) ) && - debug_query_string ) + else if (debug_query_string) { - // printf("%s\n", "invalid syntax"); + AuditError* error = (AuditError*)palloc(sizeof(*error)); + error->sqlerrcode = edata->sqlerrcode; + error->message = strdup(edata->message); AuditEventStackItem *stackItem = NULL; stackItem = stack_push(); + stackItem->auditEvent.errorList = lappend(stackItem->auditEvent.errorList, error); // Default values stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL; @@ -2808,44 +2822,31 @@ audit_emit_log_hook(ErrorData *edata) stackItem->auditEvent.commandText = debug_query_string; log_audit_event(stackItem); - - } - /* - * Audit additional error messages - * for now, only for log level ERROR - * make sure not causing alert duplication (like, for FAILED LOGINS which audit in other function) - * in case stack is not empty it means we didnt audit this event yet - * - */ - else if (edata->elevel == ERROR && auditEventStack != NULL) - { - // printf("%s\n", "failed during execution"); - log_audit_event(auditEventStack); } + /* - * Audit failed login - * which didnt trigger audit_ClientAuthentication_hook + * - auditEventStack does not exist + * - debug_query_string does not exist + * - an error in the session itself */ - else if ( edata->elevel == FATAL && - edata->sqlerrcode == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION && - ( strcmp(edata->filename,"auth.c") == 0 - || strcmp(edata->filename,"postmaster.c") == 0 - || strcmp(edata->filename,"miscinit.c") == 0 )) + else { - // printf("%s\n", "failed login, not trigger audit_ClientAuthentication_hook"); - - PostgreSQL_proc proc; - - initHandlers(MyProcPort, &proc); - - // update the stored info - json_file_handler.set_proc(proc); - json_unix_socket_handler.set_proc(proc); - // enable handlers - json_file_handler.set_enable(json_file_handler_enable); - json_unix_socket_handler.set_enable(json_unix_socket_handler_enable); - - // Done with configuration, the actual logging will be done by run_at_exit() + const ProcError error = { + edata->sqlerrcode + ,edata->message + }; + g_proc.error_list.push_back(error); + + // ensure sensor event if auth hook got bypased + const int cat = ERRCODE_TO_CATEGORY(edata->sqlerrcode); + if (!g_proc.initialized && cat == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION) { + initHandlers(MyProcPort, &g_proc); + updateProc(&g_proc, MyProcPort); + g_proc.initialized = true; + json_file_handler.set_enable(json_file_handler_enable); + json_unix_socket_handler.set_enable(json_unix_socket_handler_enable); + // Done with configuration, the actual logging will be done by run_at_exit() + } } // clean-up @@ -3185,6 +3186,74 @@ void remove_version_file() } } +static ProcError synthesize_failed_login(const Port *port) +{ + const char *errstr; + int errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION; + + switch (port->hba->auth_method) { + case uaReject: + case uaImplicitReject: + errstr = gettext_noop("authentication failed for user \"%s\": host rejected"); + break; + case uaTrust: + errstr = gettext_noop("\"trust\" authentication failed for user \"%s\""); + break; + case uaIdent: + errstr = gettext_noop("Ident authentication failed for user \"%s\""); + break; + case uaPeer: + errstr = gettext_noop("Peer authentication failed for user \"%s\""); + break; + case uaPassword: + case uaMD5: +#if PG_VERSION_NUM >= 100000 + case uaSCRAM: +#endif + errstr = gettext_noop("password authentication failed for user \"%s\""); + /* We use it to indicate if a .pgpass password failed. */ + errcode_return = ERRCODE_INVALID_PASSWORD; + break; + case uaGSS: + errstr = gettext_noop("GSSAPI authentication failed for user \"%s\""); + break; + case uaSSPI: + errstr = gettext_noop("SSPI authentication failed for user \"%s\""); + break; + case uaPAM: + errstr = gettext_noop("PAM authentication failed for user \"%s\""); + break; +#if PG_VERSION_NUM >= 90600 + case uaBSD: + errstr = gettext_noop("BSD authentication failed for user \"%s\""); + break; +#endif + case uaLDAP: + errstr = gettext_noop("LDAP authentication failed for user \"%s\""); + break; + case uaCert: + errstr = gettext_noop("certificate authentication failed for user \"%s\""); + break; + case uaRADIUS: + errstr = gettext_noop("RADIUS authentication failed for user \"%s\""); + break; + default: + errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method"); + break; + } + + char* error_message = NULL; + const int res = asprintf(&error_message, errstr, port->user_name); + if (res == -1) + error_message = NULL; + + const ProcError error = {errcode_return, error_message ? error_message : ""}; + free(error_message); + + return error; +} + + /* Module initialization */ /* @@ -3197,8 +3266,15 @@ run_at_exit(void) { AUDIT_DEBUG_LOG("%d run_at_exit running", getpid()); + // libpq goes to stright exit on STATUS_EOF, without logging the error. + // Regenerate the error code here. + if (g_proc.auth_status == STATUS_EOF && !g_proc.connected && g_proc.user[0] && g_proc.error_list.empty()) + g_proc.error_list.push_back(synthesize_failed_login(MyProcPort)); + // log disconnect here + g_proc.query_id++; Audit_handler::log_audit_disconnect(); + g_proc = PostgreSQL_proc(); // clear it out, AFTER any final logging // release allocated memory (allocated directly by malloc/strdup // and not part of PG MemoryContext) @@ -3227,6 +3303,14 @@ run_at_exit(void) void _PG_init(void) { + if (log_min_messages > WARNING) { + const char* old_min_name = "???"; + if (log_min_messages == FATAL) old_min_name = "FATAL"; + if (log_min_messages == PANIC) old_min_name = "PANIC"; + if (log_min_messages == ERROR) old_min_name = "ERROR"; + log_min_messages = WARNING; + AUDIT_DEBUG_LOG("Lowering log_min_messages from [%s] to [WARNING] as required by error code collection.", old_min_name); + } /* Must be loaded with shared_preload_libaries */ if (IsUnderPostmaster) { @@ -3240,8 +3324,7 @@ _PG_init(void) // init the handlers json_formatter.m_perform_password_masking = check_do_password_masking; - PostgreSQL_proc proc; - int res = json_file_handler.init(&json_formatter, & proc); + int res = json_file_handler.init(&json_formatter); if (res != 0) { // best guess at error code @@ -3250,7 +3333,7 @@ _PG_init(void) "%s unable to init json file handler. res: %d. Aborting.", AUDIT_LOG_PREFIX, res))); } - res = json_unix_socket_handler.init(&json_formatter, & proc); + res = json_unix_socket_handler.init(&json_formatter); if (res != 0) { // best guess at error code diff --git a/src/audit_handler.cc b/src/audit_handler.cc index 9ec1357..56624b8 100644 --- a/src/audit_handler.cc +++ b/src/audit_handler.cc @@ -76,8 +76,6 @@ void Audit_io_handler::log_connect() return; } - m_proc.connected = true; - m_formatter->command_format(& m_proc, "Connect", "Connect", this); } @@ -94,14 +92,11 @@ void Audit_io_handler::log_disconnect() { AUDIT_DEBUG_LOG("audit failed login event, user [%s]", m_proc.user); m_formatter->command_format(& m_proc, "Failed Login", "Failed Login", this); - m_proc = PostgreSQL_proc(); // clear it out, AFTER any final logging return; } } m_formatter->command_format(& m_proc, "Quit", "Quit", this); - - m_proc = PostgreSQL_proc(); // clear it out, AFTER any final logging } @@ -559,7 +554,7 @@ ssize_t Audit_json_formatter::start_msg_format(IWriter *writer) yajl_add_uint64(gen, "date", ts); // plugin version info - yajl_add_string_val(gen, "audit-version", POSTGRESQL_AUDIT_PLUGIN_VERSION"-"POSTGRESQL_AUDIT_PLUGIN_REVISION); + yajl_add_string_val(gen, "audit-version", POSTGRESQL_AUDIT_PLUGIN_VERSION "-" POSTGRESQL_AUDIT_PLUGIN_REVISION); yajl_add_string_val(gen, "audit-protocol-version", AUDIT_PROTOCOL_VERSION); // hostname running PostgreSQL @@ -760,7 +755,7 @@ static const char *get_database_name() return db_name; } -ssize_t Audit_json_formatter::event_format(PostgreSQL_proc *proc, AuditEventStackItem *pItem, IWriter *writer) +ssize_t Audit_json_formatter::event_format(const PostgreSQL_proc *proc, AuditEventStackItem *pItem, IWriter *writer) { // initialize yajl yajl_gen gen = yajl_gen_alloc(NULL); @@ -770,7 +765,7 @@ ssize_t Audit_json_formatter::event_format(PostgreSQL_proc *proc, AuditEventStac yajl_add_uint64(gen, "date", ts); yajl_add_uint64(gen, "thread-id", proc->pid); - yajl_add_uint64(gen, "query-id", ++proc->query_id); + yajl_add_uint64(gen, "query-id", proc->query_id); yajl_add_string_val(gen, "user", proc->user); yajl_add_string_val(gen, "priv_user", proc->priv_user); @@ -783,6 +778,23 @@ ssize_t Audit_json_formatter::event_format(PostgreSQL_proc *proc, AuditEventStac const char *cmd = pItem->auditEvent.command; yajl_add_string_val(gen, "cmd", cmd); + yajl_add_string(gen, "error_list"); + yajl_gen_array_open(gen); + const ListCell* errorList = list_head(pItem->auditEvent.errorList); + while (errorList) { + const AuditError* error = (const AuditError*)lfirst(errorList); + yajl_gen_map_open(gen); + yajl_add_string_val(gen, "sqlstate", unpack_sql_state(error->sqlerrcode)); // unpack the alphanumeric SQLSTATE + yajl_add_string_val(gen, "message", error->message); + yajl_gen_map_close(gen); +#if PG_VERSION_NUM >= 130000 + errorList = lnext(pItem->auditEvent.errorList, errorList); +#else + errorList = lnext(errorList); +#endif + } + yajl_gen_array_close(gen); + const char *db_name = get_database_name(); if (pItem->auditEvent.objectName != NULL) @@ -942,7 +954,7 @@ ssize_t Audit_json_formatter::event_format(PostgreSQL_proc *proc, AuditEventStac } // centralize YAJL handling -ssize_t Audit_json_formatter::command_format(PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer) +ssize_t Audit_json_formatter::command_format(const PostgreSQL_proc *proc, const char *command, const char *query, IWriter *writer) { yajl_gen gen = yajl_gen_alloc(NULL); yajl_gen_map_open(gen); @@ -951,7 +963,7 @@ ssize_t Audit_json_formatter::command_format(PostgreSQL_proc *proc, const char * yajl_add_uint64(gen, "date", ts); yajl_add_uint64(gen, "thread-id", proc->pid); - yajl_add_uint64(gen, "query-id", ++proc->query_id); + yajl_add_uint64(gen, "query-id", proc->query_id); yajl_add_string_val(gen, "user", proc->user); yajl_add_string_val(gen, "priv_user", proc->priv_user); @@ -964,6 +976,17 @@ ssize_t Audit_json_formatter::command_format(PostgreSQL_proc *proc, const char * yajl_add_string_val(gen, "cmd", command); yajl_add_string_val(gen, "query", query); + yajl_add_string(gen, "error_list"); + yajl_gen_array_open(gen); + for (size_t i = 0; i < proc->error_list.size(); i++) { + const ProcError& error = proc->error_list[i]; + yajl_gen_map_open(gen); + yajl_add_string_val(gen, "sqlstate", unpack_sql_state(error.sqlerrcode)); // unpack the alphanumeric SQLSTATE + yajl_add_string_val(gen, "message", error.message.c_str()); + yajl_gen_map_close(gen); + } + yajl_gen_array_close(gen); + ssize_t res = -2; yajl_gen_status stat = yajl_gen_map_close(gen); // close the object if (stat == yajl_gen_status_ok) // all is good write the buffer out