/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-macros.h" #include "util-color.h" #include "util-strings.h" #include "libei-private.h" struct ei_log_context { const char *file; const char *func; int line; }; _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, file, const char *); _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, func, const char *); _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, line, unsigned int); static void ei_default_log_handler(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *ctx) { struct lut { const char *color; const char *prefix; } lut[] = { { .color = ansi_colorcode[RED], .prefix = "", }, /* debug starts at 10 */ { .color = ansi_colorcode[HIGHLIGHT], .prefix = "DEBUG", }, { .color = ansi_colorcode[GREEN], .prefix = "INFO", }, { .color = ansi_colorcode[BLUE], .prefix = "WARN", }, { .color = ansi_colorcode[RED], .prefix = "ERROR", }, }; static time_t last_time = 0; const char *reset_code = ansi_colorcode[RESET]; run_only_once { if (!isatty(STDERR_FILENO)) { struct lut *l; ARRAY_FOR_EACH(lut, l) l->color = ""; reset_code = ""; } } time_t now = time(NULL); char timestamp[64]; if (last_time != now) { struct tm *tm = localtime(&now); strftime(timestamp, sizeof(timestamp), "%T", tm); } else { xsnprintf(timestamp, sizeof(timestamp), "..."); } size_t idx = priority/10; assert(idx < ARRAY_LENGTH(lut)); fprintf(stderr, " %8s | %s%4s%s | %s\n", timestamp, lut[idx].color, lut[idx].prefix, reset_code, message); last_time = now; } _public_ void ei_log_set_handler(struct ei *ei, ei_log_handler log_handler) { ei->log.handler = log_handler ? log_handler : ei_default_log_handler; } _public_ void ei_log_set_priority(struct ei *ei, enum ei_log_priority priority) { switch (priority) { case EI_LOG_PRIORITY_DEBUG: case EI_LOG_PRIORITY_INFO: case EI_LOG_PRIORITY_WARNING: case EI_LOG_PRIORITY_ERROR: break; default: abort(); } ei->log.priority = priority; } _public_ enum ei_log_priority ei_log_get_priority(const struct ei *ei) { return ei->log.priority; } void ei_log_msg(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...) { va_list args; va_start(args, format); ei_log_msg_va(ei, priority, file, lineno, func, format, args); va_end(args); } void ei_log_msg_va(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list ap) { if (ei->log.priority > priority || !ei->log.handler) return; _cleanup_free_ char *message = xvaprintf(format, ap); struct ei_log_context ctx = { .file = file, .line = lineno, .func = func, }; ei->log.handler(ei, priority, message, &ctx); } #ifdef _enable_tests_ #include "util-munit.h" struct log_handler_check { enum ei_log_priority min_priority; const char *expected_message; }; static void test_loghandler(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *ctx) { struct log_handler_check *check = ei_get_user_data(ei); munit_assert_int(priority, >=, check->min_priority); munit_assert_string_equal(message, check->expected_message); /* Second arg is the line number, if this code ever moves further up, * this test may fail */ munit_assert_int(ei_log_context_get_line(ctx), >, 170); munit_assert_true(strendswith(ei_log_context_get_file(ctx), "libei-log.c")); munit_assert_string_equal(ei_log_context_get_func(ctx), "test_log_handler"); } MUNIT_TEST(test_log_handler) { struct log_handler_check check = {0}; struct ei *ei = ei_new(&check); munit_assert_ptr_equal(ei->log.handler, ei_default_log_handler); munit_assert_int(ei->log.priority, ==, EI_LOG_PRIORITY_INFO); ei_log_set_handler(ei, test_loghandler); check.min_priority = EI_LOG_PRIORITY_INFO; /* default */ check.expected_message = "info message"; log_debug(ei, "default is below this level"); log_info(ei, "info message"); check.expected_message = "error message"; log_error(ei, "error message"); check.min_priority = EI_LOG_PRIORITY_ERROR; ei_log_set_priority(ei, EI_LOG_PRIORITY_ERROR); munit_assert_int(ei->log.priority, ==, EI_LOG_PRIORITY_ERROR); log_error(ei, "error message"); log_warn(ei, "warn message"); log_info(ei, "info message"); log_debug(ei, "debug message"); ei_log_set_priority(ei, EI_LOG_PRIORITY_DEBUG); /* Make sure they come through at the right priority */ check.min_priority = EI_LOG_PRIORITY_ERROR; check.expected_message = "error message"; log_error(ei, "error message"); check.min_priority = EI_LOG_PRIORITY_WARNING; check.expected_message = "warn message"; log_warn(ei, "warn message"); check.min_priority = EI_LOG_PRIORITY_INFO; check.expected_message = "info message"; log_info(ei, "info message"); check.min_priority = EI_LOG_PRIORITY_DEBUG; check.expected_message = "debug message"; log_debug(ei, "debug message"); /* Can't capture this easily, so this is mostly just a crash test */ ei_log_set_handler(ei, NULL); munit_assert_ptr_equal(ei->log.handler, ei_default_log_handler); log_debug(ei, "ignore this, testing NULL log handler"); ei_unref(ei); return MUNIT_OK; } #endif