3692 lines
123 KiB
C++
3692 lines
123 KiB
C++
/* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
/*
|
|
NB This module has an unusual amount of failsafes, OOM checks, and
|
|
so on as it implements a public API. This makes a fair number
|
|
of minor code paths cases of "we should never get here (unless
|
|
someone's going out of their way to break to API)". :)
|
|
*/
|
|
|
|
#define LOG_SUBSYSTEM_TAG "Server"
|
|
|
|
#include "log_builtins_filter_imp.h"
|
|
#include "log_builtins_imp.h" // internal structs
|
|
// connection_events_loop_aborted()
|
|
|
|
#include "my_dir.h"
|
|
#include "mysys_err.h"
|
|
|
|
#include <mysql/components/services/log_service.h>
|
|
#include <mysql/components/services/log_shared.h> // data types
|
|
|
|
#include "registry.h" // mysql_registry_imp
|
|
#include "server_component.h"
|
|
#include "sql/current_thd.h" // current_thd
|
|
#include "sql/log.h" // make_iso8601_timestamp, log_write_errstream,
|
|
// log_get_thread_id, mysql_errno_to_symbol,
|
|
// mysql_symbol_to_errno, log_vmessage,
|
|
// error_message_for_error_log
|
|
#include "sql/mysqld.h" // opt_log_(timestamps|error_services),
|
|
#include "sql/sql_class.h" // THD
|
|
|
|
// Must come after sql/log.h.
|
|
#include "mysql/components/services/log_builtins.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <syslog.h>
|
|
#else
|
|
#include <stdio.h>
|
|
|
|
#include "my_sys.h"
|
|
|
|
extern CHARSET_INFO my_charset_utf16le_bin; // used in Windows EventLog
|
|
static HANDLE hEventLog = NULL; // global
|
|
#define MSG_DEFAULT 0xC0000064L
|
|
#endif
|
|
|
|
extern log_filter_ruleset *log_filter_builtin_rules; // what it says on the tin
|
|
|
|
PSI_memory_key key_memory_log_error_loaded_services;
|
|
PSI_memory_key key_memory_log_error_stack;
|
|
|
|
using std::string;
|
|
using std::unique_ptr;
|
|
|
|
struct log_service_cache_entry;
|
|
|
|
struct log_service_cache_entry_free {
|
|
/**
|
|
Release an entry in the hash of log services.
|
|
|
|
@param sce the entry to free
|
|
*/
|
|
void operator()(log_service_cache_entry *sce) const;
|
|
};
|
|
|
|
/**
|
|
We're caching handles to the services used in error logging
|
|
as looking them up is costly.
|
|
*/
|
|
using cache_entry_with_deleter =
|
|
unique_ptr<log_service_cache_entry, log_service_cache_entry_free>;
|
|
static collation_unordered_map<string, cache_entry_with_deleter>
|
|
*log_service_cache;
|
|
|
|
/**
|
|
Lock for the log "stack" (i.e. the list of active log-services).
|
|
X-locked while stack is changed/configured.
|
|
S-locked while stack is used.
|
|
*/
|
|
static mysql_rwlock_t THR_LOCK_log_stack;
|
|
|
|
/**
|
|
Make sure only one instance of syslog/Eventlog code runs at a time.
|
|
*/
|
|
static mysql_mutex_t THR_LOCK_log_syseventlog;
|
|
|
|
/**
|
|
Make sure only one instance of the buffered "writer" runs at a time.
|
|
|
|
In normal operation, the log-event will be created dynamically, then
|
|
it will be fed through the pipeline, and then it will be released.
|
|
Since the event is allocated in the caller, we can be sure it won't
|
|
go away wholesale during processing, and since the event is local to
|
|
the caller, no other thread will tangle with it. It is therefore safe
|
|
in those cases not to wrap a lock around the event.
|
|
(The log-pipeline will still grab a shared lock, THR_LOCK_log_stack,
|
|
to protect the pipeline (not the event) and the log-services cache from
|
|
being changed while the pipeline is being applied.
|
|
Likewise, log-services may protect their resources (file-writers will
|
|
usually take a lock to serialize their writes; the built-in filter will
|
|
take a lock on its rule-set as that is shared between concurrent
|
|
threads running the filter, and so on).
|
|
None of these are intended to protect the event itself though.
|
|
|
|
In buffered mode on the other hand, we copy each log-event (the
|
|
original of which, see above, is owned by the caller and local
|
|
to the thread, and therefore safe without locking) to a global
|
|
buffer / backlog. As this backlog can be added to by all threads,
|
|
it must be protected by a lock (once we have fully initialized
|
|
the subsystem with log_builtins_init() and support multi-threaded
|
|
mode anyway, as indicated by log_builtins_inited being true, see
|
|
below). This is that lock.
|
|
*/
|
|
static mysql_mutex_t THR_LOCK_log_buffered;
|
|
|
|
/**
|
|
Subsystem initialized and ready to use?
|
|
*/
|
|
static bool log_builtins_inited = false;
|
|
|
|
/**
|
|
Name of the interface that log-services implements.
|
|
*/
|
|
#define LOG_SERVICES_PREFIX "log_service"
|
|
|
|
struct log_line_buffer {
|
|
log_line ll; ///< log-event we're buffering
|
|
log_line_buffer *next; ///< chronologically next log-event
|
|
};
|
|
/// Pointer to the first element in the list of buffered log messages
|
|
static log_line_buffer *log_line_buffer_start = nullptr;
|
|
/// Where to write the pointer to the newly-created tail-element of the list
|
|
static log_line_buffer **log_line_buffer_tail = &log_line_buffer_start;
|
|
|
|
/**
|
|
Timestamp: During buffered logging, when should we next consider flushing?
|
|
This variable is set to the time we'll next check whether we'll want to
|
|
flush buffered log events.
|
|
I.e. it is set to a future time (current time + window length); once
|
|
the value expires (turns into present/past), we check whether we need
|
|
to flush and update the variable to the next time we should check.
|
|
*/
|
|
static ulonglong log_buffering_timeout = 0;
|
|
|
|
/// If after this many seconds we're still buffering, flush!
|
|
#ifndef LOG_BUFFERING_TIMEOUT_AFTER
|
|
#define LOG_BUFFERING_TIMEOUT_AFTER (60)
|
|
#endif
|
|
/// Thereafter, if still buffering after this many more seconds, flush again!
|
|
#ifndef LOG_BUFFERING_TIMEOUT_EVERY
|
|
#define LOG_BUFFERING_TIMEOUT_EVERY (10)
|
|
#endif
|
|
/// Time function returns microseconds, we want seconds.
|
|
#ifndef LOG_BUFFERING_TIME_SCALE
|
|
#define LOG_BUFFERING_TIME_SCALE 1000000
|
|
#endif
|
|
|
|
/// Does buffer contain SYSTEM or ERROR prio messages? Flush early only then!
|
|
static int log_buffering_flushworthy = false;
|
|
|
|
/**
|
|
Timestamp of when we last flushed to traditional error-log
|
|
(built-in sink). Log lines with timestamps older than this
|
|
have already been flushed to the default log-sink, so we
|
|
won't do so again to prevent duplicates. (The buffered events
|
|
are still kept around until buffered logging ends however in
|
|
case we need to flush them to other log-writers then.
|
|
This timestamp is updated to the present and then drifts into
|
|
the past, in contrast to the time-out value (see there).
|
|
*/
|
|
static ulonglong log_sink_trad_last = 0;
|
|
|
|
/**
|
|
Finding and acquiring a service in the component framework is
|
|
expensive, and we may use services a log (depending on how many
|
|
events are logged per second), so we cache the relevant data.
|
|
This struct describes a given service.
|
|
*/
|
|
struct log_service_cache_entry {
|
|
char *name; ///< name of this service
|
|
size_t name_len; ///< service-name's length
|
|
my_h_service service; ///< handle (service framework)
|
|
int opened; ///< currently open instances
|
|
int requested; ///< requested instances
|
|
int chistics; ///< multi-open supported, etc.
|
|
};
|
|
|
|
/**
|
|
State of a given instance of a service. A service may support being
|
|
opened several times.
|
|
*/
|
|
typedef struct _log_service_instance {
|
|
log_service_cache_entry *sce; ///< the service in question
|
|
void *instance; ///< instance handle (multi-open)
|
|
struct _log_service_instance *next; ///< next instance (any service)
|
|
} log_service_instance;
|
|
|
|
static log_service_instance *log_service_instances = nullptr; ///< anchor
|
|
|
|
/**
|
|
An error-stream.
|
|
Rather than implement its own file operations, a log-service may use
|
|
convenience functions defined in this file. These functions use the
|
|
log_errstream struct to describe their log-files. These structs are
|
|
opaque to the log-services.
|
|
*/
|
|
struct log_errstream {
|
|
FILE *file{nullptr}; ///< file to log to
|
|
mysql_mutex_t LOCK_errstream; ///< lock for logging
|
|
};
|
|
|
|
/// What mode is error-logging in (e.g. are loadable services available yet)?
|
|
static enum log_error_stage log_error_stage_current =
|
|
LOG_ERROR_STAGE_BUFFERING_EARLY;
|
|
|
|
/// Set error-logging stage hint (e.g. are loadable services available yet?).
|
|
void log_error_stage_set(enum log_error_stage les) {
|
|
log_error_stage_current = les;
|
|
}
|
|
|
|
/// What mode is error-logging in (e.g. are loadable services available yet)?
|
|
enum log_error_stage log_error_stage_get() { return log_error_stage_current; }
|
|
|
|
/**
|
|
Test whether a given log-service name refers to a built-in
|
|
service (built-in filter or built-in sink at this point).
|
|
|
|
@param name the name -- either just the component's, or
|
|
a fully qualified service.component
|
|
@param len the length of the aforementioned name
|
|
@retval if built-in filter: flags for built-in|singleton|filter
|
|
@retval if built-in sink: flags for built-in|singleton|sink
|
|
@retval otherwise: LOG_SERVICE_UNSPECIFIED
|
|
*/
|
|
static int log_service_check_if_builtin(const char *name, size_t len) {
|
|
const size_t builtin_len = sizeof(LOG_SERVICES_PREFIX) - 1;
|
|
|
|
if ((len > (builtin_len + 1)) && (name[builtin_len] == '.') &&
|
|
(0 == strncmp(name, LOG_SERVICES_PREFIX, builtin_len))) {
|
|
name += builtin_len;
|
|
len -= builtin_len;
|
|
}
|
|
|
|
if ((len == sizeof(LOG_BUILTINS_FILTER) - 1) &&
|
|
(0 == strncmp(name, LOG_BUILTINS_FILTER, len)))
|
|
return LOG_SERVICE_BUILTIN | LOG_SERVICE_FILTER | LOG_SERVICE_SINGLETON;
|
|
|
|
if ((len == sizeof(LOG_BUILTINS_SINK) - 1) &&
|
|
(0 == strncmp(name, LOG_BUILTINS_SINK, len)))
|
|
return LOG_SERVICE_BUILTIN | LOG_SERVICE_SINK | LOG_SERVICE_SINGLETON;
|
|
|
|
if ((len == sizeof(LOG_BUILTINS_BUFFER) - 1) &&
|
|
(0 == strncmp(name, LOG_BUILTINS_BUFFER, len)))
|
|
return LOG_SERVICE_BUILTIN | LOG_SERVICE_SINK | LOG_SERVICE_SINGLETON |
|
|
LOG_SERVICE_BUFFER;
|
|
|
|
return LOG_SERVICE_UNSPECIFIED;
|
|
}
|
|
|
|
/**
|
|
Test whether given service has *all* of the given characteristics.
|
|
(See log_service_chistics for a list!)
|
|
|
|
@param sce service cache entry for the service in question
|
|
@param required_flags flags we're interested in (bitwise or'd)
|
|
|
|
@retval true if all given flags are present, false otherwise
|
|
*/
|
|
static inline bool log_service_has_characteristics(log_service_cache_entry *sce,
|
|
int required_flags) {
|
|
return ((sce->chistics & required_flags) == required_flags);
|
|
}
|
|
|
|
/**
|
|
Pre-defined "well-known" keys, as opposed to ad hoc ones,
|
|
for key/value pairs in logging.
|
|
*/
|
|
typedef struct _log_item_wellknown_key {
|
|
const char *name; ///< key name
|
|
size_t name_len; ///< length of key's name
|
|
log_item_class item_class; ///< item class (float/int/string)
|
|
log_item_type item_type; ///< exact type, 1:1 relationship with name
|
|
} log_item_wellknown_key;
|
|
|
|
/**
|
|
We support a number of predefined keys, such as "error-code" or
|
|
"message". These are defined here. We also support user-defined
|
|
"ad hoc" (or "generic") keys that let users of the error stack
|
|
add values with arbitrary keys (as long as those keys don't coincide
|
|
with the wellknown ones, anyway).
|
|
|
|
The idea here is that we want the flexibility of arbitrary keys,
|
|
while being able to do certain optimizations for the common case.
|
|
This also allows us to relatively cheaply add some convenience
|
|
features, e.g. we know that error symbol ("ER_STARTUP") and
|
|
error code (1451) are related, and can supply one as the other
|
|
is submitted. Likewise of course, we can use the error code to
|
|
fetch the associated registered error message string for that
|
|
error code. Et cetera!
|
|
*/
|
|
static const log_item_wellknown_key log_item_wellknown_keys[] = {
|
|
{STRING_WITH_LEN("--ERROR--"), LOG_UNTYPED, LOG_ITEM_END},
|
|
{STRING_WITH_LEN("log_type"), LOG_INTEGER, LOG_ITEM_LOG_TYPE},
|
|
{STRING_WITH_LEN("err_code"), LOG_INTEGER, LOG_ITEM_SQL_ERRCODE},
|
|
{STRING_WITH_LEN("err_symbol"), LOG_CSTRING, LOG_ITEM_SQL_ERRSYMBOL},
|
|
{STRING_WITH_LEN("SQL_state"), LOG_CSTRING, LOG_ITEM_SQL_STATE},
|
|
{STRING_WITH_LEN("OS_errno"), LOG_INTEGER, LOG_ITEM_SYS_ERRNO},
|
|
{STRING_WITH_LEN("OS_errmsg"), LOG_CSTRING, LOG_ITEM_SYS_STRERROR},
|
|
{STRING_WITH_LEN("source_file"), LOG_CSTRING, LOG_ITEM_SRC_FILE},
|
|
{STRING_WITH_LEN("source_line"), LOG_INTEGER, LOG_ITEM_SRC_LINE},
|
|
{STRING_WITH_LEN("function"), LOG_CSTRING, LOG_ITEM_SRC_FUNC},
|
|
{STRING_WITH_LEN("subsystem"), LOG_CSTRING, LOG_ITEM_SRV_SUBSYS},
|
|
{STRING_WITH_LEN("component"), LOG_CSTRING, LOG_ITEM_SRV_COMPONENT},
|
|
{STRING_WITH_LEN("user"), LOG_LEX_STRING, LOG_ITEM_MSC_USER},
|
|
{STRING_WITH_LEN("host"), LOG_LEX_STRING, LOG_ITEM_MSC_HOST},
|
|
{STRING_WITH_LEN("thread"), LOG_INTEGER, LOG_ITEM_SRV_THREAD},
|
|
{STRING_WITH_LEN("query_id"), LOG_INTEGER, LOG_ITEM_SQL_QUERY_ID},
|
|
{STRING_WITH_LEN("table"), LOG_CSTRING, LOG_ITEM_SQL_TABLE_NAME},
|
|
{STRING_WITH_LEN("prio"), LOG_INTEGER, LOG_ITEM_LOG_PRIO},
|
|
{STRING_WITH_LEN("label"), LOG_CSTRING, LOG_ITEM_LOG_LABEL},
|
|
{STRING_WITH_LEN("verbatim"), LOG_CSTRING, LOG_ITEM_LOG_VERBATIM},
|
|
{STRING_WITH_LEN("msg"), LOG_CSTRING, LOG_ITEM_LOG_MESSAGE},
|
|
{STRING_WITH_LEN("msg_id"), LOG_INTEGER, LOG_ITEM_LOG_LOOKUP},
|
|
{STRING_WITH_LEN("time"), LOG_CSTRING, LOG_ITEM_LOG_TIMESTAMP},
|
|
{STRING_WITH_LEN("buffered"), LOG_INTEGER, LOG_ITEM_LOG_BUFFERED},
|
|
{STRING_WITH_LEN("and_n_more"), LOG_INTEGER, LOG_ITEM_LOG_SUPPRESSED},
|
|
/*
|
|
We should never see the following key names in normal operations
|
|
(but see the user-specified key instead). These have entries all
|
|
the same, covering the entirety of log_item_type, so we can use the
|
|
usual mechanisms for type-to-class mapping etc.
|
|
We could set the names to nullptr, but they're not much overhead, add
|
|
readability, and allow for easily creating debug info of the form,
|
|
"%s:%s=\"%s\"", wellknown_name, item->key, item->value
|
|
*/
|
|
{STRING_WITH_LEN("misc_float"), LOG_FLOAT, LOG_ITEM_GEN_FLOAT},
|
|
{STRING_WITH_LEN("misc_integer"), LOG_INTEGER, LOG_ITEM_GEN_INTEGER},
|
|
{STRING_WITH_LEN("misc_string"), LOG_LEX_STRING, LOG_ITEM_GEN_LEX_STRING},
|
|
{STRING_WITH_LEN("misc_cstring"), LOG_CSTRING, LOG_ITEM_GEN_CSTRING}};
|
|
|
|
static uint log_item_wellknown_keys_count =
|
|
(sizeof(log_item_wellknown_keys) / sizeof(log_item_wellknown_key));
|
|
|
|
/*
|
|
string helpers
|
|
*/
|
|
|
|
/**
|
|
Compare two NUL-terminated byte strings
|
|
|
|
Note that when comparing without length limit, the long string
|
|
is greater if they're equal up to the length of the shorter
|
|
string, but the shorter string will be considered greater if
|
|
its "value" up to that point is greater:
|
|
|
|
compare 'abc','abcd': -100 (longer wins if otherwise same)
|
|
compare 'abca','abcd': -3 (higher value wins)
|
|
compare 'abcaaaaa','abcd': -3 (higher value wins)
|
|
|
|
@param a the first string
|
|
@param b the second string
|
|
@param len compare at most this many characters --
|
|
0 for no limit
|
|
@param case_insensitive ignore upper/lower case in comparison
|
|
|
|
@retval -1 a < b
|
|
@retval 0 a == b
|
|
@retval 1 a > b
|
|
*/
|
|
int log_string_compare(const char *a, const char *b, size_t len,
|
|
bool case_insensitive) {
|
|
if (a == nullptr) /* purecov: begin inspected */
|
|
return (b == nullptr) ? 0 : -1;
|
|
else if (b == nullptr)
|
|
return 1; /* purecov: end */
|
|
else if (len < 1) // no length limit for comparison
|
|
{
|
|
return case_insensitive ? native_strcasecmp(a, b) : strcmp(a, b);
|
|
}
|
|
|
|
return case_insensitive ? native_strncasecmp(a, b, len) : strncmp(a, b, len);
|
|
}
|
|
|
|
/*
|
|
log item helpers
|
|
*/
|
|
|
|
/**
|
|
Predicate used to determine whether a type is generic
|
|
(generic string, generic float, generic integer) rather
|
|
than a well-known type.
|
|
|
|
@param t log item type to examine
|
|
|
|
@retval true if generic type
|
|
@retval false if wellknown type
|
|
*/
|
|
bool log_item_generic_type(log_item_type t) {
|
|
return (t & (LOG_ITEM_GEN_CSTRING | LOG_ITEM_GEN_LEX_STRING |
|
|
LOG_ITEM_GEN_INTEGER | LOG_ITEM_GEN_FLOAT));
|
|
}
|
|
|
|
/**
|
|
Predicate used to determine whether a class is a string
|
|
class (C-string or Lex-string).
|
|
|
|
@param c log item class to examine
|
|
|
|
@retval true if of a string class
|
|
@retval false if not of a string class
|
|
*/
|
|
bool log_item_string_class(log_item_class c) {
|
|
return ((c == LOG_CSTRING) || (c == LOG_LEX_STRING));
|
|
}
|
|
|
|
/**
|
|
Predicate used to determine whether a class is a numeric
|
|
class (integer or float).
|
|
|
|
@param c log item class to examine
|
|
|
|
@retval true if of a numeric class
|
|
@retval false if not of a numeric class
|
|
*/
|
|
bool log_item_numeric_class(log_item_class c) {
|
|
return ((c == LOG_INTEGER) || (c == LOG_FLOAT));
|
|
}
|
|
|
|
/**
|
|
Get an integer value from a log-item of float or integer type.
|
|
|
|
@param li log item to get the value from
|
|
@param[out] i longlong to store the value in
|
|
*/
|
|
void log_item_get_int(log_item *li, longlong *i) /* purecov: begin inspected */
|
|
{
|
|
if (li->item_class == LOG_FLOAT)
|
|
*i = (longlong)li->data.data_float;
|
|
else
|
|
*i = (longlong)li->data.data_integer;
|
|
} /* purecov: end */
|
|
|
|
/**
|
|
Get a float value from a log-item of float or integer type.
|
|
|
|
@param li log item to get the value from
|
|
@param[out] f float to store the value in
|
|
*/
|
|
void log_item_get_float(log_item *li, double *f) {
|
|
if (li->item_class == LOG_FLOAT)
|
|
*f = (float)li->data.data_float;
|
|
else
|
|
*f = (float)li->data.data_integer;
|
|
}
|
|
|
|
/**
|
|
Get a string value from a log-item of C-string or Lex string type.
|
|
|
|
@param li log item to get the value from
|
|
@param[out] str char-pointer to store the pointer to the value in
|
|
@param[out] len size_t pointer to store the length of the value in
|
|
*/
|
|
void log_item_get_string(log_item *li, char **str, size_t *len) {
|
|
if ((*str = const_cast<char *>(li->data.data_string.str)) == nullptr)
|
|
*len = 0;
|
|
else if (li->item_class & LOG_CSTRING)
|
|
*len = strlen(li->data.data_string.str);
|
|
else
|
|
*len = li->data.data_string.length;
|
|
}
|
|
|
|
/**
|
|
See whether a string is a wellknown field name.
|
|
|
|
@param key potential key starts here
|
|
@param len length of the string to examine
|
|
|
|
@retval LOG_ITEM_TYPE_RESERVED: reserved, but not "wellknown" key
|
|
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
|
|
@retval >0: index in array of wellknowns
|
|
*/
|
|
int log_item_wellknown_by_name(const char *key, size_t len) {
|
|
uint c;
|
|
// optimize and safeify lookup
|
|
for (c = 0; (c < log_item_wellknown_keys_count); c++) {
|
|
if ((log_item_wellknown_keys[c].name_len == len) &&
|
|
(0 == native_strncasecmp(log_item_wellknown_keys[c].name, key, len))) {
|
|
if (log_item_generic_type(log_item_wellknown_keys[c].item_type) ||
|
|
(log_item_wellknown_keys[c].item_type == LOG_ITEM_END))
|
|
return LOG_ITEM_TYPE_RESERVED;
|
|
return c;
|
|
}
|
|
}
|
|
return LOG_ITEM_TYPE_NOT_FOUND;
|
|
}
|
|
|
|
/**
|
|
See whether a type is wellknown.
|
|
|
|
@param t log item type to examine
|
|
|
|
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
|
|
@retval >0: index in array of wellknowns
|
|
*/
|
|
int log_item_wellknown_by_type(log_item_type t) {
|
|
uint c;
|
|
// optimize and safeify lookup
|
|
for (c = 0; (c < log_item_wellknown_keys_count); c++) {
|
|
if (log_item_wellknown_keys[c].item_type == t) return c;
|
|
}
|
|
DBUG_PRINT("warning", ("wellknown_by_type: type %d is not well-known."
|
|
" Or, you know, known.",
|
|
t));
|
|
return LOG_ITEM_TYPE_NOT_FOUND;
|
|
}
|
|
|
|
/**
|
|
Accessor: from a record describing a wellknown key, get its name
|
|
|
|
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
|
|
|
|
@retval name (NTBS)
|
|
*/
|
|
const char *log_item_wellknown_get_name(uint idx) {
|
|
return log_item_wellknown_keys[idx].name;
|
|
}
|
|
|
|
/**
|
|
Accessor: from a record describing a wellknown key, get its type
|
|
|
|
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
|
|
|
|
@retval the log item type for the wellknown key
|
|
*/
|
|
log_item_type log_item_wellknown_get_type(uint idx) {
|
|
return log_item_wellknown_keys[idx].item_type;
|
|
}
|
|
|
|
/**
|
|
Accessor: from a record describing a wellknown key, get its class
|
|
|
|
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
|
|
|
|
@retval the log item class for the wellknown key
|
|
*/
|
|
log_item_class log_item_wellknown_get_class(uint idx) {
|
|
return log_item_wellknown_keys[idx].item_class;
|
|
}
|
|
|
|
/**
|
|
Sanity check an item.
|
|
Certain log sinks have very low requirements with regard to the data
|
|
they receive; they write keys as strings, and then data according to
|
|
the item's class (string, integer, or float), formatted to the sink's
|
|
standards (e.g. JSON, XML, ...).
|
|
Code that has higher requirements can use this check to see whether
|
|
the given item is of a known type (whether generic or wellknown),
|
|
whether the given type and class agree, and whether in case of a
|
|
well-known type, the given key is correct for that type.
|
|
If your code generates items that don't pass this check, you should
|
|
probably go meditate on it.
|
|
|
|
@param li the log_item to check
|
|
|
|
@retval LOG_ITEM_OK no problems
|
|
@retval LOG_ITEM_TYPE_NOT_FOUND unknown item type
|
|
@retval LOG_ITEM_CLASS_MISMATCH item_class derived from type isn't
|
|
what's set on the item
|
|
@retval LOG_ITEM_KEY_MISMATCH class not generic, so key should
|
|
match wellknown
|
|
@retval LOG_ITEM_STRING_NULL class is string, pointer is nullptr
|
|
@retval LOG_ITEM_KEY_NULL no key set (this is legal e.g. on aux
|
|
items of filter rules, but should not
|
|
occur in a log_line, i.e., log_sinks are
|
|
within their rights to discard such items)
|
|
*/
|
|
int log_item_inconsistent(log_item *li) {
|
|
int w, c;
|
|
|
|
// invalid type
|
|
if ((w = log_item_wellknown_by_type(li->type)) == LOG_ITEM_TYPE_NOT_FOUND)
|
|
return LOG_ITEM_TYPE_NOT_FOUND;
|
|
|
|
// fetch expected storage class for this type
|
|
if ((c = log_item_wellknown_keys[w].item_class) == LOG_CSTRING)
|
|
c = LOG_LEX_STRING;
|
|
|
|
// class and type don't match
|
|
if (c != li->item_class) return LOG_ITEM_CLASS_MISMATCH;
|
|
|
|
// no key set
|
|
if (li->key == nullptr) return LOG_ITEM_KEY_NULL;
|
|
|
|
// it's not a generic, and key and type don't match
|
|
if (!log_item_generic_type(li->type) &&
|
|
(0 != strcmp(li->key, log_item_wellknown_keys[w].name)))
|
|
return LOG_ITEM_KEY_MISMATCH;
|
|
|
|
// strings should have non-nullptr
|
|
if ((c == LOG_LEX_STRING) && (li->data.data_string.str == nullptr))
|
|
return LOG_ITEM_STRING_NULL;
|
|
|
|
return LOG_ITEM_OK;
|
|
}
|
|
|
|
/**
|
|
Release any of key and value on a log-item that were dynamically allocated.
|
|
|
|
@param li log-item to release the payload of
|
|
*/
|
|
void log_item_free(log_item *li) {
|
|
if (li->alloc & LOG_ITEM_FREE_KEY) my_free(const_cast<char *>(li->key));
|
|
|
|
if (li->alloc & LOG_ITEM_FREE_VALUE) {
|
|
if (li->item_class == LOG_LEX_STRING)
|
|
my_free(const_cast<char *>(li->data.data_string.str));
|
|
else
|
|
DBUG_ASSERT(false);
|
|
}
|
|
|
|
li->alloc = LOG_ITEM_FREE_NONE;
|
|
}
|
|
|
|
/**
|
|
Dynamically allocate and initialize a log_line.
|
|
|
|
@retval nullptr could not set up buffer (too small?)
|
|
@retval other address of the newly initialized log_line
|
|
*/
|
|
log_line *log_line_init() {
|
|
log_line *ll;
|
|
if ((ll = (log_line *)my_malloc(key_memory_log_error_stack, sizeof(log_line),
|
|
MYF(0))) != nullptr)
|
|
memset(ll, 0, sizeof(log_line));
|
|
return ll;
|
|
}
|
|
|
|
/**
|
|
Release a log_line allocated with line_init()
|
|
|
|
@param ll a log_line previously allocated with line_init()
|
|
*/
|
|
void log_line_exit(log_line *ll) {
|
|
if (ll != nullptr) my_free(ll);
|
|
}
|
|
|
|
/**
|
|
Predicate indicating whether a log line is "willing" to accept any more
|
|
key/value pairs.
|
|
|
|
@param ll the log-line to examine
|
|
|
|
@retval false if not full / if able to accept another log_item
|
|
@retval true if full
|
|
*/
|
|
bool log_line_full(log_line *ll) {
|
|
return ((ll == nullptr) || (ll->count >= LOG_ITEM_MAX));
|
|
}
|
|
|
|
/**
|
|
Duplicate a log-event. This is a deep copy where the items (key/value pairs)
|
|
have their own allocated memory separate from that in the source item.
|
|
|
|
@param dst log_line that will hold the copy
|
|
@param src log_line we copy from
|
|
|
|
@retval false on success
|
|
@retval true if out of memory
|
|
*/
|
|
bool log_line_duplicate(log_line *dst, log_line *src) {
|
|
int c;
|
|
|
|
DBUG_ASSERT((dst != nullptr) && (src != nullptr));
|
|
|
|
*dst = *src;
|
|
|
|
for (c = 0; c < src->count; c++) {
|
|
dst->item[c].alloc = LOG_ITEM_FREE_NONE;
|
|
|
|
if ((dst->item[c].key =
|
|
my_strndup(key_memory_log_error_loaded_services, src->item[c].key,
|
|
strlen(src->item[c].key), MYF(0))) != nullptr) {
|
|
// We just allocated a key, remember to free it later:
|
|
dst->item[c].alloc = LOG_ITEM_FREE_KEY;
|
|
|
|
// If the value is a string, duplicate it, and remember to free it later!
|
|
if (log_item_string_class(src->item[c].item_class) &&
|
|
(src->item[c].data.data_string.str != nullptr)) {
|
|
if ((dst->item[c].data.data_string.str = my_strndup(
|
|
key_memory_log_error_loaded_services,
|
|
src->item[c].data.data_string.str,
|
|
src->item[c].data.data_string.length, MYF(0))) != nullptr)
|
|
dst->item[c].alloc |= LOG_ITEM_FREE_VALUE;
|
|
else
|
|
goto fail; /* purecov: inspected */
|
|
}
|
|
} else
|
|
goto fail; /* purecov: inspected */
|
|
}
|
|
|
|
return false;
|
|
|
|
fail: /* purecov: begin inspected */
|
|
dst->count = c + 1; // consider only the items we actually processed
|
|
log_line_item_free_all(dst); // free those items
|
|
return true; // flag failure
|
|
/* purecov: end */
|
|
}
|
|
|
|
/**
|
|
How many items are currently set on the given log_line?
|
|
|
|
@param ll the log-line to examine
|
|
|
|
@retval the number of items set
|
|
*/
|
|
int log_line_item_count(log_line *ll) { return ll->count; }
|
|
|
|
/**
|
|
Test whether a given type is presumed present on the log line.
|
|
|
|
@param ll the log_line to examine
|
|
@param m the log_type to test for
|
|
|
|
@retval 0 not present
|
|
@retval !=0 present
|
|
*/
|
|
log_item_type_mask log_line_item_types_seen(log_line *ll,
|
|
log_item_type_mask m) {
|
|
return (ll != nullptr) ? (ll->seen & m) : 0;
|
|
}
|
|
|
|
/**
|
|
Release log line item (key/value pair) with the index elem in log line ll.
|
|
This frees whichever of key and value were dynamically allocated.
|
|
This leaves a "gap" in the bag that may immediately be overwritten
|
|
with an updated element. If the intention is to remove the item without
|
|
replacing it, use log_line_item_remove() instead!
|
|
|
|
@param ll log_line
|
|
@param elem index of the key/value pair to release
|
|
*/
|
|
void log_line_item_free(log_line *ll, size_t elem) {
|
|
DBUG_ASSERT(ll->count > 0);
|
|
log_item_free(&(ll->item[elem]));
|
|
}
|
|
|
|
/**
|
|
Release all log line items (key/value pairs) in log line ll.
|
|
This frees whichever keys and values were dynamically allocated.
|
|
|
|
@param ll log_line
|
|
*/
|
|
void log_line_item_free_all(log_line *ll) {
|
|
while (ll->count > 0) log_item_free(&(ll->item[--ll->count]));
|
|
ll->seen = LOG_ITEM_END;
|
|
}
|
|
|
|
/**
|
|
Release log line item (key/value pair) with the index elem in log line ll.
|
|
This frees whichever of key and value were dynamically allocated.
|
|
This moves any trailing items to fill the "gap" and decreases the counter
|
|
of elements in the log line. If the intention is to leave a "gap" in the
|
|
bag that may immediately be overwritten with an updated element, use
|
|
log_line_item_free() instead!
|
|
|
|
@param ll log_line
|
|
@param elem index of the key/value pair to release
|
|
*/
|
|
void log_line_item_remove(log_line *ll, int elem) {
|
|
DBUG_ASSERT(ll->count > 0);
|
|
|
|
log_line_item_free(ll, elem);
|
|
|
|
// Fill the gap if needed (if there are more elements and we're not the tail)
|
|
if ((ll->count > 1) && (elem < (ll->count - 1)))
|
|
ll->item[elem] = ll->item[ll->count - 1];
|
|
|
|
ll->count--;
|
|
}
|
|
|
|
/**
|
|
Find the (index of the) last key/value pair of the given name
|
|
in the log line.
|
|
|
|
@param ll log line
|
|
@param key the key to look for
|
|
|
|
@retval -1: none found
|
|
@retval -2: invalid search-key given
|
|
@retval -3: no log_line given
|
|
@retval >=0: index of the key/value pair in the log line
|
|
*/
|
|
int log_line_index_by_name(log_line *ll, const char *key) {
|
|
uint32 count = ll->count;
|
|
|
|
if (ll == nullptr) /* purecov: begin inspected */
|
|
return -3;
|
|
else if ((key == nullptr) || (key[0] == '\0'))
|
|
return -2; /* purecov: end */
|
|
|
|
/*
|
|
As later items overwrite earlier ones, return the rightmost match!
|
|
*/
|
|
while (count > 0) {
|
|
if (0 == strcmp(ll->item[--count].key, key)) return count;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
Find the last item matching the given key in the log line.
|
|
|
|
@param ll log line
|
|
@param key the key to look for
|
|
|
|
@retval nullptr item not found
|
|
@retval otherwise pointer to the item (not a copy thereof!)
|
|
*/
|
|
log_item *log_line_item_by_name(log_line *ll, const char *key) {
|
|
int i = log_line_index_by_name(ll, key);
|
|
return (i < 0) ? nullptr : &ll->item[i];
|
|
}
|
|
|
|
/**
|
|
Find the (index of the) last key/value pair of the given type
|
|
in the log line.
|
|
|
|
@param ll log line
|
|
@param t the log item type to look for
|
|
|
|
@retval <0: none found
|
|
@retval >=0: index of the key/value pair in the log line
|
|
*/
|
|
int log_line_index_by_type(log_line *ll, log_item_type t) {
|
|
uint32 count = ll->count;
|
|
|
|
/*
|
|
As later items overwrite earlier ones, return the rightmost match!
|
|
*/
|
|
while (count > 0) {
|
|
if (ll->item[--count].type == t) return count;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
Find the (index of the) last key/value pair of the given type
|
|
in the log line. This variant accepts a reference item and looks
|
|
for an item that is of the same type (for wellknown types), or
|
|
one that is of a generic type, and with the same key name (for
|
|
generic types). For example, a reference item containing a
|
|
generic string with key "foo" will a generic string, integer, or
|
|
float with the key "foo".
|
|
|
|
@param ll log line
|
|
@param ref a reference item of the log item type to look for
|
|
|
|
@retval <0: none found
|
|
@retval >=0: index of the key/value pair in the log line
|
|
*/
|
|
int log_line_index_by_item(log_line *ll, log_item *ref) {
|
|
uint32 count = ll->count;
|
|
|
|
if (log_item_generic_type(ref->type)) {
|
|
while (count > 0) {
|
|
count--;
|
|
|
|
if (log_item_generic_type(ll->item[count].type) &&
|
|
(native_strcasecmp(ref->key, ll->item[count].key) == 0))
|
|
return count;
|
|
}
|
|
} else {
|
|
while (count > 0) {
|
|
if (ll->item[--count].type == ref->type) return count;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
Initializes a log entry for use. This simply puts it in a defined
|
|
state; if you wish to reset an existing item, see log_item_free().
|
|
|
|
@param li the log-item to initialize
|
|
*/
|
|
void log_item_init(log_item *li) { memset(li, 0, sizeof(log_item)); }
|
|
|
|
/**
|
|
Initializes an entry in a log line for use. This simply puts it in
|
|
a defined state; if you wish to reset an existing item, see
|
|
log_item_free().
|
|
This resets the element beyond the last. The element count is not
|
|
adjusted; this is for the caller to do once it sets up a valid
|
|
element to suit its needs in the cleared slot. Finally, it is up
|
|
to the caller to make sure that an element can be allocated.
|
|
|
|
@param ll the log-line to initialize a log_item in
|
|
|
|
@retval the address of the cleared log_item
|
|
*/
|
|
log_item *log_line_item_init(log_line *ll) {
|
|
log_item_init(&ll->item[ll->count]);
|
|
return &ll->item[ll->count];
|
|
}
|
|
|
|
/**
|
|
Create new log item with key name "key", and allocation flags of
|
|
"alloc" (see enum_log_item_free).
|
|
Will return a pointer to the item's log_item_data struct for
|
|
convenience.
|
|
This is mostly interesting for filters and other services that create
|
|
items that are not part of a log_line; sources etc. that intend to
|
|
create an item for a log_line (the more common case) should usually
|
|
use the below line_item_set_with_key() which creates an item (like
|
|
this function does), but also correctly inserts it into a log_line.
|
|
|
|
@param li the log_item to work on
|
|
@param t the item-type
|
|
@param key the key to set on the item.
|
|
ignored for non-generic types (may pass nullptr for those)
|
|
see alloc
|
|
@param alloc LOG_ITEM_FREE_KEY if key was allocated by caller
|
|
LOG_ITEM_FREE_NONE if key was not allocated
|
|
Allocated keys will automatically free()d when the
|
|
log_item is.
|
|
The log_item's alloc flags will be set to the
|
|
submitted value; specifically, any pre-existing
|
|
value will be clobbered. It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@retval a pointer to the log_item's log_data, for easy chaining:
|
|
log_item_set_with_key(...)->data_integer= 1;
|
|
*/
|
|
log_item_data *log_item_set_with_key(log_item *li, log_item_type t,
|
|
const char *key, uint32 alloc) {
|
|
int c = log_item_wellknown_by_type(t);
|
|
|
|
li->alloc = alloc;
|
|
if (log_item_generic_type(t)) {
|
|
li->key = key;
|
|
} else {
|
|
li->key = log_item_wellknown_keys[c].name;
|
|
DBUG_ASSERT((alloc & LOG_ITEM_FREE_KEY) == 0);
|
|
}
|
|
|
|
// If we accept a C-string as input, it'll become a Lex string internally
|
|
if ((li->item_class = log_item_wellknown_keys[c].item_class) == LOG_CSTRING)
|
|
li->item_class = LOG_LEX_STRING;
|
|
|
|
li->type = t;
|
|
|
|
DBUG_ASSERT(((alloc & LOG_ITEM_FREE_VALUE) == 0) ||
|
|
(li->item_class == LOG_CSTRING) ||
|
|
(li->item_class == LOG_LEX_STRING));
|
|
|
|
return &li->data;
|
|
}
|
|
|
|
/**
|
|
Create new log item in log line "ll", with key name "key", and
|
|
allocation flags of "alloc" (see enum_log_item_free).
|
|
On success, the number of registered items on the log line is increased,
|
|
the item's type is added to the log_line's "seen" property,
|
|
and a pointer to the item's log_item_data struct is returned for
|
|
convenience.
|
|
|
|
@param ll the log_line to work on
|
|
@param t the item-type
|
|
@param key the key to set on the item.
|
|
ignored for non-generic types (may pass nullptr for those)
|
|
see alloc
|
|
@param alloc LOG_ITEM_FREE_KEY if key was allocated by caller
|
|
LOG_ITEM_FREE_NONE if key was not allocated
|
|
Allocated keys will automatically free()d when the
|
|
log_item is.
|
|
The log_item's alloc flags will be set to the
|
|
submitted value; specifically, any pre-existing
|
|
value will be clobbered. It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@retval !nullptr a pointer to the log_item's log_data, for easy chaining:
|
|
log_line_item_set_with_key(...)->data_integer= 1;
|
|
@retval nullptr could not create a log_item in given log_line
|
|
*/
|
|
log_item_data *log_line_item_set_with_key(log_line *ll, log_item_type t,
|
|
const char *key, uint32 alloc) {
|
|
log_item *li;
|
|
|
|
if (log_line_full(ll)) return nullptr;
|
|
|
|
li = &(ll->item[ll->count]);
|
|
|
|
log_item_set_with_key(li, t, key, alloc);
|
|
ll->seen |= t;
|
|
ll->count++;
|
|
|
|
return &li->data;
|
|
}
|
|
|
|
/**
|
|
As log_item_set_with_key(), except that the key is automatically
|
|
derived from the wellknown log_item_type t.
|
|
|
|
Create new log item with type "t".
|
|
Will return a pointer to the item's log_item_data struct for
|
|
convenience.
|
|
This is mostly interesting for filters and other services that create
|
|
items that are not part of a log_line; sources etc. that intend to
|
|
create an item for a log_line (the more common case) should usually
|
|
use the below line_item_set_with_key() which creates an item (like
|
|
this function does), but also correctly inserts it into a log_line.
|
|
|
|
The allocation of this item will be LOG_ITEM_FREE_NONE;
|
|
specifically, any pre-existing value will be clobbered.
|
|
It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@param li the log_item to work on
|
|
@param t the item-type
|
|
|
|
@retval a pointer to the log_item's log_data, for easy chaining:
|
|
log_item_set_with_key(...)->data_integer= 1;
|
|
*/
|
|
log_item_data *log_item_set(log_item *li, log_item_type t) {
|
|
return log_item_set_with_key(li, t, nullptr, LOG_ITEM_FREE_NONE);
|
|
}
|
|
|
|
/**
|
|
Create a new log item of well-known type "t" in log line "ll".
|
|
On success, the number of registered items on the log line is increased,
|
|
the item's type is added to the log_line's "seen" property,
|
|
and a pointer to the item's log_item_data struct is returned for
|
|
convenience.
|
|
|
|
The allocation of this item will be LOG_ITEM_FREE_NONE;
|
|
specifically, any pre-existing value will be clobbered.
|
|
It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@param ll the log_line to work on
|
|
@param t the item-type
|
|
|
|
@retval !nullptr a pointer to the log_item's log_data, for easy chaining:
|
|
log_line_item_set(...)->data_integer= 1;
|
|
@retval nullptr could not create a log_item in given log_line
|
|
*/
|
|
log_item_data *log_line_item_set(log_line *ll, log_item_type t) {
|
|
return log_line_item_set_with_key(ll, t, nullptr, LOG_ITEM_FREE_NONE);
|
|
}
|
|
|
|
/**
|
|
Set an integer value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param i integer to set
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
bool log_item_set_int(log_item_data *lid, longlong i) {
|
|
if (lid != nullptr) {
|
|
lid->data_integer = i;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Set a floating point value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param f float to set
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
bool log_item_set_float(log_item_data *lid, double f) {
|
|
if (lid != nullptr) {
|
|
lid->data_float = f;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Set a string value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param s pointer to string
|
|
@param s_len length of string
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
bool log_item_set_lexstring(log_item_data *lid, const char *s, size_t s_len) {
|
|
if (lid != nullptr) {
|
|
lid->data_string.str = (s == nullptr) ? "" : s;
|
|
lid->data_string.length = s_len;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Set a string value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param s pointer to NTBS
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
bool log_item_set_cstring(log_item_data *lid, const char *s) {
|
|
if (lid != nullptr) {
|
|
lid->data_string.str = (s == nullptr) ? "" : s;
|
|
lid->data_string.length = strlen(lid->data_string.str);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Convenience function: Derive a log label ("error", "warning",
|
|
"information") from a severity.
|
|
|
|
@param prio the severity/prio in question
|
|
|
|
@return a label corresponding to that priority.
|
|
@retval "System" for prio of SYSTEM_LEVEL
|
|
@retval "Error" for prio of ERROR_LEVEL
|
|
@retval "Warning" for prio of WARNING_LEVEL
|
|
@retval "Note" for prio of INFORMATION_LEVEL
|
|
*/
|
|
const char *log_label_from_prio(int prio) {
|
|
switch (prio) {
|
|
case SYSTEM_LEVEL:
|
|
return "System";
|
|
case ERROR_LEVEL:
|
|
return "Error";
|
|
case WARNING_LEVEL:
|
|
return "Warning";
|
|
case INFORMATION_LEVEL:
|
|
return "Note";
|
|
default:
|
|
DBUG_ASSERT(false);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
services: log sinks: basic logging ("classic error-log")
|
|
Will write timestamp, label, thread-ID, and message to stderr/file.
|
|
If you should not be able to specify a label, one will be generated
|
|
for you from the line's priority field.
|
|
|
|
@param instance instance handle
|
|
@param ll the log line to write
|
|
|
|
@retval int number of added fields, if any
|
|
*/
|
|
static int log_sink_trad(void *instance MY_ATTRIBUTE((unused)), log_line *ll) {
|
|
const char *label = "", *msg = "";
|
|
int c, out_fields = 0;
|
|
size_t msg_len = 0, ts_len = 0, label_len = 0, subsys_len = 0;
|
|
enum loglevel prio = ERROR_LEVEL;
|
|
unsigned int errcode = 0;
|
|
log_item_type item_type = LOG_ITEM_END;
|
|
log_item_type_mask out_types = 0;
|
|
const char *iso_timestamp = "", *subsys = "";
|
|
my_thread_id thread_id = 0;
|
|
ulonglong microtime = 0;
|
|
|
|
if (ll->count > 0) {
|
|
for (c = 0; c < ll->count; c++) {
|
|
item_type = ll->item[c].type;
|
|
|
|
if (log_item_inconsistent(&ll->item[c])) continue;
|
|
|
|
out_fields++;
|
|
|
|
switch (item_type) {
|
|
case LOG_ITEM_LOG_BUFFERED:
|
|
microtime = (ulonglong)ll->item[c].data.data_integer;
|
|
break;
|
|
case LOG_ITEM_SQL_ERRCODE:
|
|
errcode = (unsigned int)ll->item[c].data.data_integer;
|
|
break;
|
|
case LOG_ITEM_LOG_PRIO:
|
|
prio = (enum loglevel)ll->item[c].data.data_integer;
|
|
break;
|
|
case LOG_ITEM_LOG_MESSAGE:
|
|
msg = ll->item[c].data.data_string.str;
|
|
msg_len = ll->item[c].data.data_string.length;
|
|
break;
|
|
case LOG_ITEM_LOG_LABEL:
|
|
label = ll->item[c].data.data_string.str;
|
|
label_len = ll->item[c].data.data_string.length;
|
|
break;
|
|
case LOG_ITEM_SRV_SUBSYS:
|
|
subsys = ll->item[c].data.data_string.str;
|
|
if ((subsys_len = ll->item[c].data.data_string.length) > 12)
|
|
subsys_len = 12;
|
|
break;
|
|
case LOG_ITEM_LOG_TIMESTAMP:
|
|
iso_timestamp = ll->item[c].data.data_string.str;
|
|
ts_len = ll->item[c].data.data_string.length;
|
|
break;
|
|
case LOG_ITEM_SRV_THREAD:
|
|
thread_id = (my_thread_id)ll->item[c].data.data_integer;
|
|
break;
|
|
default:
|
|
out_fields--;
|
|
}
|
|
out_types |= item_type;
|
|
}
|
|
|
|
/*
|
|
Prevent duplicates in incremental flush of buffered log events.
|
|
If start-up is very slow, we'll let error-log event buffering
|
|
"time out": we'll print the events so far to the traditional
|
|
error log (and only to that log, as external log-writers cannot
|
|
be loaded until later, when InnoDB, upon which the component
|
|
framework depends, has been initialized). Then, we'll go back
|
|
to buffering. Eventually, we'll either time-out and flush again,
|
|
or start-up completes, and we can do a proper flush of the
|
|
buffered error-events.
|
|
At that point, any loaded log-writers will be called for the
|
|
first time, whereas this here traditional writer may have been
|
|
called upon multiple times to give incremental updates.
|
|
Therefore, we must prevent duplicates in this, and only in this
|
|
writer. Ready? (Who lives in a time-out under the C? Buffered
|
|
events, in trad sink!)
|
|
*/
|
|
if (out_types & LOG_ITEM_LOG_BUFFERED) {
|
|
// abort if we've already printed this (to trad log only!)
|
|
if (microtime <= log_sink_trad_last) return 0;
|
|
// remember micro-time of last buffered event we've printed
|
|
log_sink_trad_last = microtime;
|
|
}
|
|
|
|
if (!(out_types & LOG_ITEM_LOG_MESSAGE)) {
|
|
msg =
|
|
"No error message, or error message of non-string type. "
|
|
"This is almost certainly a bug!";
|
|
msg_len = strlen(msg);
|
|
|
|
prio = ERROR_LEVEL; // force severity
|
|
out_types &= ~(LOG_ITEM_LOG_LABEL); // regenerate label
|
|
out_types |= LOG_ITEM_LOG_MESSAGE; // we added a message
|
|
}
|
|
|
|
{
|
|
char buff_line[LOG_BUFF_MAX];
|
|
size_t len;
|
|
|
|
if (!(out_types & LOG_ITEM_LOG_LABEL)) {
|
|
label = (prio == ERROR_LEVEL) ? "ERROR" : log_label_from_prio(prio);
|
|
label_len = strlen(label);
|
|
}
|
|
|
|
if (!(out_types & LOG_ITEM_LOG_TIMESTAMP)) {
|
|
char buff_local_time[iso8601_size];
|
|
|
|
make_iso8601_timestamp(buff_local_time, my_micro_time(),
|
|
opt_log_timestamps);
|
|
iso_timestamp = buff_local_time;
|
|
ts_len = strlen(buff_local_time);
|
|
}
|
|
|
|
/*
|
|
WL#11009 adds "error identifier" as a field in square brackets
|
|
that directly precedes the error message. As a result, new
|
|
tools can check for the presence of this field by testing
|
|
whether the first character of the presumed message field is '['.
|
|
Older tools will just consider this identifier part of the
|
|
message; this should therefore not affect log aggregation.
|
|
Tools reacting to the contents of the message may wish to
|
|
use the new field instead as it's simpler to parse.
|
|
The rules are like so:
|
|
|
|
'[' [ <namespace> ':' ] <identifier> ']'
|
|
|
|
That is, an error identifier may be namespaced by a
|
|
subsystem/component name and a ':'; the identifier
|
|
itself should be considered opaque; in particular, it
|
|
may be non-numerical: [ <alpha> | <digit> | '_' | '.' | '-' ]
|
|
*/
|
|
len = snprintf(buff_line, sizeof(buff_line),
|
|
"%.*s %u [%.*s] [MY-%06u] [%.*s] %.*s", (int)ts_len,
|
|
iso_timestamp, thread_id, (int)label_len, label, errcode,
|
|
(int)subsys_len, subsys, (int)msg_len, msg);
|
|
|
|
log_write_errstream(buff_line, len);
|
|
|
|
return out_fields; // returning number of processed items
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
services: log sinks: buffered logging
|
|
|
|
During start-up, we buffer log-info until a) we have basic info for
|
|
the built-in logger (what file to log to, verbosity, and so on), and
|
|
b) advanced info (any logging components to load, any configuration
|
|
for them, etc.).
|
|
|
|
As a failsafe, if start-up takes very, very long, and a time-out is
|
|
reached before reaching b) and we actually have something worth
|
|
reporting (e.g. errors, as opposed to info), we try to keep the user
|
|
informed by using the basic logger configured in a), while going on
|
|
buffering all info and flushing it to any advanced loggers when b)
|
|
is reached.
|
|
|
|
1) This function checks and, if needed, updates the time-out, and calls
|
|
the flush functions as needed. It is internal to the logger and should
|
|
not be called from elsewhere.
|
|
|
|
2) Function will save log-event (if given) for later filtering and output.
|
|
|
|
3) Function acquires/releases THR_LOCK_log_buffered if initialized.
|
|
|
|
@param instance instance handle
|
|
Not currently used in this writer;
|
|
if this changes later, keep in mind
|
|
that nullptr will be passed if this
|
|
is called before the structured
|
|
logger's locks are initialized, so
|
|
that must remain a valid argument!
|
|
@param ll The log line to write,
|
|
or nullptr to not add a new logline,
|
|
but to just check whether the time-out
|
|
has been reached and if so, flush
|
|
as needed.
|
|
|
|
@retval -1 can not add event to buffer (OOM?)
|
|
@retval >0 number of added fields
|
|
*/
|
|
static int log_sink_buffer(void *instance MY_ATTRIBUTE((unused)),
|
|
log_line *ll) {
|
|
log_line_buffer *llb = nullptr; ///< log-line buffer
|
|
ulonglong now = 0;
|
|
int count = 0;
|
|
|
|
/*
|
|
If we were actually given an event, add it to the buffer.
|
|
*/
|
|
if (ll != nullptr) {
|
|
if ((llb = (log_line_buffer *)my_malloc(key_memory_log_error_stack,
|
|
sizeof(log_line_buffer), MYF(0))) ==
|
|
nullptr)
|
|
return -1; /* purecov: inspected */
|
|
|
|
llb->next = nullptr;
|
|
|
|
/*
|
|
Don't let the submitter free the keys/values; we'll do it later when
|
|
the buffer is flushed and then de-allocated!
|
|
(No lock needed for copy as the target-event is still private to this
|
|
function, and the source-event is alloc'd in the caller so will be
|
|
there at least until we return.)
|
|
*/
|
|
log_line_duplicate(&llb->ll, ll);
|
|
|
|
/*
|
|
Remember it when an ERROR or SYSTEM prio event was buffered.
|
|
If buffered logging times out and the buffer contains such an event,
|
|
we force a premature flush so the user will know what's going on.
|
|
*/
|
|
{
|
|
int index_prio = log_line_index_by_type(&llb->ll, LOG_ITEM_LOG_PRIO);
|
|
|
|
if ((index_prio >= 0) &&
|
|
(llb->ll.item[index_prio].data.data_integer <= ERROR_LEVEL))
|
|
log_buffering_flushworthy = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Insert the new last event into the buffer
|
|
(a singly linked list of events).
|
|
*/
|
|
if (log_builtins_inited) mysql_mutex_lock(&THR_LOCK_log_buffered);
|
|
|
|
now = my_micro_time();
|
|
|
|
if (ll != nullptr) {
|
|
/*
|
|
Save the current time so we can regenerate the textual timestamp
|
|
later when we have the command-line options telling us what format
|
|
it should be in (e.g. UTC or system time).
|
|
*/
|
|
if (!log_line_full(&llb->ll)) {
|
|
log_line_item_set(&llb->ll, LOG_ITEM_LOG_BUFFERED)->data_integer = now;
|
|
}
|
|
|
|
*log_line_buffer_tail = llb;
|
|
log_line_buffer_tail = &(llb->next);
|
|
|
|
/*
|
|
Save the element-count now as the time-out flush below may release
|
|
the underlying log line buffer, llb, making that info inaccessible.
|
|
*/
|
|
count = llb->ll.count;
|
|
}
|
|
|
|
/*
|
|
Handle buffering time-out!
|
|
*/
|
|
|
|
// Buffering very first event; set up initial time-out ...
|
|
if (log_buffering_timeout == 0)
|
|
log_buffering_timeout =
|
|
now + (LOG_BUFFERING_TIMEOUT_AFTER * LOG_BUFFERING_TIME_SCALE);
|
|
|
|
if (log_error_stage_get() == LOG_ERROR_STAGE_BUFFERING_UNIPLEX) {
|
|
/*
|
|
When not multiplexing several log-writers into the same stream,
|
|
we need not delay.
|
|
*/
|
|
log_sink_buffer_flush(LOG_BUFFER_REPORT_AND_KEEP);
|
|
}
|
|
|
|
// Need to flush? Check on time-out, or when explicitly asked to.
|
|
else if ((now > log_buffering_timeout) || (ll == nullptr)) {
|
|
// We timed out. Flush, and set up new, possibly shorter subsequent timeout.
|
|
|
|
/*
|
|
Timing out is somewhat awkward. Ideally, it shouldn't happen at all;
|
|
but as long as it does, it is extremely unlikely to happen during early
|
|
set-up -- instead, it might happen during engine set-up ("cannot lock
|
|
DB file, another server instance already has it", "applying large
|
|
binlog", etc.).
|
|
|
|
The good news is that this means we've already parsed the command line
|
|
options; we have the timestamp format, the error log file, the verbosity,
|
|
and the suppression list (if any).
|
|
|
|
The bad news is, if the engine we're waiting for is InnoDB, which the
|
|
component framework persists its component list in, the log-writers
|
|
selected by the DBA won't have been loaded yet. This of course means
|
|
that the time-out flushes will only be available in the built-in,
|
|
"traditional" format, but at least this way, the user gets updates
|
|
during long waits. Additionally if the server exits during start-up
|
|
(i.e. before full logging is available), we'll have log info of what
|
|
happened (albeit not in the preferred format).
|
|
|
|
If start-up completes and external log-services become available,
|
|
we will flush all buffered messages to any external log-writers
|
|
requested by the user (using their preferred log-filtering set-up
|
|
as well).
|
|
*/
|
|
|
|
// If anything of sufficient priority is in the buffer ...
|
|
if (log_buffering_flushworthy) {
|
|
/*
|
|
... log it now to the traditional log, but keep the buffered
|
|
events around in case we need to write them to loadable log-sinks
|
|
later.
|
|
*/
|
|
log_sink_buffer_flush(LOG_BUFFER_REPORT_AND_KEEP);
|
|
// Remember that all high-prio events so far have now been shown.
|
|
log_buffering_flushworthy = false;
|
|
}
|
|
|
|
// Whether we've needed to flush or not, start a new window:
|
|
log_buffering_timeout =
|
|
now + (LOG_BUFFERING_TIMEOUT_EVERY * LOG_BUFFERING_TIME_SCALE);
|
|
}
|
|
|
|
if (log_builtins_inited) mysql_mutex_unlock(&THR_LOCK_log_buffered);
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
Convenience function. Same as log_sink_buffer(), except we only
|
|
check whether the time-out for buffered logging has been reached
|
|
during start-up, and act accordingly; no new logging information
|
|
is added (i.e., we only provide functionality 1 described in the
|
|
preamble for log_sink_buffer() above).
|
|
*/
|
|
void log_sink_buffer_check_timeout(void) { log_sink_buffer(nullptr, nullptr); }
|
|
|
|
/**
|
|
Process all buffered log-events.
|
|
|
|
LOG_BUFFER_DISCARD_ONLY simply releases all buffered log-events
|
|
(used when called from discard_error_log_messages()).
|
|
|
|
LOG_BUFFER_PROCESS_AND_DISCARD sends the events through the
|
|
configured error log stack first before discarding them.
|
|
(used when called from flush_error_log_messages()).
|
|
Must remain safe to call repeatedly (with subsequent calls only
|
|
outputting any events added after the previous flush).
|
|
|
|
LOG_BUFFER_REPORT_AND_KEEP is used to show incremental status
|
|
updates when the start-up takes unusually long. When that happens,
|
|
we "report" the intermediate state using the built-in sink
|
|
(as that's the only one we available to us at the time), and
|
|
"keep" the events around. That way if log sinks other that the
|
|
built-in one are configured, we can flush all of the buffered
|
|
events there once initialization completes.
|
|
(used when log_sink_buffer() detects a time-out, see also:
|
|
LOG_BUFFERING_TIMEOUT_AFTER, LOG_BUFFERING_TIMEOUT_EVERY)
|
|
|
|
@param mode LOG_BUFFER_DISCARD_ONLY (to just
|
|
throw away the buffered events), or
|
|
LOG_BUFFER_PROCESS_AND_DISCARD to
|
|
filter/print them first, or
|
|
LOG_BUFFER_REPORT_AND_KEEP to print
|
|
an intermediate report on time-out.
|
|
|
|
To use LOG_BUFFER_REPORT_AND_KEEP in a
|
|
multi-threaded environment, the caller
|
|
needs to hold THR_LOCK_log_buffered
|
|
while calling this; for the other modes,
|
|
this function will acquire and release the
|
|
lock as needed; in this second scenario,
|
|
the lock will not be held while calling
|
|
log_services (via log_line_submit()).
|
|
*/
|
|
void log_sink_buffer_flush(enum log_sink_buffer_flush_mode mode) {
|
|
log_line_buffer *llp, *local_head, *local_tail = nullptr;
|
|
|
|
/*
|
|
"steal" public list of buffered log events
|
|
|
|
The general mechanism is that we move the buffered events from
|
|
the global list to one local to this function, iterate over
|
|
it, and then put it back. If anything was added to the global
|
|
list we emptied while were working, we append the new items
|
|
from the global list to the end of the "stolen" list, and then
|
|
make that union the new global list. The grand idea here is
|
|
that this way, we only have to acquire a lock very briefly
|
|
(while updating the global list's head and tail, once for
|
|
"stealing" it, once for giving it back), rather than holding
|
|
a lock the entire time, or locking each event individually,
|
|
while still remaining safe if one caller starts a
|
|
flush-with-print, and then another runs a flush-to-delete
|
|
that might catch up and cause trouble if we neither held
|
|
a lock nor stole the list.
|
|
*/
|
|
|
|
/*
|
|
If the lock hasn't been init'd yet, don't get it.
|
|
|
|
Likewise don't get it in LOG_BUFFER_REPORT_AND_KEEP mode as
|
|
then the caller already has it. We generally only grab the lock
|
|
here when coming from log.cc's discard_error_log_messages() or
|
|
flush_error_log_messages().
|
|
*/
|
|
if (log_builtins_inited && (mode != LOG_BUFFER_REPORT_AND_KEEP))
|
|
mysql_mutex_lock(&THR_LOCK_log_buffered);
|
|
|
|
local_head = log_line_buffer_start; // save list head
|
|
log_line_buffer_start = nullptr; // empty public list
|
|
log_line_buffer_tail = &log_line_buffer_start; // adjust tail of public list
|
|
|
|
if (log_builtins_inited && (mode != LOG_BUFFER_REPORT_AND_KEEP))
|
|
mysql_mutex_unlock(&THR_LOCK_log_buffered);
|
|
|
|
// get head element from list of buffered log events
|
|
llp = local_head;
|
|
|
|
while (llp != nullptr) {
|
|
/*
|
|
Forward the buffered lines to log-writers
|
|
(other than the buffered writer), unless
|
|
we're in "discard" mode, in which case,
|
|
we'll just throw the information away.
|
|
*/
|
|
if (mode != LOG_BUFFER_DISCARD_ONLY) {
|
|
log_service_instance *lsi = log_service_instances;
|
|
|
|
// regenerate timestamp with the correct options
|
|
char local_time_buff[iso8601_size];
|
|
int index_buff = log_line_index_by_type(&llp->ll, LOG_ITEM_LOG_BUFFERED);
|
|
int index_time = log_line_index_by_type(&llp->ll, LOG_ITEM_LOG_TIMESTAMP);
|
|
ulonglong now;
|
|
|
|
if (index_buff >= 0)
|
|
now = llp->ll.item[index_buff].data.data_integer;
|
|
else // we failed to set a timestamp earlier (OOM?), use current time!
|
|
now = my_micro_time(); /* purecov: inspected */
|
|
|
|
DBUG_EXECUTE_IF("log_error_normalize", { now = 0; });
|
|
|
|
make_iso8601_timestamp(local_time_buff, now, opt_log_timestamps);
|
|
char *date = my_strndup(key_memory_log_error_stack, local_time_buff,
|
|
strlen(local_time_buff) + 1, MYF(0));
|
|
|
|
if (date != nullptr) {
|
|
if (index_time >= 0) {
|
|
// release old timestamp value
|
|
if (llp->ll.item[index_time].alloc & LOG_ITEM_FREE_VALUE) {
|
|
my_free(const_cast<char *>(
|
|
llp->ll.item[index_time].data.data_string.str));
|
|
}
|
|
// set new timestamp value
|
|
llp->ll.item[index_time].data.data_string.str = date;
|
|
llp->ll.item[index_time].data.data_string.length = strlen(date);
|
|
llp->ll.item[index_time].alloc |= LOG_ITEM_FREE_VALUE;
|
|
} else if (!log_line_full(&llp->ll)) { /* purecov: begin inspected */
|
|
// set all new timestamp key/value pair; we didn't previously have one
|
|
// This shouldn't happen unless we run out of space during submit()!
|
|
log_item_data *d =
|
|
log_line_item_set(&llp->ll, LOG_ITEM_LOG_TIMESTAMP);
|
|
if (d != nullptr) {
|
|
d->data_string.str = local_time_buff;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
llp->ll.item[llp->ll.count].alloc |= LOG_ITEM_FREE_VALUE;
|
|
llp->ll.seen |= LOG_ITEM_LOG_TIMESTAMP;
|
|
} else
|
|
my_free(date); // couldn't create key/value pair for timestamp
|
|
} else
|
|
my_free(date); // log_line is full
|
|
} /* purecov: end */
|
|
|
|
/*
|
|
If no log-service is configured (because log_builtins_init()
|
|
hasn't run and initialized the log-pipeline), or if the
|
|
log-service is the buffered writer (which is only available
|
|
internally and used during start-up), it's still early in the
|
|
start-up sequence, so we may not have all configured external
|
|
log-components yet. Therefore, and since this is a fallback
|
|
only, we ignore the configuration and use the built-in services
|
|
that we know are available, on the grounds that when something
|
|
goes wrong, information with undesired formatting is still
|
|
better than not knowing about the issue at all.
|
|
*/
|
|
if ((lsi == nullptr) || (lsi->sce->chistics & LOG_SERVICE_BUFFER)) {
|
|
/*
|
|
This is a fallback used when start-up takes very long.
|
|
|
|
In fallback modes, we run with default settings and
|
|
services.
|
|
|
|
In "keep" mode (that is, "start-up's taking a long time,
|
|
so show the user the info so far in the basic format, but
|
|
keep the log-events so we can log them properly later"),
|
|
we expect to be able to log the events with the correct
|
|
settings and services later (if start-up gets that far).
|
|
As the services we use in the meantime may change or even
|
|
remove log events, we'll let the services work on a
|
|
temporary copy of the events here. The final logging will
|
|
then use the original event. This way, no information is
|
|
lost.
|
|
*/
|
|
if (mode == LOG_BUFFER_REPORT_AND_KEEP) {
|
|
log_line temp_line; // copy the line so the filter may mangle it
|
|
|
|
log_line_duplicate(&temp_line, &llp->ll);
|
|
|
|
// Only run the built-in filter if any rules are defined.
|
|
if (log_filter_builtin_rules != nullptr)
|
|
log_builtins_filter_run(log_filter_builtin_rules, &temp_line);
|
|
|
|
// Emit to the built-in writer. Empty lines will be ignored.
|
|
log_sink_trad(nullptr, &temp_line);
|
|
|
|
log_line_item_free_all(&temp_line); // release our temporary copy
|
|
|
|
local_tail = llp;
|
|
llp = llp->next; // go to next element, keep head pointer the same
|
|
continue; // skip the free()-ing
|
|
|
|
} else { // LOG_BUFFER_PROCESS_AND_DISCARD
|
|
/*
|
|
This is a fallback used primarily when start-up is aborted.
|
|
|
|
If we get here, flush_error_log_messages() was called
|
|
before logging came out of buffered mode. (If it was
|
|
called after buffered modes completes, we should land
|
|
in the following branch instead.)
|
|
|
|
We're asked to print all log-info so far using basic
|
|
logging, and to then throw it away (rather than keep
|
|
it around for proper logging). This usually implies
|
|
that we're shutting down because some unrecoverable
|
|
situation has arisen during start-up, so a) the user
|
|
needs to know about it even if full logging (as
|
|
configured) is not available yet, and b) we'll shut
|
|
down before we'll ever get full logging, so keeping
|
|
the info around is pointless.
|
|
*/
|
|
|
|
if (log_filter_builtin_rules != nullptr)
|
|
log_builtins_filter_run(log_filter_builtin_rules, &llp->ll);
|
|
|
|
log_sink_trad(nullptr, &llp->ll);
|
|
}
|
|
} else { // !LOG_SERVICE_BUFFER
|
|
/*
|
|
If we get here, logging has left the buffered phase, and
|
|
we can write out the log-events using the configuration
|
|
requested by the user, as it should be!
|
|
*/
|
|
log_line_submit(&llp->ll); // frees keys + values (but not llp itself)
|
|
goto kv_freed;
|
|
}
|
|
}
|
|
|
|
log_line_item_free_all(&local_head->ll); // free key/value pairs
|
|
kv_freed:
|
|
local_head = local_head->next; // delist event
|
|
my_free(llp); // free buffered event
|
|
llp = local_head;
|
|
}
|
|
|
|
if (log_builtins_inited && (mode != LOG_BUFFER_REPORT_AND_KEEP))
|
|
mysql_mutex_lock(&THR_LOCK_log_buffered);
|
|
|
|
/*
|
|
At this point, if (local_head == nullptr), we either didn't have
|
|
a list to begin with, or we just emptied the local version.
|
|
Since we also emptied the global version at the top, whatever's
|
|
in there now (still empty, or new events attached while we were
|
|
processing) is now authoritive, and no change is needed here.
|
|
|
|
If local_head is non-NULL, we started with a non-empty local list
|
|
and mode was KEEP. In that case, we merge the local list back into
|
|
the global one:
|
|
*/
|
|
|
|
if (local_head != nullptr) {
|
|
DBUG_ASSERT(local_tail != nullptr);
|
|
|
|
/*
|
|
Append global list to end of local list. If global list is still
|
|
empty, the global tail pointer points at the global anchor, so
|
|
we set the global tail pointer to the next-pointer in last stolen
|
|
item. If global list was non-empty, the tail pointer already
|
|
points at the correct element's next-pointer (the correct element
|
|
being the last one in the global list, as we'll append the
|
|
global list to the end of the local list).
|
|
*/
|
|
if ((local_tail->next = log_line_buffer_start) == nullptr)
|
|
log_line_buffer_tail = &local_tail->next;
|
|
|
|
/*
|
|
Return the stolen list, with any log-events that happened while
|
|
we were processing appended to its end.
|
|
*/
|
|
log_line_buffer_start = local_head;
|
|
}
|
|
|
|
if (log_builtins_inited && (mode != LOG_BUFFER_REPORT_AND_KEEP))
|
|
mysql_mutex_unlock(&THR_LOCK_log_buffered);
|
|
}
|
|
|
|
/**
|
|
Complete, filter, and write submitted log items.
|
|
|
|
This expects a log_line collection of log-related key/value pairs,
|
|
e.g. from log_message().
|
|
|
|
Where missing, timestamp, priority, thread-ID (if any) and so forth
|
|
are added.
|
|
|
|
Log item source services, log item filters, and log item sinks are
|
|
then called.
|
|
|
|
@param ll key/value pairs describing info to log
|
|
|
|
@retval int number of fields in created log line
|
|
*/
|
|
int log_line_submit(log_line *ll) {
|
|
log_item_iter iter_save;
|
|
|
|
DBUG_TRACE;
|
|
|
|
/*
|
|
The log-services we'll call below are likely to change the default
|
|
iter. Since log-services are allowed to call the logger, we'll save
|
|
the iter on entry and restore it on exit to be properly re-entrant in
|
|
that regard.
|
|
*/
|
|
iter_save = ll->iter;
|
|
ll->iter.ll = nullptr;
|
|
|
|
/*
|
|
If anything of what was submitted survived, proceed ...
|
|
*/
|
|
if (ll->count > 0) {
|
|
THD *thd = nullptr;
|
|
|
|
// avoid some allocs/frees.
|
|
char local_time_buff[iso8601_size];
|
|
char strerr_buf[MYSYS_STRERROR_SIZE];
|
|
|
|
/* auto-add a prio item */
|
|
if (!(ll->seen & LOG_ITEM_LOG_PRIO) && !log_line_full(ll)) {
|
|
log_line_item_set(ll, LOG_ITEM_LOG_PRIO)->data_integer = ERROR_LEVEL;
|
|
}
|
|
|
|
/* auto-add a timestamp item if needed */
|
|
if (!(ll->seen & LOG_ITEM_LOG_TIMESTAMP) && !log_line_full(ll)) {
|
|
log_item_data *d;
|
|
ulonglong now = my_micro_time();
|
|
|
|
DBUG_EXECUTE_IF("log_error_normalize", { now = 0; });
|
|
|
|
make_iso8601_timestamp(local_time_buff, now, opt_log_timestamps);
|
|
|
|
d = log_line_item_set(ll, LOG_ITEM_LOG_TIMESTAMP);
|
|
d->data_string.str = local_time_buff;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
}
|
|
|
|
/* auto-add a strerror item if relevant and available */
|
|
if (!(ll->seen & LOG_ITEM_SYS_STRERROR) && !log_line_full(ll) &&
|
|
(ll->seen & LOG_ITEM_SYS_ERRNO)) {
|
|
int en; // operating system errno
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SYS_ERRNO);
|
|
log_item_data *d = log_line_item_set(ll, LOG_ITEM_SYS_STRERROR);
|
|
|
|
DBUG_ASSERT(n >= 0);
|
|
|
|
en = (int)ll->item[n].data.data_integer;
|
|
my_strerror(strerr_buf, sizeof(strerr_buf), en);
|
|
d->data_string.str = strerr_buf;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
}
|
|
|
|
/* add thread-related info, if available */
|
|
if ((thd = current_thd) != nullptr) {
|
|
/* auto-add a thread item if needed */
|
|
if (!(ll->seen & LOG_ITEM_SRV_THREAD) && !log_line_full(ll)) {
|
|
my_thread_id tid = log_get_thread_id(thd);
|
|
|
|
DBUG_EXECUTE_IF("log_error_normalize", { tid = 0; });
|
|
|
|
log_line_item_set(ll, LOG_ITEM_SRV_THREAD)->data_integer = tid;
|
|
}
|
|
}
|
|
|
|
/* auto-add a symbolic MySQL error code item item if needed */
|
|
if (!(ll->seen & LOG_ITEM_SQL_ERRSYMBOL) && !log_line_full(ll) &&
|
|
(ll->seen & LOG_ITEM_SQL_ERRCODE)) {
|
|
int ec; // MySQL error code
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SQL_ERRCODE);
|
|
const char *es;
|
|
|
|
DBUG_ASSERT(n >= 0);
|
|
|
|
ec = (int)ll->item[n].data.data_integer;
|
|
if ((ec != 0) && ((es = mysql_errno_to_symbol(ec)) != nullptr)) {
|
|
log_item_data *d = log_line_item_set(ll, LOG_ITEM_SQL_ERRSYMBOL);
|
|
d->data_string.str = es;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
}
|
|
}
|
|
/* auto-add a numeric MySQL error code item item if needed */
|
|
else if (!(ll->seen & LOG_ITEM_SQL_ERRCODE) && !log_line_full(ll) &&
|
|
(ll->seen & LOG_ITEM_SQL_ERRSYMBOL)) {
|
|
const char *es; // MySQL error symbol
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SQL_ERRSYMBOL);
|
|
int ec;
|
|
|
|
DBUG_ASSERT(n >= 0);
|
|
|
|
es = ll->item[n].data.data_string.str;
|
|
|
|
DBUG_ASSERT(es != nullptr);
|
|
|
|
if ((ec = mysql_symbol_to_errno(es)) > 0) {
|
|
log_item_data *d = log_line_item_set(ll, LOG_ITEM_SQL_ERRCODE);
|
|
d->data_integer = ec;
|
|
}
|
|
}
|
|
|
|
/* auto-add a SQL state item item if needed */
|
|
if (!(ll->seen & LOG_ITEM_SQL_STATE) && !log_line_full(ll) &&
|
|
(ll->seen & LOG_ITEM_SQL_ERRCODE)) {
|
|
int ec; // MySQL error code
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SQL_ERRCODE);
|
|
const char *es;
|
|
|
|
if (n < 0) {
|
|
n = log_line_index_by_type(ll, LOG_ITEM_SQL_ERRSYMBOL);
|
|
DBUG_ASSERT(n >= 0);
|
|
|
|
es = ll->item[n].data.data_string.str;
|
|
DBUG_ASSERT(es != nullptr);
|
|
|
|
ec = mysql_symbol_to_errno(es);
|
|
} else
|
|
ec = (int)ll->item[n].data.data_integer;
|
|
|
|
if ((ec > 0) && ((es = mysql_errno_to_sqlstate((uint)ec)) != nullptr)) {
|
|
log_item_data *d = log_line_item_set(ll, LOG_ITEM_SQL_STATE);
|
|
d->data_string.str = es;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
}
|
|
}
|
|
|
|
/* add the default sub-system if none is set */
|
|
if (!(ll->seen & LOG_ITEM_SRV_SUBSYS) && !log_line_full(ll)) {
|
|
log_item_data *d = log_line_item_set(ll, LOG_ITEM_SRV_SUBSYS);
|
|
d->data_string.str = LOG_SUBSYSTEM_TAG;
|
|
d->data_string.length = strlen(d->data_string.str);
|
|
}
|
|
|
|
/* normalize source line if needed */
|
|
DBUG_EXECUTE_IF("log_error_normalize", {
|
|
if (ll->seen & LOG_ITEM_SRC_LINE) {
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SRC_LINE);
|
|
|
|
if (n >= 0) {
|
|
ll->item[n] = ll->item[ll->count - 1];
|
|
ll->count--;
|
|
ll->seen &= ~LOG_ITEM_SRC_LINE;
|
|
}
|
|
}
|
|
});
|
|
|
|
/*
|
|
We were called before even the buffered sink (and our locks)
|
|
were set up. This usually means that something went
|
|
catastrophically wrong, so we'll make sure the information
|
|
(e.g. cause of failure) isn't lost.
|
|
*/
|
|
if (!log_builtins_inited)
|
|
log_sink_buffer(nullptr, ll);
|
|
|
|
else {
|
|
mysql_rwlock_rdlock(&THR_LOCK_log_stack);
|
|
|
|
/*
|
|
sources:
|
|
Add info from other log item sources,
|
|
e.g. that supplied by the client on connect using mysql_options4();
|
|
|
|
filters:
|
|
Remove or modify entries
|
|
|
|
sinks:
|
|
Write logs
|
|
*/
|
|
|
|
log_service_cache_entry *sce;
|
|
log_service_instance *lsi = log_service_instances;
|
|
|
|
while ((lsi != nullptr) && ((sce = lsi->sce) != nullptr)) {
|
|
if (sce->chistics & LOG_SERVICE_BUFFER)
|
|
log_sink_buffer(lsi->instance, ll);
|
|
|
|
else if (!(sce->chistics & LOG_SERVICE_BUILTIN)) {
|
|
SERVICE_TYPE(log_service) * ls;
|
|
|
|
ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(sce->service);
|
|
|
|
if (ls != nullptr) ls->run(lsi->instance, ll);
|
|
|
|
} else if (log_service_has_characteristics(
|
|
sce, (LOG_SERVICE_BUILTIN | LOG_SERVICE_FILTER)))
|
|
log_builtins_filter_run(log_filter_builtin_rules, ll);
|
|
|
|
else if (log_service_has_characteristics(
|
|
sce, (LOG_SERVICE_BUILTIN | LOG_SERVICE_SINK)))
|
|
log_sink_trad(lsi->instance, ll);
|
|
|
|
lsi = lsi->next;
|
|
}
|
|
|
|
mysql_rwlock_unlock(&THR_LOCK_log_stack);
|
|
}
|
|
|
|
#if !defined(DBUG_OFF)
|
|
/*
|
|
Assert that we're not given anything but server error-log codes
|
|
or global error codes (shared between MySQL server and clients).
|
|
If your code bombs out here, check whether you're trying to log
|
|
using an error-code in the range intended for messages that are
|
|
sent to the client, not the error-log, (< ER_SERVER_RANGE_START).
|
|
*/
|
|
if (ll->seen & LOG_ITEM_SQL_ERRCODE) {
|
|
int n = log_line_index_by_type(ll, LOG_ITEM_SQL_ERRCODE);
|
|
if (n >= 0) {
|
|
int ec = (int)ll->item[n].data.data_integer;
|
|
DBUG_ASSERT((ec < 1) || (ec >= EE_ERROR_FIRST && ec <= EE_ERROR_LAST) ||
|
|
(ec >= ER_SERVER_RANGE_START));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// release any memory that might need it
|
|
log_line_item_free_all(ll);
|
|
}
|
|
|
|
ll->iter = iter_save;
|
|
|
|
return ll->count;
|
|
}
|
|
|
|
/**
|
|
Make and return an ISO 8601 / RFC 3339 compliant timestamp.
|
|
Heeds log_timestamps.
|
|
|
|
@param buf A buffer of at least 26 bytes to store the timestamp in
|
|
(19 + tzinfo tail + \0)
|
|
@param utime Microseconds since the epoch
|
|
@param mode if 0, use UTC; if 1, use local time
|
|
|
|
@retval length of timestamp (excluding \0)
|
|
*/
|
|
int make_iso8601_timestamp(char *buf, ulonglong utime, int mode) {
|
|
struct tm my_tm;
|
|
char tzinfo[8] = "Z"; // max 6 chars plus \0
|
|
size_t len;
|
|
time_t seconds;
|
|
|
|
seconds = utime / 1000000;
|
|
utime = utime % 1000000;
|
|
|
|
if (mode == 0)
|
|
gmtime_r(&seconds, &my_tm);
|
|
else if (mode == 1) {
|
|
localtime_r(&seconds, &my_tm);
|
|
|
|
#ifdef HAVE_TM_GMTOFF
|
|
/*
|
|
The field tm_gmtoff is the offset (in seconds) of the time represented
|
|
from UTC, with positive values indicating east of the Prime Meridian.
|
|
Originally a BSDism, this is also supported in glibc, so this should
|
|
cover the majority of our platforms.
|
|
*/
|
|
long tim = -my_tm.tm_gmtoff;
|
|
#else
|
|
/*
|
|
Work this out "manually".
|
|
*/
|
|
struct tm my_gm;
|
|
long tim, gm;
|
|
gmtime_r(&seconds, &my_gm);
|
|
gm = (my_gm.tm_sec + 60 * (my_gm.tm_min + 60 * my_gm.tm_hour));
|
|
tim = (my_tm.tm_sec + 60 * (my_tm.tm_min + 60 * my_tm.tm_hour));
|
|
tim = gm - tim;
|
|
#endif
|
|
char dir = '-';
|
|
|
|
if (tim < 0) {
|
|
dir = '+';
|
|
tim = -tim;
|
|
}
|
|
snprintf(tzinfo, sizeof(tzinfo), "%c%02d:%02d", dir, (int)(tim / (60 * 60)),
|
|
(int)((tim / 60) % 60));
|
|
} else {
|
|
DBUG_ASSERT(false);
|
|
}
|
|
|
|
len = snprintf(buf, iso8601_size, "%04d-%02d-%02dT%02d:%02d:%02d.%06lu%s",
|
|
my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
|
|
my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec,
|
|
(unsigned long)utime, tzinfo);
|
|
|
|
return std::min<int>((int)len, iso8601_size - 1);
|
|
}
|
|
|
|
/**
|
|
Helper: get token from error stack configuration string
|
|
|
|
@param[in,out] s start of the token (may be positioned on whitespace
|
|
on call; this will be adjusted to the first non-white
|
|
character)
|
|
@param[out] e end of the token
|
|
@param[in,out] d delimiter (in: last used, \0 if none; out: detected here)
|
|
|
|
@retval <0 an error occur
|
|
@retval the length in bytes of the token
|
|
*/
|
|
static size_t log_builtins_stack_get_service_from_var(const char **s,
|
|
const char **e, char *d) {
|
|
DBUG_ASSERT(s != nullptr);
|
|
DBUG_ASSERT(e != nullptr);
|
|
|
|
// proceed to next service (skip whitespace, and the delimiter once defined)
|
|
while (isspace(**s) || ((*d != '\0') && (**s == *d))) (*s)++;
|
|
|
|
*e = *s;
|
|
|
|
// find end of service
|
|
while ((**e != '\0') && !isspace(**e)) {
|
|
if ((**e == ';') || (**e == ',')) {
|
|
if (*d == '\0') // no delimiter determined yet
|
|
{
|
|
if (*e == *s) // token may not start with a delimiter
|
|
return -1;
|
|
*d = **e; // save the delimiter we found
|
|
} else if (**e != *d) // different delimiter than last time: error
|
|
return -2;
|
|
}
|
|
if (**e == *d) // found a valid delimiter; end scan
|
|
goto done;
|
|
(*e)++; // valid part of token found, go on!
|
|
}
|
|
|
|
done:
|
|
return (size_t)(*e - *s);
|
|
}
|
|
|
|
/**
|
|
Look up a log service by name (in the service registry).
|
|
|
|
@param name name of the component
|
|
@param len length of that name
|
|
|
|
@retval a handle to that service.
|
|
*/
|
|
static my_h_service log_service_get_by_name(const char *name, size_t len) {
|
|
char buf[128];
|
|
my_h_service service = nullptr;
|
|
size_t needed;
|
|
|
|
needed =
|
|
snprintf(buf, sizeof(buf), LOG_SERVICES_PREFIX ".%.*s", (int)len, name);
|
|
|
|
if (needed > sizeof(buf)) return service;
|
|
|
|
if ((!imp_mysql_server_registry.acquire(buf, &service)) &&
|
|
(service != nullptr)) {
|
|
SERVICE_TYPE(log_service) * ls;
|
|
|
|
if ((ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(service)) ==
|
|
nullptr) {
|
|
imp_mysql_server_registry.release(service);
|
|
service = nullptr;
|
|
}
|
|
} else
|
|
service = nullptr;
|
|
|
|
return service;
|
|
}
|
|
|
|
void log_service_cache_entry_free::operator()(
|
|
log_service_cache_entry *sce) const {
|
|
if (sce == nullptr) return;
|
|
|
|
if (sce->name != nullptr) my_free(sce->name);
|
|
|
|
DBUG_ASSERT(sce->opened == 0);
|
|
|
|
if (sce->service != nullptr) imp_mysql_server_registry.release(sce->service);
|
|
|
|
memset(sce, 0, sizeof(log_service_cache_entry));
|
|
|
|
my_free(sce);
|
|
}
|
|
|
|
/**
|
|
Create a new entry in the cache of log services.
|
|
|
|
@param name Name of component that provides the service
|
|
@param name_len Length of that name
|
|
@param srv The handle of the log_service
|
|
|
|
@retval A new log_service_cache_entry on success
|
|
@retval nullptr on failure
|
|
*/
|
|
static log_service_cache_entry *log_service_cache_entry_new(const char *name,
|
|
size_t name_len,
|
|
my_h_service srv) {
|
|
char *n = my_strndup(key_memory_log_error_stack, name, name_len, MYF(0));
|
|
log_service_cache_entry *sce = nullptr;
|
|
|
|
if (n != nullptr) {
|
|
// make new service cache entry
|
|
if ((sce = (log_service_cache_entry *)my_malloc(
|
|
key_memory_log_error_stack, sizeof(log_service_cache_entry),
|
|
MYF(0))) == nullptr)
|
|
my_free(n);
|
|
else {
|
|
memset(sce, 0, sizeof(log_service_cache_entry));
|
|
sce->name = n;
|
|
sce->name_len = name_len;
|
|
sce->service = srv;
|
|
sce->chistics = LOG_SERVICE_UNSPECIFIED;
|
|
sce->requested = 0;
|
|
sce->opened = 0;
|
|
}
|
|
}
|
|
|
|
return sce;
|
|
}
|
|
|
|
/**
|
|
Find out characteristics of a service (e.g. whether it is a singleton)
|
|
by asking it.
|
|
|
|
(See log_service_chistics for a list of possible characteristics!)
|
|
|
|
@param service what service to examine
|
|
|
|
@retval a set of log_service_chistics flags
|
|
*/
|
|
static int log_service_get_characteristics(my_h_service service) {
|
|
SERVICE_TYPE(log_service) * ls;
|
|
|
|
DBUG_ASSERT(service != nullptr);
|
|
|
|
ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(service);
|
|
|
|
// no information available, default to restrictive
|
|
if (ls->characteristics == nullptr)
|
|
return LOG_SERVICE_UNSPECIFIED |
|
|
LOG_SERVICE_SINGLETON; /* purecov: inspected */
|
|
|
|
return ls->characteristics();
|
|
}
|
|
|
|
/**
|
|
Allocate and open a new instance of a given service.
|
|
|
|
@param sce the cache-entry for the service
|
|
@param ll a log_line containing optional parameters, or nullptr
|
|
|
|
@return a pointer to an instance record or success, nullptr otherwise
|
|
*/
|
|
log_service_instance *log_service_instance_new(log_service_cache_entry *sce,
|
|
log_line *ll) {
|
|
log_service_instance *lsi;
|
|
|
|
// make new service instance entry
|
|
if ((lsi = (log_service_instance *)my_malloc(
|
|
key_memory_log_error_stack, sizeof(log_service_instance), MYF(0))) !=
|
|
nullptr) {
|
|
memset(lsi, 0, sizeof(log_service_instance));
|
|
lsi->sce = sce;
|
|
|
|
DBUG_ASSERT(sce != nullptr);
|
|
|
|
if (lsi->sce->service != nullptr) {
|
|
SERVICE_TYPE(log_service) *ls = nullptr;
|
|
|
|
ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(lsi->sce->service);
|
|
|
|
if ((ls == nullptr) ||
|
|
((ls->open != nullptr) && (ls->open(ll, &lsi->instance) < 0)))
|
|
goto fail;
|
|
}
|
|
|
|
lsi->sce->opened++;
|
|
}
|
|
|
|
return lsi;
|
|
|
|
fail:
|
|
my_free(lsi);
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
Close and release all instances of all log services.
|
|
*/
|
|
static void log_service_instance_release_all() {
|
|
log_service_instance *lsi, *lsi_next;
|
|
|
|
lsi = log_service_instances;
|
|
log_service_instances = nullptr;
|
|
|
|
// release all instances!
|
|
while (lsi != nullptr) {
|
|
SERVICE_TYPE(log_service) * ls;
|
|
|
|
ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(lsi->sce->service);
|
|
|
|
if (ls != nullptr) {
|
|
if (ls->close != nullptr) ls->close(&lsi->instance);
|
|
}
|
|
|
|
lsi->sce->opened--;
|
|
lsi_next = lsi->next;
|
|
my_free(lsi);
|
|
lsi = lsi_next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Call flush() on all log_services.
|
|
flush() function must not try to log anything, as we hold an
|
|
exclusive lock on the stack.
|
|
|
|
@retval 0 no problems
|
|
@retval -1 error
|
|
*/
|
|
int log_builtins_error_stack_flush() {
|
|
int rr = 0;
|
|
log_service_cache_entry *sce;
|
|
log_service_instance *lsi;
|
|
|
|
if (!log_builtins_inited) return 0;
|
|
|
|
mysql_rwlock_wrlock(&THR_LOCK_log_stack);
|
|
|
|
lsi = log_service_instances;
|
|
|
|
while ((lsi != nullptr) && ((sce = lsi->sce) != nullptr)) {
|
|
if (!(sce->chistics & LOG_SERVICE_BUILTIN)) {
|
|
SERVICE_TYPE(log_service) *ls = nullptr;
|
|
ls = reinterpret_cast<SERVICE_TYPE(log_service) *>(sce->service);
|
|
|
|
if (ls != nullptr) {
|
|
if ((ls->flush != nullptr) && (ls->flush(&lsi->instance) < 0)) rr--;
|
|
} else
|
|
rr--;
|
|
}
|
|
lsi = lsi->next;
|
|
}
|
|
|
|
mysql_rwlock_unlock(&THR_LOCK_log_stack);
|
|
|
|
return rr;
|
|
}
|
|
|
|
/**
|
|
Set up custom error logging stack.
|
|
|
|
@param conf The configuration string
|
|
@param check_only If true, report on whether configuration is valid
|
|
(i.e. whether all requested services are available),
|
|
but do not apply the new configuration.
|
|
if false, set the configuration (acquire the
|
|
necessary services, update the hash by
|
|
adding/deleting entries as necessary)
|
|
@param[out] pos If an error occurs and this pointer is non-null,
|
|
the position in the configuration string where
|
|
the error occurred will be written to the
|
|
pointed-to size_t.
|
|
|
|
@retval 0 success
|
|
@retval -1 expected delimiter not found
|
|
@retval -2 one or more services not found
|
|
@retval -3 failed to create service cache entry
|
|
@retval -4 tried to open multiple instances of a singleton
|
|
@retval -5 failed to create service instance entry
|
|
@retval -6 last element in pipeline should be a sink
|
|
@retval -7 service only available during start-up;
|
|
may not be set by the user
|
|
@retval -101 service name may not start with a delimiter
|
|
@retval -102 delimiters ',' and ';' may not be mixed
|
|
*/
|
|
int log_builtins_error_stack(const char *conf, bool check_only, size_t *pos) {
|
|
const char *start = conf, *end;
|
|
char delim = '\0';
|
|
long len;
|
|
my_h_service service;
|
|
int rr = 0;
|
|
int count = 0;
|
|
log_service_cache_entry *sce = nullptr;
|
|
log_service_instance *lsi;
|
|
int chistics = LOG_SERVICE_UNSPECIFIED;
|
|
|
|
mysql_rwlock_wrlock(&THR_LOCK_log_stack);
|
|
|
|
// if we're actually setting this configuration, release the previous one!
|
|
if (!check_only) log_service_instance_release_all();
|
|
|
|
// clear keep flag on all service cache entries
|
|
for (auto &key_and_value : *log_service_cache) {
|
|
sce = key_and_value.second.get();
|
|
sce->requested = 0;
|
|
|
|
DBUG_ASSERT(check_only || (sce->opened == 0));
|
|
}
|
|
|
|
sce = nullptr;
|
|
|
|
lsi = nullptr;
|
|
while ((len = log_builtins_stack_get_service_from_var(&start, &end, &delim)) >
|
|
0) {
|
|
chistics = LOG_SERVICE_UNSPECIFIED;
|
|
|
|
// more than one services listed, but no delimiter used (only space)
|
|
if ((++count > 1) && (delim == '\0')) {
|
|
rr = -1; // at least one service not found => fail
|
|
goto done;
|
|
}
|
|
|
|
// find current service name in service-cache
|
|
auto it = log_service_cache->find(string(start, len));
|
|
|
|
// not found in cache; is it a built-in default?
|
|
if (it == log_service_cache->end()) {
|
|
chistics = log_service_check_if_builtin(start, len);
|
|
|
|
/*
|
|
Buffered logging is only used during start-up.
|
|
As we set it up internally from a constant, we don't
|
|
check the value first, but go straight to the set phase.
|
|
If we do see the special sink during the check phase,
|
|
it was requested by the user. That is not supported
|
|
(as it isn't useful), so we throw an error here.
|
|
*/
|
|
if (check_only && (chistics & LOG_SERVICE_BUFFER)) {
|
|
rr = -7;
|
|
goto done;
|
|
}
|
|
|
|
// not a built-in; ask component framework
|
|
if (!(chistics & LOG_SERVICE_BUILTIN)) {
|
|
service = log_service_get_by_name(start, len);
|
|
|
|
// not found in framework, signal failure
|
|
if (service == nullptr) {
|
|
rr = -2; // at least one service not found => fail
|
|
goto done;
|
|
}
|
|
} else
|
|
service = nullptr;
|
|
|
|
// make a cache-entry for this service
|
|
if ((sce = log_service_cache_entry_new(start, len, service)) == nullptr) {
|
|
// failed to make cache-entry. if we hold a service handle, release it!
|
|
/* purecov: begin inspected */
|
|
if (service != nullptr) imp_mysql_server_registry.release(service);
|
|
rr = -3;
|
|
goto done; /* purecov: end */
|
|
}
|
|
|
|
// service is not built-in, so we know nothing about it. Ask it!
|
|
if ((sce->chistics = chistics) == LOG_SERVICE_UNSPECIFIED)
|
|
sce->chistics =
|
|
log_service_get_characteristics(service) &
|
|
~LOG_SERVICE_BUILTIN; // loaded service can not be built-in
|
|
|
|
log_service_cache->emplace(string(sce->name, sce->name_len),
|
|
cache_entry_with_deleter(sce));
|
|
} else
|
|
sce = it->second.get();
|
|
|
|
// at this point, it's in cache, one way or another
|
|
sce->requested++;
|
|
|
|
if (check_only) {
|
|
// tried to multi-open a service that doesn't support it => fail
|
|
if ((sce->requested > 1) && (sce->chistics & LOG_SERVICE_SINGLETON)) {
|
|
rr = -4;
|
|
goto done;
|
|
}
|
|
} else if ((sce->requested == 1) ||
|
|
!(sce->chistics & LOG_SERVICE_SINGLETON)) {
|
|
log_service_instance *lsi_new = nullptr;
|
|
|
|
// actually setting this config, so open this instance!
|
|
lsi_new = log_service_instance_new(sce, nullptr);
|
|
|
|
if (lsi_new != nullptr) // add to chain of instances
|
|
{
|
|
if (log_service_instances == nullptr)
|
|
log_service_instances = lsi_new;
|
|
else {
|
|
DBUG_ASSERT(lsi != nullptr);
|
|
lsi->next = lsi_new;
|
|
}
|
|
|
|
lsi = lsi_new;
|
|
} else // could not make new instance entry; fail
|
|
{
|
|
rr = -5; /* purecov: inspected */
|
|
goto done; /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
/*
|
|
If neither branch was true, we're in set mode, but the set-up
|
|
is invalid (i.e. we're trying to multi-open a singleton). As
|
|
this should have been caught in the check phase, we don't
|
|
specfically handle it here; the invalid element is skipped and
|
|
not added to the instance list; that way, we'll get as close
|
|
to a working configuration as possible in our attempt to fail
|
|
somewhat gracefully.
|
|
*/
|
|
|
|
start = end;
|
|
}
|
|
|
|
if (len < 0)
|
|
rr = len - 100;
|
|
else if ((sce != nullptr) && !(sce->chistics & LOG_SERVICE_SINK))
|
|
rr = -6;
|
|
else
|
|
rr = 0;
|
|
|
|
done:
|
|
// remove stale entries from cache
|
|
for (auto it = log_service_cache->begin(); it != log_service_cache->end();) {
|
|
sce = it->second.get();
|
|
|
|
if (sce->opened <= 0)
|
|
it = log_service_cache->erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
|
|
mysql_rwlock_unlock(&THR_LOCK_log_stack);
|
|
|
|
if (pos != nullptr) *pos = (size_t)(start - conf);
|
|
|
|
return rr;
|
|
}
|
|
|
|
/**
|
|
De-initialize the structured logging subsystem.
|
|
|
|
@retval 0 no errors
|
|
@retval -1 not stopping, never started
|
|
*/
|
|
int log_builtins_exit() {
|
|
if (!log_builtins_inited) return -1;
|
|
|
|
mysql_rwlock_wrlock(&THR_LOCK_log_stack);
|
|
mysql_mutex_lock(&THR_LOCK_log_buffered);
|
|
mysql_mutex_lock(&THR_LOCK_log_syseventlog);
|
|
|
|
log_builtins_filter_exit();
|
|
log_service_instance_release_all();
|
|
delete log_service_cache;
|
|
|
|
log_builtins_inited = false;
|
|
log_error_stage_set(LOG_ERROR_STAGE_BUFFERING_EARLY);
|
|
|
|
mysql_rwlock_unlock(&THR_LOCK_log_stack);
|
|
mysql_rwlock_destroy(&THR_LOCK_log_stack);
|
|
|
|
mysql_mutex_unlock(&THR_LOCK_log_syseventlog);
|
|
mysql_mutex_destroy(&THR_LOCK_log_syseventlog);
|
|
|
|
mysql_mutex_unlock(&THR_LOCK_log_buffered);
|
|
mysql_mutex_destroy(&THR_LOCK_log_buffered);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Initialize the structured logging subsystem.
|
|
|
|
Since we're initializing various locks here, we must call this late enough
|
|
so this is clean, but early enough so it still happens while we're running
|
|
single-threaded -- this specifically also means we must call it before we
|
|
start plug-ins / storage engines / external components!
|
|
|
|
@retval 0 no errors
|
|
@retval -1 couldn't initialize stack lock
|
|
@retval -2 couldn't initialize built-in default filter
|
|
@retval -3 couldn't set up service hash
|
|
@retval -4 couldn't initialize syseventlog lock
|
|
@retval -5 couldn't set service pipeline
|
|
@retval -6 couldn't initialize buffered logging lock
|
|
*/
|
|
int log_builtins_init() {
|
|
int rr = 0;
|
|
|
|
DBUG_ASSERT(!log_builtins_inited);
|
|
|
|
// Reset flag. This is *also* set on definition, this is intentional.
|
|
log_buffering_flushworthy = false;
|
|
|
|
if (mysql_rwlock_init(0, &THR_LOCK_log_stack)) return -1;
|
|
|
|
if (mysql_mutex_init(0, &THR_LOCK_log_syseventlog, MY_MUTEX_INIT_FAST)) {
|
|
mysql_rwlock_destroy(&THR_LOCK_log_stack);
|
|
return -4;
|
|
}
|
|
|
|
if (mysql_mutex_init(0, &THR_LOCK_log_buffered, MY_MUTEX_INIT_FAST)) {
|
|
rr = -6;
|
|
goto fail;
|
|
}
|
|
|
|
mysql_rwlock_wrlock(&THR_LOCK_log_stack);
|
|
|
|
if (log_builtins_filter_init())
|
|
rr = -2;
|
|
else {
|
|
log_service_cache =
|
|
new collation_unordered_map<string, cache_entry_with_deleter>(
|
|
system_charset_info, 0);
|
|
if (log_service_cache == nullptr) rr = -3;
|
|
}
|
|
|
|
log_service_instances = nullptr;
|
|
|
|
mysql_rwlock_unlock(&THR_LOCK_log_stack);
|
|
|
|
if (rr >= 0) {
|
|
if (log_builtins_error_stack(LOG_BUILTINS_BUFFER, false, nullptr) >= 0) {
|
|
log_error_stage_set(LOG_ERROR_STAGE_BUFFERING_EARLY);
|
|
log_builtins_inited = true;
|
|
return 0;
|
|
} else {
|
|
rr = -5; /* purecov: inspected */
|
|
DBUG_ASSERT(false); /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
fail:
|
|
mysql_rwlock_destroy(&THR_LOCK_log_stack); /* purecov: begin inspected */
|
|
mysql_mutex_destroy(&THR_LOCK_log_syseventlog);
|
|
|
|
return rr; /* purecov: end */
|
|
}
|
|
|
|
/*
|
|
Service: helpers for logging. Mostly accessors for log events.
|
|
See include/mysql/components/services/log_builtins.h for more information.
|
|
*/
|
|
|
|
/**
|
|
See whether a type is wellknown.
|
|
|
|
@param t log item type to examine
|
|
|
|
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
|
|
@retval >0: index in array of wellknowns
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::wellknown_by_type, (log_item_type t)) {
|
|
return log_item_wellknown_by_type(t);
|
|
}
|
|
|
|
/**
|
|
See whether a string is a wellknown field name.
|
|
|
|
@param key potential key starts here
|
|
@param length length of the string to examine
|
|
|
|
@retval LOG_ITEM_TYPE_RESERVED: reserved, but not "wellknown" key
|
|
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
|
|
@retval >0: index in array of wellknowns
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::wellknown_by_name,
|
|
(const char *key, size_t length)) {
|
|
return log_item_wellknown_by_name(key, length);
|
|
}
|
|
|
|
/**
|
|
Accessor: from a record describing a wellknown key, get its type
|
|
|
|
@param i index in array of wellknowns, see log_item_wellknown_by_...()
|
|
|
|
@retval the log item type for the wellknown key
|
|
*/
|
|
DEFINE_METHOD(log_item_type, log_builtins_imp::wellknown_get_type, (uint i)) {
|
|
return log_item_wellknown_get_type(i);
|
|
}
|
|
|
|
/**
|
|
Accessor: from a record describing a wellknown key, get its name
|
|
|
|
@param i index in array of wellknowns, see log_item_wellknown_by_...()
|
|
|
|
@retval name (NTBS)
|
|
*/
|
|
DEFINE_METHOD(const char *, log_builtins_imp::wellknown_get_name, (uint i)) {
|
|
return log_item_wellknown_get_name(i);
|
|
}
|
|
|
|
/**
|
|
Sanity check an item.
|
|
Certain log sinks have very low requirements with regard to the data
|
|
they receive; they write keys as strings, and then data according to
|
|
the item's class (string, integer, or float), formatted to the sink's
|
|
standards (e.g. JSON, XML, ...).
|
|
Code that has higher requirements can use this check to see whether
|
|
the given item is of a known type (whether generic or wellknown),
|
|
whether the given type and class agree, and whether in case of a
|
|
well-known type, the given key is correct for that type.
|
|
If your code generates items that don't pass this check, you should
|
|
probably go meditate on it.
|
|
|
|
@param li the log_item to check
|
|
|
|
@retval 0 no problems
|
|
@retval -2 unknown item type
|
|
@retval -3 item_class derived from type isn't what's set on the item
|
|
@retval -4 class not generic, so key should match wellknown
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::item_inconsistent, (log_item * li)) {
|
|
return log_item_inconsistent(li);
|
|
}
|
|
|
|
/**
|
|
Predicate used to determine whether a type is generic
|
|
(generic string, generic float, generic integer) rather
|
|
than a well-known type.
|
|
|
|
@param t log item type to examine
|
|
|
|
@retval true if generic type
|
|
@retval false if wellknown type
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_generic_type, (log_item_type t)) {
|
|
return log_item_generic_type(t);
|
|
}
|
|
|
|
/**
|
|
Predicate used to determine whether a class is a string
|
|
class (C-string or Lex-string).
|
|
|
|
@param c log item class to examine
|
|
|
|
@retval true if of a string class
|
|
@retval false if not of a string class
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_string_class, (log_item_class c)) {
|
|
return log_item_string_class(c);
|
|
}
|
|
|
|
/**
|
|
Predicate used to determine whether a class is a numeric
|
|
class (integer or float).
|
|
|
|
@param c log item class to examine
|
|
|
|
@retval true if of a numeric class
|
|
@retval false if not of a numeric class
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_numeric_class, (log_item_class c)) {
|
|
return log_item_numeric_class(c);
|
|
}
|
|
|
|
/**
|
|
Set an integer value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param i integer to set
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_set_int,
|
|
(log_item_data * lid, longlong i)) {
|
|
return log_item_set_int(lid, i);
|
|
}
|
|
|
|
/**
|
|
Set a floating point value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param f float to set
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_set_float,
|
|
(log_item_data * lid, double f)) {
|
|
return log_item_set_float(lid, f);
|
|
}
|
|
|
|
/**
|
|
Set a string value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param s pointer to string
|
|
@param s_len length of string
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_set_lexstring,
|
|
(log_item_data * lid, const char *s, size_t s_len)) {
|
|
return log_item_set_lexstring(lid, s, s_len);
|
|
}
|
|
|
|
/**
|
|
Set a string value on a log_item.
|
|
Fails gracefully if no log_item_data is supplied, so it can safely
|
|
wrap log_line_item_set[_with_key]().
|
|
|
|
@param lid log_item_data struct to set the value on
|
|
@param s pointer to NTBS
|
|
|
|
@retval true lid was nullptr (possibly: OOM, could not set up log_item)
|
|
@retval false all's well
|
|
*/
|
|
DEFINE_METHOD(bool, log_builtins_imp::item_set_cstring,
|
|
(log_item_data * lid, const char *s)) {
|
|
return log_item_set_cstring(lid, s);
|
|
}
|
|
|
|
/**
|
|
Create new log item with key name "key", and allocation flags of
|
|
"alloc" (see enum_log_item_free).
|
|
Will return a pointer to the item's log_item_data struct for
|
|
convenience.
|
|
This is mostly interesting for filters and other services that create
|
|
items that are not part of a log_line; sources etc. that intend to
|
|
create an item for a log_line (the more common case) should usually
|
|
use the below line_item_set_with_key() which creates an item (like
|
|
this function does), but also correctly inserts it into a log_line.
|
|
|
|
@param li the log_item to work on
|
|
@param t the item-type
|
|
@param key the key to set on the item.
|
|
ignored for non-generic types (may pass nullptr for those)
|
|
see alloc
|
|
@param alloc LOG_ITEM_FREE_KEY if key was allocated by caller
|
|
LOG_ITEM_FREE_NONE if key was not allocated
|
|
Allocated keys will automatically free()d when the
|
|
log_item is.
|
|
The log_item's alloc flags will be set to the
|
|
submitted value; specifically, any pre-existing
|
|
value will be clobbered. It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@retval a pointer to the log_item's log_data, for easy chaining:
|
|
log_item_set_with_key(...)->data_integer= 1;
|
|
*/
|
|
DEFINE_METHOD(log_item_data *, log_builtins_imp::item_set_with_key,
|
|
(log_item * li, log_item_type t, const char *key, uint32 alloc)) {
|
|
return log_item_set_with_key(li, t, key, alloc);
|
|
}
|
|
|
|
/**
|
|
As log_item_set_with_key(), except that the key is automatically
|
|
derived from the wellknown log_item_type t.
|
|
|
|
Create new log item with type "t".
|
|
Will return a pointer to the item's log_item_data struct for
|
|
convenience.
|
|
This is mostly interesting for filters and other services that create
|
|
items that are not part of a log_line; sources etc. that intend to
|
|
create an item for a log_line (the more common case) should usually
|
|
use the below line_item_set_with_key() which creates an item (like
|
|
this function does), but also correctly inserts it into a log_line.
|
|
|
|
The allocation of this item will be LOG_ITEM_FREE_NONE;
|
|
specifically, any pre-existing value will be clobbered.
|
|
It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@param li the log_item to work on
|
|
@param t the item-type
|
|
|
|
@retval a pointer to the log_item's log_data, for easy chaining:
|
|
log_item_set_with_key(...)->data_integer= 1;
|
|
*/
|
|
DEFINE_METHOD(log_item_data *, log_builtins_imp::item_set,
|
|
(log_item * li, log_item_type t)) {
|
|
return log_item_set(li, t);
|
|
}
|
|
|
|
/**
|
|
Create new log item in log line "ll", with key name "key", and
|
|
allocation flags of "alloc" (see enum_log_item_free).
|
|
On success, the number of registered items on the log line is increased,
|
|
the item's type is added to the log_line's "seen" property,
|
|
and a pointer to the item's log_item_data struct is returned for
|
|
convenience.
|
|
|
|
@param ll the log_line to work on
|
|
@param t the item-type
|
|
@param key the key to set on the item.
|
|
ignored for non-generic types (may pass nullptr for those)
|
|
see alloc
|
|
@param alloc LOG_ITEM_FREE_KEY if key was allocated by caller
|
|
LOG_ITEM_FREE_NONE if key was not allocated
|
|
Allocated keys will automatically free()d when the
|
|
log_item is.
|
|
The log_item's alloc flags will be set to the
|
|
submitted value; specifically, any pre-existing
|
|
value will be clobbered. It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
|
|
@retval !nullptr a pointer to the log_item's log_data, for easy chaining:
|
|
line_item_set_with_key(...)->data_integer= 1;
|
|
@retval nullptr could not create a log_item in given log_line
|
|
*/
|
|
DEFINE_METHOD(log_item_data *, log_builtins_imp::line_item_set_with_key,
|
|
(log_line * ll, log_item_type t, const char *key, uint32 alloc)) {
|
|
return log_line_item_set_with_key(ll, t, key, alloc);
|
|
}
|
|
|
|
/**
|
|
Create a new log item of well-known type "t" in log line "ll".
|
|
On success, the number of registered items on the log line is increased,
|
|
the item's type is added to the log_line's "seen" property,
|
|
and a pointer to the item's log_item_data struct is returned for
|
|
convenience.
|
|
|
|
The allocation of this item will be LOG_ITEM_FREE_NONE;
|
|
specifically, any pre-existing value will be clobbered.
|
|
It is therefore WRONG
|
|
a) to use this on a log_item that already has a key;
|
|
it should only be used on freshly init'd log_items;
|
|
b) to use this on a log_item that already has a
|
|
value (specifically, an allocated one); the correct
|
|
order is to init a log_item, then set up type and
|
|
key, and finally to set the value. If said value is
|
|
an allocated string, the log_item's alloc should be
|
|
bitwise or'd with LOG_ITEM_FREE_VALUE.
|
|
|
|
@param ll the log_line to work on
|
|
@param t the item-type
|
|
|
|
@retval !nullptr a pointer to the log_item's log_data, for easy chaining:
|
|
line_item_set(...)->data_integer= 1;
|
|
@retval nullptr could not create a log_item in given log_line
|
|
*/
|
|
DEFINE_METHOD(log_item_data *, log_builtins_imp::line_item_set,
|
|
(log_line * ll, log_item_type t)) {
|
|
return log_line_item_set_with_key(ll, t, nullptr, LOG_ITEM_FREE_NONE);
|
|
}
|
|
|
|
/**
|
|
Dynamically allocate and initialize a log_line.
|
|
|
|
@retval nullptr could not set up buffer (too small?)
|
|
@retval other address of the newly initialized log_line
|
|
*/
|
|
DEFINE_METHOD(log_line *, log_builtins_imp::line_init, ()) {
|
|
return log_line_init();
|
|
}
|
|
|
|
/**
|
|
Release a log_line allocated with line_init()
|
|
|
|
@param ll a log_line previously allocated with line_init()
|
|
*/
|
|
DEFINE_METHOD(void, log_builtins_imp::line_exit, (log_line * ll)) {
|
|
log_line_exit(ll);
|
|
}
|
|
|
|
/**
|
|
How many items are currently set on the given log_line?
|
|
|
|
@param ll the log-line to examine
|
|
|
|
@retval the number of items set
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::line_item_count, (log_line * ll)) {
|
|
return log_line_item_count(ll);
|
|
}
|
|
|
|
/**
|
|
Test whether a given type is presumed present on the log line.
|
|
|
|
@param ll the log_line to examine
|
|
@param m the log_type to test for
|
|
|
|
@retval 0 not present
|
|
@retval !=0 present
|
|
*/
|
|
DEFINE_METHOD(log_item_type_mask, log_builtins_imp::line_item_types_seen,
|
|
(log_line * ll, log_item_type_mask m)) {
|
|
return log_line_item_types_seen(ll, m);
|
|
}
|
|
|
|
/**
|
|
Get an iterator for the items in a log_line.
|
|
For now, only one iterator may exist per log_line.
|
|
|
|
@param ll the log_line to examine
|
|
|
|
@retval a log_iterm_iter, or nullptr on failure
|
|
*/
|
|
DEFINE_METHOD(log_item_iter *, log_builtins_imp::line_item_iter_acquire,
|
|
(log_line * ll)) {
|
|
if (ll == nullptr) return nullptr;
|
|
|
|
// If the default iter has already been claimed, refuse to overwrite it.
|
|
if (ll->iter.ll != nullptr) return nullptr;
|
|
|
|
ll->iter.ll = ll;
|
|
ll->iter.index = -1;
|
|
|
|
return &ll->iter;
|
|
}
|
|
|
|
/**
|
|
Release an iterator for the items in a log_line.
|
|
|
|
@param it the iterator to release
|
|
*/
|
|
DEFINE_METHOD(void, log_builtins_imp::line_item_iter_release,
|
|
(log_item_iter * it)) {
|
|
DBUG_ASSERT(it != nullptr);
|
|
DBUG_ASSERT(it->ll != nullptr);
|
|
|
|
it->ll = nullptr;
|
|
}
|
|
|
|
/**
|
|
Use the log_line iterator to get the first item from the set.
|
|
|
|
@param it the iterator to use
|
|
|
|
@retval pointer to the first log_item in the collection, or nullptr
|
|
*/
|
|
DEFINE_METHOD(log_item *, log_builtins_imp::line_item_iter_first,
|
|
(log_item_iter * it)) {
|
|
DBUG_ASSERT(it != nullptr);
|
|
DBUG_ASSERT(it->ll != nullptr);
|
|
|
|
if (it->ll->count < 1) return nullptr;
|
|
|
|
it->index = 0;
|
|
return &it->ll->item[it->index];
|
|
}
|
|
|
|
/**
|
|
Use the log_line iterator to get the next item from the set.
|
|
|
|
@param it the iterator to use
|
|
|
|
@retval pointer to the next log_item in the collection, or nullptr
|
|
*/
|
|
DEFINE_METHOD(log_item *, log_builtins_imp::line_item_iter_next,
|
|
(log_item_iter * it)) {
|
|
DBUG_ASSERT(it != nullptr);
|
|
DBUG_ASSERT(it->ll != nullptr);
|
|
DBUG_ASSERT(it->index >= 0);
|
|
|
|
it->index++;
|
|
|
|
if (it->index >= it->ll->count) return nullptr;
|
|
|
|
return &it->ll->item[it->index];
|
|
}
|
|
|
|
/**
|
|
Use the log_line iterator to get the current item from the set.
|
|
|
|
@param it the iterator to use
|
|
|
|
@retval pointer to the current log_item in the collection, or nullptr
|
|
*/
|
|
DEFINE_METHOD(log_item *, log_builtins_imp::line_item_iter_current,
|
|
(log_item_iter * it)) {
|
|
DBUG_ASSERT(it != nullptr);
|
|
DBUG_ASSERT(it->ll != nullptr);
|
|
DBUG_ASSERT(it->index >= 0);
|
|
|
|
if (it->index >= it->ll->count) return nullptr;
|
|
|
|
return &it->ll->item[it->index];
|
|
}
|
|
|
|
/**
|
|
Complete, filter, and write submitted log items.
|
|
|
|
This expects a log_line collection of log-related key/value pairs,
|
|
e.g. from log_message().
|
|
|
|
Where missing, timestamp, priority, thread-ID (if any) and so forth
|
|
are added.
|
|
|
|
Log item source services, log item filters, and log item sinks are
|
|
then called; then all applicable resources are freed.
|
|
|
|
This interface is intended to facilitate the building of submission
|
|
interfaces other than the variadic message() one below. See the
|
|
example fluent C++ LogEvent() wrapper for an example of how to leverage
|
|
it.
|
|
|
|
@param ll key/value pairs describing info to log
|
|
|
|
@retval int number of fields in created log line
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::line_submit, (log_line * ll)) {
|
|
return log_line_submit(ll);
|
|
}
|
|
|
|
/**
|
|
Submit a log-message for log "log_type".
|
|
Variadic convenience function for logging.
|
|
|
|
This fills in the array that is used by the filter and log-writer
|
|
services. Where missing, timestamp, priority, and thread-ID (if any)
|
|
are added. Log item source services, log item filters, and log item
|
|
writers are called.
|
|
|
|
The variadic list accepts a list of "assignments" of the form
|
|
- log_item_type, value, for well-known types, and
|
|
- log_item_type, key, value, for ad-hoc types (LOG_ITEM_GEN_*)
|
|
|
|
As its last item, the list should have
|
|
- an element of type LOG_ITEM_LOG_MESSAGE, containing a printf-style
|
|
format string, followed by all variables necessary to satisfy the
|
|
substitutions in that string
|
|
|
|
OR
|
|
|
|
- an element of type LOG_ITEM_LOG_LOOKUP, containing a MySQL error code,
|
|
which will be looked up in the list or regular error messages, followed
|
|
by all variables necessary to satisfy the substitutions in that string
|
|
|
|
OR
|
|
|
|
- an element of type LOG_ITEM_LOG_VERBATIM, containing a string that will
|
|
be used directly, with no % substitutions
|
|
|
|
see log_vmessage() for more information.
|
|
|
|
@param log_type what log should this go to?
|
|
@param ... fields: LOG_ITEM_* tag, [[key], value]
|
|
@retval int return value of log_vmessage()
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::message, (int log_type, ...)) {
|
|
va_list lili;
|
|
int ret;
|
|
|
|
va_start(lili, log_type);
|
|
ret = log_vmessage(log_type, lili);
|
|
va_end(lili);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
Escape \0 bytes, add \0 terminator. For log-sinks that terminate in
|
|
an API using C-strings.
|
|
|
|
@param li list_item to process
|
|
|
|
@retval -1 out of memory
|
|
@retval 0 success
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::sanitize, (log_item * li)) {
|
|
size_t in_len = li->data.data_string.length, out_len, len;
|
|
const char *in_start = li->data.data_string.str, *in_read;
|
|
char *out_start = nullptr, *out_write;
|
|
int nuls_found = 0;
|
|
|
|
DBUG_ASSERT((li != nullptr) && (li->item_class == LOG_LEX_STRING));
|
|
|
|
// find out how many \0 to escape
|
|
for (in_read = in_start, len = in_len;
|
|
((in_read = (const char *)memchr(in_read, '\0', len)) != nullptr);) {
|
|
nuls_found++;
|
|
in_read++; // skip over \0
|
|
len = in_len - (in_read - in_start);
|
|
}
|
|
|
|
/*
|
|
Current length + 3 extra for each NUL so we can escape it + terminating NUL
|
|
*/
|
|
out_len = in_len + (nuls_found * 3) + 1;
|
|
|
|
if ((out_start = (char *)my_malloc(key_memory_log_error_loaded_services,
|
|
out_len, MYF(0))) == nullptr)
|
|
return -1;
|
|
|
|
/*
|
|
copy over
|
|
*/
|
|
in_read = in_start;
|
|
out_write = out_start;
|
|
|
|
while (nuls_found--) {
|
|
// copy part before NUL
|
|
len = strlen(in_read);
|
|
strcpy(out_write, in_read);
|
|
out_write += len;
|
|
|
|
// add escaped NUL
|
|
strcpy(out_write, "\\000");
|
|
out_write += 4;
|
|
in_read += (len + 1);
|
|
}
|
|
|
|
// calculate tail (with no further NUL bytes) length
|
|
len = (in_read > in_start) ? (in_read - in_start) : in_len;
|
|
|
|
// copy tail
|
|
strncpy(out_write, in_read, len);
|
|
|
|
/*
|
|
NUL terminate. (the formula above always gives a minimum out-size of 1.)
|
|
*/
|
|
out_start[out_len - 1] = '\0';
|
|
|
|
if (li->alloc & LOG_ITEM_FREE_VALUE) {
|
|
my_free(const_cast<char *>(in_start));
|
|
}
|
|
|
|
li->data.data_string.str = out_start;
|
|
li->alloc |= LOG_ITEM_FREE_VALUE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Return MySQL error message for a given error code.
|
|
|
|
@param mysql_errcode the error code the message for which to look up
|
|
|
|
@retval the message (a printf-style format string)
|
|
*/
|
|
DEFINE_METHOD(const char *, log_builtins_imp::errmsg_by_errcode,
|
|
(int mysql_errcode)) {
|
|
return error_message_for_error_log(mysql_errcode);
|
|
}
|
|
|
|
/**
|
|
Return MySQL error code for a given error symbol.
|
|
|
|
@param sym the symbol to look up
|
|
|
|
@retval -1 failure
|
|
@retval >=0 the MySQL error code
|
|
*/
|
|
DEFINE_METHOD(longlong, log_builtins_imp::errcode_by_errsymbol,
|
|
(const char *sym)) {
|
|
return mysql_symbol_to_errno(sym);
|
|
}
|
|
|
|
/**
|
|
Convenience function: Derive a log label ("error", "warning",
|
|
"information") from a severity.
|
|
|
|
@param prio the severity/prio in question
|
|
|
|
@return a label corresponding to that priority.
|
|
@retval "Error" for prio of ERROR_LEVEL or higher
|
|
@retval "Warning" for prio of WARNING_LEVEL
|
|
@retval "Note" otherwise
|
|
*/
|
|
DEFINE_METHOD(const char *, log_builtins_imp::label_from_prio, (int prio)) {
|
|
return log_label_from_prio(prio);
|
|
}
|
|
|
|
/**
|
|
open an error log file
|
|
|
|
@param file if beginning with '.':
|
|
@@global.log_error, except with this extension
|
|
otherwise:
|
|
use this as file name in the same location as
|
|
@@global.log_error
|
|
|
|
Value may not contain folder separators!
|
|
|
|
@param[out] my_errstream an error log handle, or nullptr on failure
|
|
|
|
@retval 0 success
|
|
@retval -1 EINVAL: my_errstream is NULL
|
|
@retval -2 EINVAL: invalid file name / extension
|
|
@retval -3 OOM: could not allocate file handle
|
|
@retval -4 couldn't lock lock
|
|
@retval -5 couldn't write to given location
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::open_errstream,
|
|
(const char *file, void **my_errstream)) {
|
|
log_errstream *les;
|
|
int rr;
|
|
|
|
if (my_errstream == nullptr) return -1;
|
|
|
|
*my_errstream = nullptr;
|
|
|
|
les = (log_errstream *)my_malloc(key_memory_log_error_loaded_services,
|
|
sizeof(log_errstream), MYF(0));
|
|
|
|
if (les == nullptr) return -3;
|
|
|
|
new (les) log_errstream();
|
|
|
|
if (mysql_mutex_init(0, &les->LOCK_errstream, MY_MUTEX_INIT_FAST)) {
|
|
my_free(les);
|
|
return -4;
|
|
}
|
|
|
|
if ((file == nullptr) || (file[0] == '\0') ||
|
|
(strchr(file, FN_LIBCHAR) != nullptr)) {
|
|
rr = -2;
|
|
goto fail_with_free;
|
|
} else if ((log_error_dest == nullptr) ||
|
|
(!strcmp(log_error_dest, "stderr"))) {
|
|
// using default stream, no file struct needed
|
|
les->file = nullptr;
|
|
} else {
|
|
char errorlog_filename_buff[FN_REFLEN];
|
|
char path[FN_REFLEN];
|
|
size_t path_length;
|
|
MY_STAT f_stat;
|
|
|
|
dirname_part(path, log_error_dest, &path_length);
|
|
|
|
rr = -5;
|
|
|
|
if (file[0] == '.') {
|
|
fn_format(errorlog_filename_buff, log_error_dest, path, file,
|
|
MY_APPEND_EXT | MY_REPLACE_DIR);
|
|
|
|
} else {
|
|
fn_format(errorlog_filename_buff, file, path, ".les", MY_REPLACE_DIR);
|
|
}
|
|
|
|
if (my_stat(errorlog_filename_buff, &f_stat, MYF(0)) != nullptr) {
|
|
if (!(f_stat.st_mode & MY_S_IWRITE)) goto fail_with_free;
|
|
} else {
|
|
if (path_length && my_access(path, (F_OK | W_OK))) goto fail_with_free;
|
|
}
|
|
|
|
les->file = my_fopen(errorlog_filename_buff,
|
|
O_APPEND | O_WRONLY | MY_FOPEN_BINARY, MYF(0));
|
|
|
|
if (les->file == nullptr) goto fail_with_free;
|
|
}
|
|
|
|
*my_errstream = les;
|
|
|
|
return 0;
|
|
|
|
fail_with_free:
|
|
my_free(les);
|
|
|
|
return rr;
|
|
}
|
|
|
|
/**
|
|
write to an error log file previously opened with open_errstream()
|
|
|
|
@param my_errstream a handle describing the log file
|
|
@param buffer pointer to the string to write
|
|
@param length length of the string to write
|
|
|
|
@retval 0 success
|
|
@retval !0 failure
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::write_errstream,
|
|
(void *my_errstream, const char *buffer, size_t length)) {
|
|
log_errstream *les = (log_errstream *)my_errstream;
|
|
|
|
if ((les == nullptr) || (les->file == nullptr))
|
|
log_write_errstream(buffer, length);
|
|
else {
|
|
mysql_mutex_lock(&les->LOCK_errstream);
|
|
fprintf(les->file, "%.*s\n", (int)length, buffer);
|
|
fflush(les->file);
|
|
mysql_mutex_unlock(&les->LOCK_errstream);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
are we writing to a dedicated errstream, or are we sharing it?
|
|
|
|
@param my_errstream a handle describing the log file
|
|
|
|
@retval <0 error
|
|
@retval 0 not dedicated (multiplexed, stderr, ...)
|
|
@retval 1 dedicated
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::dedicated_errstream,
|
|
(void *my_errstream)) {
|
|
log_errstream *les = (log_errstream *)my_errstream;
|
|
|
|
if (les == nullptr) return -1;
|
|
|
|
return (les->file != nullptr) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
close an error log file previously opened with open_errstream()
|
|
|
|
@param my_errstream a handle describing the log file
|
|
|
|
@retval 0 success
|
|
@retval !0 failure
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_imp::close_errstream, (void **my_errstream)) {
|
|
int rr;
|
|
|
|
if (my_errstream == nullptr) return -1;
|
|
|
|
log_errstream *les = (log_errstream *)(*my_errstream);
|
|
|
|
if (les == nullptr) return -2;
|
|
|
|
*my_errstream = nullptr;
|
|
|
|
if (les->file != nullptr) {
|
|
my_fclose(les->file, MYF(0));
|
|
// Continue to log after closing, you'll log to stderr. That'll learn ya.
|
|
les->file = nullptr;
|
|
}
|
|
|
|
rr = mysql_mutex_destroy(&les->LOCK_errstream);
|
|
|
|
my_free(les);
|
|
|
|
return rr;
|
|
}
|
|
|
|
/*
|
|
Service: some stand-ins for string functions we need until they are
|
|
implemented in a more comprehensive service.
|
|
3rd party services should not rely on these being here forever.
|
|
*/
|
|
|
|
/**
|
|
Wrapper for my_malloc()
|
|
|
|
Alloc (len+1) bytes.
|
|
|
|
@param len length of string to copy
|
|
*/
|
|
DEFINE_METHOD(void *, log_builtins_string_imp::malloc, (size_t len)) {
|
|
return my_malloc(key_memory_log_error_loaded_services, len, MYF(0));
|
|
}
|
|
|
|
/**
|
|
Wrapper for my_strndup()
|
|
|
|
Alloc (len+1) bytes, then copy len bytes from fm, and \0 terminate.
|
|
Like my_strndup(), and unlike strndup(), \0 in input won't end copying.
|
|
|
|
@param fm string to copy
|
|
@param len length of string to copy
|
|
*/
|
|
DEFINE_METHOD(char *, log_builtins_string_imp::strndup,
|
|
(const char *fm, size_t len)) {
|
|
return my_strndup(key_memory_log_error_loaded_services, fm, len, MYF(0));
|
|
}
|
|
|
|
/**
|
|
Wrapper for my_free() - free allocated memory
|
|
*/
|
|
DEFINE_METHOD(void, log_builtins_string_imp::free, (void *ptr)) {
|
|
return my_free(ptr);
|
|
}
|
|
|
|
/**
|
|
Wrapper for strlen() - length of a nul-terminated byte string
|
|
*/
|
|
DEFINE_METHOD(size_t, log_builtins_string_imp::length, (const char *s)) {
|
|
return strlen(s);
|
|
}
|
|
|
|
/**
|
|
Wrapper for strchr() - find character in string, from the left
|
|
*/
|
|
DEFINE_METHOD(char *, log_builtins_string_imp::find_first,
|
|
(const char *s, int c)) {
|
|
return strchr(const_cast<char *>(s), c);
|
|
}
|
|
|
|
/**
|
|
Wrapper for strrchr() - find character in string, from the right
|
|
*/
|
|
DEFINE_METHOD(char *, log_builtins_string_imp::find_last,
|
|
(const char *s, int c)) {
|
|
return strrchr(const_cast<char *>(s), c);
|
|
}
|
|
|
|
/**
|
|
Compare two NUL-terminated byte strings
|
|
|
|
Note that when comparing without length limit, the long string
|
|
is greater if they're equal up to the length of the shorter
|
|
string, but the shorter string will be considered greater if
|
|
its "value" up to that point is greater:
|
|
|
|
compare 'abc','abcd': -100 (longer wins if otherwise same)
|
|
compare 'abca','abcd': -3 (higher value wins)
|
|
compare 'abcaaaaa','abcd': -3 (higher value wins)
|
|
|
|
@param a the first string
|
|
@param b the second string
|
|
@param len compare at most this many characters --
|
|
0 for no limit
|
|
@param case_insensitive ignore upper/lower case in comparison
|
|
|
|
@retval -1 a < b
|
|
@retval 0 a == b
|
|
@retval 1 a > b
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_string_imp::compare,
|
|
(const char *a, const char *b, size_t len,
|
|
bool case_insensitive)) {
|
|
return log_string_compare(a, b, len, case_insensitive);
|
|
}
|
|
|
|
/**
|
|
Wrapper for vsnprintf()
|
|
Replace all % in format string with variables from list
|
|
|
|
@param to buffer to write the result to
|
|
@param n size of that buffer
|
|
@param fmt format string
|
|
@param ap va_list with valuables for all substitutions in format string
|
|
|
|
@retval return value of vsnprintf
|
|
*/
|
|
DEFINE_METHOD(size_t, log_builtins_string_imp::substitutev,
|
|
(char *to, size_t n, const char *fmt, va_list ap)) {
|
|
return vsnprintf(to, n, fmt, ap);
|
|
}
|
|
|
|
/**
|
|
Wrapper for vsnprintf()
|
|
Replace all % in format string with variables from list
|
|
*/
|
|
DEFINE_METHOD(size_t, log_builtins_string_imp::substitute,
|
|
(char *to, size_t n, const char *fmt, ...)) {
|
|
size_t ret;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vsnprintf(to, n, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
Service: some stand-ins we need until certain other WLs are implemented.
|
|
3rd party services should not rely on these being here for long.
|
|
*/
|
|
|
|
DEFINE_METHOD(size_t, log_builtins_tmp_imp::notify_client,
|
|
(void *thd, uint severity, uint code, char *to, size_t n,
|
|
const char *format, ...)) {
|
|
size_t ret = 0;
|
|
|
|
if ((to != nullptr) && (n > 0)) {
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
ret = vsnprintf(to, n, format, ap);
|
|
va_end(ap);
|
|
|
|
push_warning((THD *)thd, (Sql_condition::enum_severity_level)severity, code,
|
|
to);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
Service: expose syslog/eventlog to other components.
|
|
3rd party services should not rely on these being here for long,
|
|
as this may be merged into a possibly mysys API later.
|
|
*/
|
|
|
|
/**
|
|
Wrapper for mysys' my_openlog.
|
|
Opens/Registers a new handle for system logging.
|
|
Note: It's a thread-unsafe function. It should either
|
|
be invoked from the main thread or some extra thread
|
|
safety measures need to be taken.
|
|
|
|
@param name Name of the event source / syslog ident.
|
|
@param option MY_SYSLOG_PIDS to log PID with each message.
|
|
@param facility Type of program. Passed to openlog().
|
|
|
|
@retval 0 Success
|
|
@retval -1 Error, log not opened
|
|
@retval -2 Error, not updated, using previous values
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_syseventlog_imp::open,
|
|
(const char *name, int option, int facility)) {
|
|
int ret;
|
|
|
|
mysql_mutex_lock(&THR_LOCK_log_syseventlog);
|
|
ret = my_openlog(name, option, facility);
|
|
mysql_mutex_unlock(&THR_LOCK_log_syseventlog);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Wrapper for mysys' my_syslog.
|
|
Sends message to the system logger. On Windows, the specified message is
|
|
internally converted to UCS-2 encoding, while on other platforms, no
|
|
conversion takes place and the string is passed to the syslog API as it is.
|
|
|
|
@param level Log level
|
|
@param msg Message to be logged
|
|
|
|
@retval 0 Success
|
|
@retval <>0 Error
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_syseventlog_imp::write,
|
|
(enum loglevel level, const char *msg)) {
|
|
int ret;
|
|
|
|
mysql_mutex_lock(&THR_LOCK_log_syseventlog);
|
|
ret = my_syslog(&my_charset_utf8_bin, level, msg);
|
|
mysql_mutex_unlock(&THR_LOCK_log_syseventlog);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Wrapper for mysys' my_closelog.
|
|
Closes/de-registers the system logging handle.
|
|
|
|
@retval 0 Success
|
|
@retval <>0 Error
|
|
*/
|
|
DEFINE_METHOD(int, log_builtins_syseventlog_imp::close, (void)) {
|
|
int ret = 0;
|
|
|
|
mysql_mutex_lock(&THR_LOCK_log_syseventlog);
|
|
ret = my_closelog();
|
|
mysql_mutex_unlock(&THR_LOCK_log_syseventlog);
|
|
|
|
return ret;
|
|
}
|