mirror of
https://salsa.debian.org/ha-team/libqb
synced 2025-12-30 10:10:59 +00:00
The message-id parameter will enable systemd catalogs. To enable message-id's the libqb should be configured with the --enable-systemd-journal option. Co-authored-by: root <Aleksei Burlakov>
1162 lines
27 KiB
C
1162 lines
27 KiB
C
/*
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Author: Angus Salkeld <asalkeld@redhat.com>
|
|
*
|
|
* libqb is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* libqb 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with libqb. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "os_base.h"
|
|
#include <ctype.h>
|
|
#ifdef HAVE_LINK_H
|
|
#include <link.h>
|
|
#endif /* HAVE_LINK_H */
|
|
#include <stdarg.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include <qb/qbdefs.h>
|
|
#include <qb/qblist.h>
|
|
#include <qb/qblog.h>
|
|
#include <qb/qbutil.h>
|
|
#include <qb/qbarray.h>
|
|
#include <qb/qbatomic.h>
|
|
#include "log_int.h"
|
|
#include "util_int.h"
|
|
#include <regex.h>
|
|
|
|
static struct qb_log_target conf[QB_LOG_TARGET_MAX];
|
|
static uint32_t conf_active_max = 0;
|
|
static int32_t in_logger = QB_FALSE;
|
|
static int32_t logger_inited = QB_FALSE;
|
|
static pthread_rwlock_t _listlock;
|
|
static qb_log_filter_fn _custom_filter_fn = NULL;
|
|
|
|
static QB_LIST_DECLARE(tags_head);
|
|
static QB_LIST_DECLARE(callsite_sections);
|
|
|
|
struct callsite_section {
|
|
struct qb_log_callsite *start;
|
|
struct qb_log_callsite *stop;
|
|
struct qb_list_head list;
|
|
};
|
|
|
|
static int32_t _log_target_enable(struct qb_log_target *t);
|
|
static void _log_target_disable(struct qb_log_target *t);
|
|
static void _log_filter_apply(struct callsite_section *sect,
|
|
uint32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
regex_t *regex,
|
|
uint8_t high_priority, uint8_t low_priority);
|
|
static void _log_filter_apply_to_cs(struct qb_log_callsite *cs,
|
|
uint32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
regex_t *regex,
|
|
uint8_t high_priority, uint8_t low_priority);
|
|
|
|
/* deprecated method of getting internal log messages */
|
|
static qb_util_log_fn_t old_internal_log_fn = NULL;
|
|
void
|
|
qb_util_set_log_function(qb_util_log_fn_t fn)
|
|
{
|
|
old_internal_log_fn = fn;
|
|
}
|
|
|
|
static void
|
|
_log_free_filter(struct qb_log_filter *flt)
|
|
{
|
|
if (flt->regex) {
|
|
regfree(flt->regex);
|
|
}
|
|
free(flt->regex);
|
|
free(flt->text);
|
|
free(flt);
|
|
}
|
|
|
|
static int32_t
|
|
_cs_matches_filter_(struct qb_log_callsite *cs,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
regex_t *regex,
|
|
uint8_t high_priority,
|
|
uint8_t low_priority)
|
|
{
|
|
int32_t match = QB_FALSE;
|
|
const char *offset = NULL;
|
|
const char *next = NULL;
|
|
|
|
if (cs->priority > low_priority ||
|
|
cs->priority < high_priority) {
|
|
return QB_FALSE;
|
|
}
|
|
if (strcmp(text, "*") == 0) {
|
|
return QB_TRUE;
|
|
}
|
|
|
|
switch (type) {
|
|
case QB_LOG_FILTER_FILE:
|
|
case QB_LOG_FILTER_FUNCTION:
|
|
next = text;
|
|
do {
|
|
char token[500];
|
|
offset = next;
|
|
next = strchrnul(offset, ',');
|
|
snprintf(token, 499, "%.*s", (int)(next - offset), offset);
|
|
|
|
if (type == QB_LOG_FILTER_FILE) {
|
|
match = (strcmp(cs->filename, token) == 0) ? 1 : 0;
|
|
} else {
|
|
match = (strcmp(cs->function, token) == 0) ? 1 : 0;
|
|
}
|
|
if (!match && next[0] != 0) {
|
|
next++;
|
|
}
|
|
|
|
} while (match == QB_FALSE && next != NULL && next[0] != 0);
|
|
break;
|
|
case QB_LOG_FILTER_FILE_REGEX:
|
|
next = next ? next : cs->filename;
|
|
/* fallthrough */
|
|
case QB_LOG_FILTER_FUNCTION_REGEX:
|
|
next = next ? next : cs->function;
|
|
/* fallthrough */
|
|
case QB_LOG_FILTER_FORMAT_REGEX:
|
|
next = next ? next : cs->format;
|
|
|
|
if (regex == NULL) {
|
|
return QB_FALSE;
|
|
}
|
|
match = regexec(regex, next, 0, NULL, 0);
|
|
if (match == 0) {
|
|
match = QB_TRUE;
|
|
} else {
|
|
match = QB_FALSE;
|
|
}
|
|
break;
|
|
case QB_LOG_FILTER_FORMAT:
|
|
if (strstr(cs->format, text)) {
|
|
match = QB_TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Format a log message into a string buffer
|
|
*
|
|
* @param[out] str Destination buffer to contain formatted string
|
|
* @param[in] cs Callsite containing format to use
|
|
* @param[in] ap Variable arguments for format
|
|
*/
|
|
/* suppress suggestion that we currently can do nothing better about
|
|
as the format specification is hidden in cs argument */
|
|
#ifdef HAVE_GCC_FORMAT_COMPLAINTS
|
|
#pragma GCC diagnostic push
|
|
#ifdef HAVE_GCC_MISSING_FORMAT_ATTRIBUTE
|
|
#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
|
|
#endif
|
|
#ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
|
|
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
|
|
#endif
|
|
#endif
|
|
static inline void
|
|
cs_format(char *str, size_t maxlen, struct qb_log_callsite *cs, va_list ap)
|
|
{
|
|
va_list ap_copy;
|
|
int len;
|
|
|
|
va_copy(ap_copy, ap);
|
|
len = vsnprintf(str, maxlen, cs->format, ap_copy);
|
|
va_end(ap_copy);
|
|
|
|
if (len > maxlen) {
|
|
len = maxlen;
|
|
}
|
|
if (str[len - 1] == '\n') {
|
|
str[len - 1] = '\0';
|
|
}
|
|
}
|
|
#ifdef HAVE_GCC_FORMAT_COMPLAINTS
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
void
|
|
qb_log_real_va_(struct qb_log_callsite *cs, va_list ap)
|
|
{
|
|
int32_t found_threaded = QB_FALSE;
|
|
struct qb_log_target *t;
|
|
struct timespec tv;
|
|
enum qb_log_target_slot pos;
|
|
size_t max_line_length = 0;
|
|
int32_t formatted = QB_FALSE;
|
|
char buf[QB_LOG_MAX_LEN];
|
|
char *str = buf;
|
|
va_list ap_copy;
|
|
|
|
if (qb_atomic_int_compare_and_exchange(&in_logger, QB_FALSE, QB_TRUE) == QB_FALSE || cs == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* 0 Work out the longest line length available */
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
if ((t->state == QB_LOG_STATE_ENABLED)
|
|
&& qb_bit_is_set(cs->targets, pos)) {
|
|
if (t->max_line_length > max_line_length) {
|
|
max_line_length = t->max_line_length;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (max_line_length > QB_LOG_MAX_LEN) {
|
|
str = malloc(max_line_length);
|
|
if (!str) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (old_internal_log_fn &&
|
|
qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
|
|
if (formatted == QB_FALSE) {
|
|
cs_format(str, max_line_length, cs, ap);
|
|
formatted = QB_TRUE;
|
|
}
|
|
qb_do_extended(str, QB_TRUE,
|
|
old_internal_log_fn(cs->filename, cs->lineno,
|
|
cs->priority, str));
|
|
}
|
|
|
|
qb_util_timespec_from_epoch_get(&tv);
|
|
|
|
/*
|
|
* 1 if we can find a threaded target that needs this log then post it
|
|
* 2 foreach non-threaded target call it's logger function
|
|
*/
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
if ((t->state == QB_LOG_STATE_ENABLED)
|
|
&& qb_bit_is_set(cs->targets, pos)) {
|
|
if (t->threaded) {
|
|
if (!found_threaded) {
|
|
found_threaded = QB_TRUE;
|
|
if (formatted == QB_FALSE) {
|
|
cs_format(str, max_line_length, cs, ap);
|
|
formatted = QB_TRUE;
|
|
}
|
|
}
|
|
|
|
} else if (t->vlogger) {
|
|
va_copy(ap_copy, ap);
|
|
t->vlogger(t->pos, cs, &tv, ap_copy);
|
|
va_end(ap_copy);
|
|
|
|
} else if (t->logger) {
|
|
if (formatted == QB_FALSE) {
|
|
cs_format(str, max_line_length, cs, ap);
|
|
formatted = QB_TRUE;
|
|
}
|
|
qb_do_extended(str, t->extended,
|
|
t->logger(t->pos, cs, &tv, str));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found_threaded) {
|
|
qb_log_thread_log_post(cs, &tv, str);
|
|
}
|
|
qb_atomic_int_set(&in_logger, QB_FALSE);
|
|
|
|
if (max_line_length > QB_LOG_MAX_LEN) {
|
|
free(str);
|
|
}
|
|
}
|
|
|
|
void
|
|
qb_log_real_(struct qb_log_callsite *cs, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, cs);
|
|
qb_log_real_va_(cs, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void
|
|
qb_log_thread_log_write(struct qb_log_callsite *cs,
|
|
struct timespec *timestamp, const char *buffer)
|
|
{
|
|
struct qb_log_target *t;
|
|
enum qb_log_target_slot pos;
|
|
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
if ((t->state == QB_LOG_STATE_ENABLED) && t->threaded
|
|
&& qb_bit_is_set(cs->targets, t->pos)) {
|
|
qb_do_extended(buffer, t->extended,
|
|
t->logger(t->pos, cs, timestamp, buffer));
|
|
}
|
|
}
|
|
}
|
|
|
|
struct qb_log_callsite*
|
|
qb_log_callsite_get2(const char *message_id,
|
|
const char *function,
|
|
const char *filename,
|
|
const char *format,
|
|
uint8_t priority,
|
|
uint32_t lineno,
|
|
uint32_t tags)
|
|
{
|
|
struct qb_log_target *t;
|
|
struct qb_log_filter *flt;
|
|
struct qb_log_callsite *cs;
|
|
int32_t new_dcs = QB_FALSE;
|
|
struct qb_list_head *f_item;
|
|
enum qb_log_target_slot pos;
|
|
|
|
if (!logger_inited) {
|
|
return NULL;
|
|
}
|
|
|
|
cs = qb_log_dcs_get(&new_dcs, message_id, function, filename,
|
|
format, priority, lineno, tags);
|
|
|
|
if (cs == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (new_dcs) {
|
|
pthread_rwlock_rdlock(&_listlock);
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
if (t->state != QB_LOG_STATE_ENABLED) {
|
|
continue;
|
|
}
|
|
qb_list_for_each(f_item, &t->filter_head) {
|
|
flt = qb_list_entry(f_item, struct qb_log_filter, list);
|
|
_log_filter_apply_to_cs(cs, t->pos, flt->conf, flt->type,
|
|
flt->text, flt->regex, flt->high_priority,
|
|
flt->low_priority);
|
|
}
|
|
}
|
|
if (tags == 0) {
|
|
qb_list_for_each(f_item, &tags_head) {
|
|
flt = qb_list_entry(f_item, struct qb_log_filter, list);
|
|
_log_filter_apply_to_cs(cs, flt->new_value, flt->conf, flt->type,
|
|
flt->text, flt->regex, flt->high_priority,
|
|
flt->low_priority);
|
|
}
|
|
} else {
|
|
cs->tags = tags;
|
|
}
|
|
if (_custom_filter_fn) {
|
|
_custom_filter_fn(cs);
|
|
}
|
|
pthread_rwlock_unlock(&_listlock);
|
|
} else {
|
|
if (tags && cs->tags != tags) {
|
|
cs->tags = tags;
|
|
}
|
|
if (_custom_filter_fn) {
|
|
_custom_filter_fn(cs);
|
|
}
|
|
}
|
|
return cs;
|
|
}
|
|
|
|
struct qb_log_callsite*
|
|
qb_log_callsite_get(const char *function,
|
|
const char *filename,
|
|
const char *format,
|
|
uint8_t priority,
|
|
uint32_t lineno,
|
|
uint32_t tags)
|
|
{
|
|
return qb_log_callsite_get2(NULL, function, filename, format,
|
|
priority, lineno, tags);
|
|
}
|
|
|
|
void
|
|
qb_log_from_external_source_va2(const char *message_id,
|
|
const char *function,
|
|
const char *filename,
|
|
const char *format,
|
|
uint8_t priority,
|
|
uint32_t lineno, uint32_t tags, va_list ap)
|
|
{
|
|
struct qb_log_callsite *cs;
|
|
|
|
if (!logger_inited) {
|
|
return;
|
|
}
|
|
|
|
cs = qb_log_callsite_get2(message_id, function, filename,
|
|
format, priority, lineno, tags);
|
|
qb_log_real_va_(cs, ap);
|
|
}
|
|
|
|
void
|
|
qb_log_from_external_source_va(const char *function,
|
|
const char *filename,
|
|
const char *format,
|
|
uint8_t priority,
|
|
uint32_t lineno, uint32_t tags, va_list ap)
|
|
{
|
|
qb_log_from_external_source_va2(NULL, function, filename,
|
|
format, priority, lineno, tags, ap);
|
|
}
|
|
|
|
void
|
|
qb_log_from_external_source(const char *function,
|
|
const char *filename,
|
|
const char *format,
|
|
uint8_t priority,
|
|
uint32_t lineno, uint32_t tags, ...)
|
|
{
|
|
struct qb_log_callsite *cs;
|
|
va_list ap;
|
|
|
|
if (!logger_inited) {
|
|
return;
|
|
}
|
|
|
|
cs = qb_log_callsite_get(function, filename,
|
|
format, priority, lineno, tags);
|
|
va_start(ap, tags);
|
|
qb_log_real_va_(cs, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void
|
|
qb_log_callsites_dump_sect(struct callsite_section *sect)
|
|
{
|
|
struct qb_log_callsite *cs;
|
|
printf(" start %p - stop %p\n", sect->start, sect->stop);
|
|
printf("filename lineno targets tags\n");
|
|
for (cs = sect->start; cs < sect->stop; cs++) {
|
|
if (cs->lineno > 0) {
|
|
#ifndef S_SPLINT_S
|
|
printf("%12s %6" PRIu32 " %16" PRIu32 " %16u\n",
|
|
cs->filename, cs->lineno, cs->targets,
|
|
cs->tags);
|
|
#endif /* S_SPLINT_S */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int32_t
|
|
qb_log_callsites_register(struct qb_log_callsite *_start,
|
|
struct qb_log_callsite *_stop)
|
|
{
|
|
struct callsite_section *sect;
|
|
struct qb_log_callsite *cs;
|
|
struct qb_log_target *t;
|
|
struct qb_log_filter *flt;
|
|
enum qb_log_target_slot pos;
|
|
|
|
if (_start == NULL || _stop == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
pthread_rwlock_rdlock(&_listlock);
|
|
qb_list_for_each_entry(sect, &callsite_sections, list) {
|
|
if (sect->start == _start || sect->stop == _stop) {
|
|
pthread_rwlock_unlock(&_listlock);
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
pthread_rwlock_unlock(&_listlock);
|
|
|
|
sect = calloc(1, sizeof(struct callsite_section));
|
|
if (sect == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
sect->start = _start;
|
|
sect->stop = _stop;
|
|
qb_list_init(§->list);
|
|
|
|
pthread_rwlock_wrlock(&_listlock);
|
|
qb_list_add(§->list, &callsite_sections);
|
|
|
|
/*
|
|
* Now apply the filters on these new callsites
|
|
*/
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
if (t->state != QB_LOG_STATE_ENABLED) {
|
|
continue;
|
|
}
|
|
qb_list_for_each_entry(flt, &t->filter_head, list) {
|
|
_log_filter_apply(sect, t->pos, flt->conf,
|
|
flt->type, flt->text, flt->regex,
|
|
flt->high_priority, flt->low_priority);
|
|
}
|
|
}
|
|
qb_list_for_each_entry(flt, &tags_head, list) {
|
|
_log_filter_apply(sect, flt->new_value, flt->conf,
|
|
flt->type, flt->text, flt->regex,
|
|
flt->high_priority, flt->low_priority);
|
|
}
|
|
pthread_rwlock_unlock(&_listlock);
|
|
if (_custom_filter_fn) {
|
|
for (cs = sect->start; cs < sect->stop; cs++) {
|
|
if (cs->lineno > 0) {
|
|
_custom_filter_fn(cs);
|
|
}
|
|
}
|
|
}
|
|
/* qb_log_callsites_dump_sect(sect); */
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
qb_log_callsites_dump(void)
|
|
{
|
|
struct callsite_section *sect;
|
|
int32_t l;
|
|
|
|
pthread_rwlock_rdlock(&_listlock);
|
|
l = qb_list_length(&callsite_sections);
|
|
printf("Callsite Database [%d]\n", l);
|
|
printf("---------------------\n");
|
|
qb_list_for_each_entry(sect, &callsite_sections, list) {
|
|
qb_log_callsites_dump_sect(sect);
|
|
}
|
|
pthread_rwlock_unlock(&_listlock);
|
|
}
|
|
|
|
static int32_t
|
|
_log_filter_exists(struct qb_list_head *list_head,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
uint8_t high_priority,
|
|
uint8_t low_priority,
|
|
uint32_t new_value)
|
|
{
|
|
struct qb_log_filter *flt;
|
|
|
|
qb_list_for_each_entry(flt, list_head, list) {
|
|
if (flt->type == type &&
|
|
flt->high_priority == high_priority &&
|
|
flt->low_priority == low_priority &&
|
|
flt->new_value == new_value &&
|
|
strcmp(flt->text, text) == 0) {
|
|
return QB_TRUE;
|
|
}
|
|
}
|
|
return QB_FALSE;
|
|
}
|
|
|
|
static int32_t
|
|
_log_filter_store(uint32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
uint8_t high_priority,
|
|
uint8_t low_priority,
|
|
struct qb_log_filter **new)
|
|
{
|
|
struct qb_log_filter *flt;
|
|
struct qb_list_head *iter;
|
|
struct qb_list_head *next;
|
|
struct qb_list_head *list_head;
|
|
|
|
switch (c) {
|
|
case QB_LOG_FILTER_ADD:
|
|
case QB_LOG_FILTER_REMOVE:
|
|
case QB_LOG_FILTER_CLEAR_ALL:
|
|
list_head = &conf[t].filter_head;
|
|
break;
|
|
|
|
case QB_LOG_TAG_SET:
|
|
case QB_LOG_TAG_CLEAR:
|
|
case QB_LOG_TAG_CLEAR_ALL:
|
|
list_head = &tags_head;
|
|
break;
|
|
default:
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if (c == QB_LOG_FILTER_ADD || c == QB_LOG_TAG_SET) {
|
|
if (text == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
if (_log_filter_exists(list_head, type, text,
|
|
high_priority, low_priority, t)) {
|
|
return -EEXIST;
|
|
}
|
|
flt = calloc(1, sizeof(struct qb_log_filter));
|
|
if (flt == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
qb_list_init(&flt->list);
|
|
flt->conf = c;
|
|
flt->type = type;
|
|
flt->text = strdup(text);
|
|
if (flt->text == NULL) {
|
|
_log_free_filter(flt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (type == QB_LOG_FILTER_FUNCTION_REGEX ||
|
|
type == QB_LOG_FILTER_FILE_REGEX ||
|
|
type == QB_LOG_FILTER_FORMAT_REGEX) {
|
|
int res;
|
|
|
|
flt->regex = calloc(1, sizeof(regex_t));
|
|
if (flt->regex == NULL) {
|
|
_log_free_filter(flt);
|
|
return -ENOMEM;
|
|
}
|
|
res = regcomp(flt->regex, flt->text, 0);
|
|
if (res) {
|
|
_log_free_filter(flt);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
flt->high_priority = high_priority;
|
|
flt->low_priority = low_priority;
|
|
flt->new_value = t;
|
|
qb_list_add_tail(&flt->list, list_head);
|
|
if (new) {
|
|
*new = flt;
|
|
}
|
|
} else if (c == QB_LOG_FILTER_REMOVE || c == QB_LOG_TAG_CLEAR) {
|
|
qb_list_for_each_safe(iter, next, list_head) {
|
|
flt = qb_list_entry(iter, struct qb_log_filter, list);
|
|
if (flt->type == type &&
|
|
flt->low_priority <= low_priority &&
|
|
flt->high_priority >= high_priority &&
|
|
(strcmp(flt->text, text) == 0 ||
|
|
strcmp("*", text) == 0)) {
|
|
qb_list_del(iter);
|
|
_log_free_filter(flt);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} else if (c == QB_LOG_FILTER_CLEAR_ALL || c == QB_LOG_TAG_CLEAR_ALL) {
|
|
qb_list_for_each_safe(iter, next, list_head) {
|
|
flt = qb_list_entry(iter, struct qb_log_filter, list);
|
|
qb_list_del(iter);
|
|
_log_free_filter(flt);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_log_filter_apply(struct callsite_section *sect,
|
|
uint32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
regex_t *regex,
|
|
uint8_t high_priority, uint8_t low_priority)
|
|
{
|
|
struct qb_log_callsite *cs;
|
|
|
|
for (cs = sect->start; cs < sect->stop; cs++) {
|
|
if (cs->lineno > 0) {
|
|
_log_filter_apply_to_cs(cs, t, c, type, text, regex,
|
|
high_priority, low_priority);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* #define _QB_FILTER_DEBUGGING_ 1 */
|
|
static void
|
|
_log_filter_apply_to_cs(struct qb_log_callsite *cs,
|
|
uint32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text,
|
|
regex_t *regex,
|
|
uint8_t high_priority, uint8_t low_priority)
|
|
{
|
|
|
|
if (c == QB_LOG_FILTER_CLEAR_ALL) {
|
|
qb_bit_clear(cs->targets, t);
|
|
return;
|
|
} else if (c == QB_LOG_TAG_CLEAR_ALL) {
|
|
cs->tags = 0;
|
|
return;
|
|
}
|
|
|
|
if (_cs_matches_filter_(cs, type, text, regex, high_priority, low_priority)) {
|
|
#ifdef _QB_FILTER_DEBUGGING_
|
|
uint32_t old_targets = cs->targets;
|
|
uint32_t old_tags = cs->tags;
|
|
#endif /* _QB_FILTER_DEBUGGING_ */
|
|
if (c == QB_LOG_FILTER_ADD) {
|
|
qb_bit_set(cs->targets, t);
|
|
} else if (c == QB_LOG_FILTER_REMOVE) {
|
|
qb_bit_clear(cs->targets, t);
|
|
} else if (c == QB_LOG_TAG_SET) {
|
|
cs->tags = t;
|
|
} else if (c == QB_LOG_TAG_CLEAR) {
|
|
cs->tags = 0;
|
|
}
|
|
#ifdef _QB_FILTER_DEBUGGING_
|
|
if (old_targets != cs->targets) {
|
|
printf("targets: %s:%u value(%d) %d -> %d\n",
|
|
cs->filename, cs->lineno, t,
|
|
old_targets, cs->targets);
|
|
}
|
|
if (old_tags != cs->tags) {
|
|
printf("tags: %s:%u value(%d) %d -> %d\n",
|
|
cs->filename, cs->lineno, t, old_tags, cs->tags);
|
|
}
|
|
#endif /* _QB_FILTER_DEBUGGING_ */
|
|
}
|
|
}
|
|
|
|
int32_t
|
|
qb_log_filter_ctl2(int32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type, const char * text,
|
|
uint8_t high_priority, uint8_t low_priority)
|
|
{
|
|
struct qb_log_filter *new_flt = NULL;
|
|
regex_t *regex = NULL;
|
|
struct callsite_section *sect;
|
|
int32_t rc;
|
|
|
|
if (!logger_inited) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (c == QB_LOG_FILTER_ADD ||
|
|
c == QB_LOG_FILTER_CLEAR_ALL ||
|
|
c == QB_LOG_FILTER_REMOVE) {
|
|
if (t < 0 || t >= QB_LOG_TARGET_MAX ||
|
|
conf[t].state == QB_LOG_STATE_UNUSED) {
|
|
return -EBADF;
|
|
}
|
|
}
|
|
|
|
if (text == NULL ||
|
|
low_priority < high_priority ||
|
|
type > QB_LOG_FILTER_FORMAT_REGEX ||
|
|
c > QB_LOG_TAG_CLEAR_ALL) {
|
|
return -EINVAL;
|
|
}
|
|
pthread_rwlock_rdlock(&_listlock);
|
|
rc = _log_filter_store(t, c, type, text, high_priority, low_priority, &new_flt);
|
|
if (rc < 0) {
|
|
pthread_rwlock_unlock(&_listlock);
|
|
return rc;
|
|
}
|
|
|
|
if (new_flt && new_flt->regex) {
|
|
regex = new_flt->regex;
|
|
}
|
|
qb_list_for_each_entry(sect, &callsite_sections, list) {
|
|
_log_filter_apply(sect, t, c, type, text, regex, high_priority, low_priority);
|
|
}
|
|
pthread_rwlock_unlock(&_listlock);
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
qb_log_filter_fn_set(qb_log_filter_fn fn)
|
|
{
|
|
struct callsite_section *sect;
|
|
struct qb_log_callsite *cs;
|
|
|
|
if (!logger_inited) {
|
|
return -EINVAL;
|
|
}
|
|
_custom_filter_fn = fn;
|
|
if (_custom_filter_fn == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
qb_list_for_each_entry(sect, &callsite_sections, list) {
|
|
for (cs = sect->start; cs < sect->stop; cs++) {
|
|
if (cs->lineno > 0) {
|
|
_custom_filter_fn(cs);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
qb_log_filter_ctl(int32_t t, enum qb_log_filter_conf c,
|
|
enum qb_log_filter_type type,
|
|
const char *text, uint8_t priority)
|
|
{
|
|
return qb_log_filter_ctl2(t, c, type, text, LOG_EMERG, priority);
|
|
}
|
|
|
|
static void
|
|
_log_target_state_set(struct qb_log_target *t, enum qb_log_target_state s)
|
|
{
|
|
enum qb_log_target_slot i;
|
|
int32_t a_set = QB_FALSE;
|
|
int32_t u_set = QB_FALSE;
|
|
|
|
t->state = s;
|
|
|
|
for (i = QB_LOG_TARGET_MAX; i > QB_LOG_TARGET_START; i--) {
|
|
if (!a_set && conf[i-1].state == QB_LOG_STATE_ENABLED) {
|
|
a_set = QB_TRUE;
|
|
conf_active_max = i-1;
|
|
}
|
|
if (!u_set && conf[i-1].state != QB_LOG_STATE_UNUSED) {
|
|
u_set = QB_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
qb_log_init(const char *name, int32_t facility, uint8_t priority)
|
|
{
|
|
int32_t l;
|
|
enum qb_log_target_slot i;
|
|
|
|
l = pthread_rwlock_init(&_listlock, NULL);
|
|
assert(l == 0);
|
|
qb_log_format_init();
|
|
|
|
for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
|
|
conf[i].pos = i;
|
|
conf[i].debug = QB_FALSE;
|
|
conf[i].file_sync = QB_FALSE;
|
|
conf[i].extended = QB_TRUE;
|
|
conf[i].state = QB_LOG_STATE_UNUSED;
|
|
(void)strlcpy(conf[i].name, name, PATH_MAX);
|
|
conf[i].facility = facility;
|
|
conf[i].max_line_length = QB_LOG_MAX_LEN;
|
|
qb_list_init(&conf[i].filter_head);
|
|
}
|
|
|
|
qb_log_dcs_init();
|
|
|
|
for (i = QB_LOG_TARGET_STATIC_START; i < QB_LOG_TARGET_STATIC_MAX; i++)
|
|
conf[i].state = QB_LOG_STATE_DISABLED;
|
|
|
|
logger_inited = QB_TRUE;
|
|
(void)qb_log_syslog_open(&conf[QB_LOG_SYSLOG]);
|
|
_log_target_state_set(&conf[QB_LOG_SYSLOG], QB_LOG_STATE_ENABLED);
|
|
(void)qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD,
|
|
QB_LOG_FILTER_FILE, "*", priority);
|
|
}
|
|
|
|
void
|
|
qb_log_fini(void)
|
|
{
|
|
struct qb_log_target *t;
|
|
struct qb_log_filter *flt;
|
|
struct callsite_section *s;
|
|
struct qb_list_head *iter;
|
|
struct qb_list_head *iter2;
|
|
struct qb_list_head *next;
|
|
struct qb_list_head *next2;
|
|
enum qb_log_target_slot pos;
|
|
|
|
if (!logger_inited) {
|
|
return;
|
|
}
|
|
logger_inited = QB_FALSE;
|
|
qb_log_thread_stop();
|
|
pthread_rwlock_destroy(&_listlock);
|
|
|
|
for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
|
|
t = &conf[pos];
|
|
_log_target_disable(t);
|
|
qb_list_for_each_safe(iter2, next2, &t->filter_head) {
|
|
flt = qb_list_entry(iter2, struct qb_log_filter, list);
|
|
qb_list_del(iter2);
|
|
_log_free_filter(flt);
|
|
}
|
|
}
|
|
qb_log_format_fini();
|
|
qb_log_dcs_fini();
|
|
qb_list_for_each_safe(iter, next, &callsite_sections) {
|
|
s = qb_list_entry(iter, struct callsite_section, list);
|
|
qb_list_del(iter);
|
|
free(s);
|
|
}
|
|
qb_list_for_each_safe(iter, next, &tags_head) {
|
|
flt = qb_list_entry(iter, struct qb_log_filter, list);
|
|
qb_list_del(iter);
|
|
_log_free_filter(flt);
|
|
}
|
|
}
|
|
|
|
struct qb_log_target *
|
|
qb_log_target_alloc(void)
|
|
{
|
|
enum qb_log_target_slot i;
|
|
for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
|
|
if (conf[i].state == QB_LOG_STATE_UNUSED) {
|
|
_log_target_state_set(&conf[i], QB_LOG_STATE_DISABLED);
|
|
return &conf[i];
|
|
}
|
|
}
|
|
errno = EMFILE;
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
qb_log_target_free(struct qb_log_target *t)
|
|
{
|
|
(void)qb_log_filter_ctl(t->pos, QB_LOG_FILTER_CLEAR_ALL,
|
|
QB_LOG_FILTER_FILE, NULL, 0);
|
|
t->debug = QB_FALSE;
|
|
t->filename[0] = '\0';
|
|
qb_log_format_set(t->pos, NULL);
|
|
_log_target_state_set(t, QB_LOG_STATE_UNUSED);
|
|
}
|
|
|
|
struct qb_log_target *
|
|
qb_log_target_get(int32_t pos)
|
|
{
|
|
return &conf[pos];
|
|
}
|
|
|
|
void *
|
|
qb_log_target_user_data_get(int32_t t)
|
|
{
|
|
if (t < 0 || t >= QB_LOG_TARGET_MAX ||
|
|
conf[t].state == QB_LOG_STATE_UNUSED) {
|
|
errno = EBADF;
|
|
return NULL;
|
|
}
|
|
|
|
return conf[t].instance;
|
|
}
|
|
|
|
int32_t
|
|
qb_log_target_user_data_set(int32_t t, void *user_data)
|
|
{
|
|
if (!logger_inited) {
|
|
return -EINVAL;
|
|
}
|
|
if (t < 0 || t >= QB_LOG_TARGET_MAX ||
|
|
conf[t].state == QB_LOG_STATE_UNUSED) {
|
|
return -EBADF;
|
|
}
|
|
|
|
conf[t].instance = user_data;
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
qb_log_custom_open(qb_log_logger_fn log_fn,
|
|
qb_log_close_fn close_fn,
|
|
qb_log_reload_fn reload_fn, void *user_data)
|
|
{
|
|
struct qb_log_target *t;
|
|
|
|
t = qb_log_target_alloc();
|
|
if (t == NULL) {
|
|
return -errno;
|
|
}
|
|
|
|
t->instance = user_data;
|
|
#ifndef S_SPLINT_S
|
|
snprintf(t->filename, PATH_MAX, "custom-%" PRIu32, t->pos);
|
|
#endif /* S_SPLINT_S */
|
|
|
|
t->logger = log_fn;
|
|
t->vlogger = NULL;
|
|
t->reload = reload_fn;
|
|
t->close = close_fn;
|
|
|
|
return t->pos;
|
|
}
|
|
|
|
void
|
|
qb_log_custom_close(int32_t t)
|
|
{
|
|
struct qb_log_target *target;
|
|
|
|
if (!logger_inited) {
|
|
return;
|
|
}
|
|
if (t < 0 || t >= QB_LOG_TARGET_MAX ||
|
|
conf[t].state == QB_LOG_STATE_UNUSED) {
|
|
return;
|
|
}
|
|
|
|
target = qb_log_target_get(t);
|
|
|
|
if (target->close) {
|
|
qb_atomic_int_set(&in_logger, QB_TRUE);
|
|
target->close(t);
|
|
qb_atomic_int_set(&in_logger, QB_FALSE);
|
|
}
|
|
qb_log_target_free(target);
|
|
}
|
|
|
|
static int32_t
|
|
_log_target_enable(struct qb_log_target *t)
|
|
{
|
|
int32_t rc = 0;
|
|
|
|
if (t->state == QB_LOG_STATE_ENABLED) {
|
|
return 0;
|
|
}
|
|
if (t->pos == QB_LOG_STDERR ||
|
|
t->pos == QB_LOG_STDOUT) {
|
|
rc = qb_log_stderr_open(t);
|
|
} else if (t->pos == QB_LOG_SYSLOG) {
|
|
rc = qb_log_syslog_open(t);
|
|
} else if (t->pos == QB_LOG_BLACKBOX) {
|
|
rc = qb_log_blackbox_open(t);
|
|
}
|
|
if (rc == 0) {
|
|
_log_target_state_set(t, QB_LOG_STATE_ENABLED);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
_log_target_disable(struct qb_log_target *t)
|
|
{
|
|
if (t->state != QB_LOG_STATE_ENABLED) {
|
|
return;
|
|
}
|
|
_log_target_state_set(t, QB_LOG_STATE_DISABLED);
|
|
if (t->close) {
|
|
qb_atomic_int_set(&in_logger, QB_TRUE);
|
|
t->close(t->pos);
|
|
qb_atomic_int_set(&in_logger, QB_FALSE);
|
|
}
|
|
}
|
|
|
|
int32_t
|
|
qb_log_ctl2(int32_t t, enum qb_log_conf c, qb_log_ctl2_arg_t arg_not4directuse)
|
|
{
|
|
int32_t rc = 0;
|
|
int32_t need_reload = QB_FALSE;
|
|
|
|
/* extract the constants and do not touch the origin anymore */
|
|
const int32_t arg_i32 = arg_not4directuse.i32;
|
|
const char * const arg_s = arg_not4directuse.s;
|
|
|
|
if (!logger_inited) {
|
|
return -EINVAL;
|
|
}
|
|
if (t < 0 || t >= QB_LOG_TARGET_MAX ||
|
|
conf[t].state == QB_LOG_STATE_UNUSED) {
|
|
return -EBADF;
|
|
}
|
|
|
|
/* Starting/stopping the thread has its own locking that can interfere with this */
|
|
if (c != QB_LOG_CONF_THREADED) {
|
|
qb_log_thread_pause(&conf[t]);
|
|
}
|
|
|
|
switch (c) {
|
|
case QB_LOG_CONF_ENABLED:
|
|
if (arg_i32) {
|
|
rc = _log_target_enable(&conf[t]);
|
|
} else {
|
|
_log_target_disable(&conf[t]);
|
|
}
|
|
break;
|
|
case QB_LOG_CONF_STATE_GET:
|
|
rc = conf[t].state;
|
|
break;
|
|
case QB_LOG_CONF_FACILITY:
|
|
conf[t].facility = arg_i32;
|
|
if (t == QB_LOG_SYSLOG) {
|
|
need_reload = QB_TRUE;
|
|
}
|
|
break;
|
|
case QB_LOG_CONF_IDENT:
|
|
(void)strlcpy(conf[t].name, arg_s, PATH_MAX);
|
|
if (t == QB_LOG_SYSLOG) {
|
|
need_reload = QB_TRUE;
|
|
}
|
|
break;
|
|
case QB_LOG_CONF_FILE_SYNC:
|
|
conf[t].file_sync = arg_i32;
|
|
break;
|
|
case QB_LOG_CONF_PRIORITY_BUMP:
|
|
conf[t].priority_bump = arg_i32;
|
|
break;
|
|
case QB_LOG_CONF_SIZE:
|
|
if (t == QB_LOG_BLACKBOX) {
|
|
if (arg_i32 <= 0) {
|
|
rc = -EINVAL;
|
|
goto unlock_fini;
|
|
}
|
|
conf[t].size = arg_i32;
|
|
need_reload = QB_TRUE;
|
|
} else {
|
|
rc = -ENOSYS;
|
|
}
|
|
break;
|
|
case QB_LOG_CONF_THREADED:
|
|
conf[t].threaded = arg_i32;
|
|
break;
|
|
case QB_LOG_CONF_EXTENDED:
|
|
conf[t].extended = arg_i32;
|
|
break;
|
|
case QB_LOG_CONF_MAX_LINE_LEN:
|
|
/* arbitrary limit, but you'd be insane to go further */
|
|
if (arg_i32 > QB_LOG_ABSOLUTE_MAX_LEN) {
|
|
rc = -EINVAL;
|
|
} else {
|
|
conf[t].max_line_length = arg_i32;
|
|
}
|
|
break;
|
|
case QB_LOG_CONF_ELLIPSIS:
|
|
conf[t].ellipsis = arg_i32;
|
|
break;
|
|
case QB_LOG_CONF_USE_JOURNAL:
|
|
#ifdef USE_JOURNAL
|
|
if (t == QB_LOG_SYSLOG) {
|
|
conf[t].use_journal = arg_i32;
|
|
need_reload = QB_TRUE;
|
|
} else {
|
|
rc = -EINVAL;
|
|
}
|
|
#else
|
|
rc = -EOPNOTSUPP;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
if (rc == 0 && need_reload && conf[t].reload) {
|
|
qb_atomic_int_set(&in_logger, QB_TRUE);
|
|
conf[t].reload(t);
|
|
qb_atomic_int_set(&in_logger, QB_FALSE);
|
|
}
|
|
|
|
unlock_fini:
|
|
if (c != QB_LOG_CONF_THREADED) {
|
|
qb_log_thread_resume(&conf[t]);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int32_t
|
|
qb_log_ctl(int32_t t, enum qb_log_conf c, int32_t arg)
|
|
{
|
|
return qb_log_ctl2(t, c, QB_LOG_CTL2_I32(arg));
|
|
}
|