/* * Original work Copyright (c) 2020 rxi (MIT License) * * ORIGINAL: * Copyright (c) 2020 rxi * * 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 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. * * * Modified work Copyright (c) 2026 Emilia Marigold (AGPLv3) * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * See the LICENSE file for the full AGPLv3 terms and conditions. ***/ #include "new_log.h" #define MAX_CALLBACKS 32 typedef struct callback_struct { log_function function; void *user_data; int severity_level; } callback_type; static struct global_log_data_struct { void *user_data; lock_function lock; int severity_level; bool quiet; callback_type callbacks[MAX_CALLBACKS]; } global_log_data_struct; static const char * level_strings[] = { "TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL" }; #ifdef LOG_USE_COLOR static const char *level_colors[] = { "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m" }; #endif static void stdout_callback(log_event_type *event) { char buffer[16]; buffer[strftime(buffer, sizeof(buffer), "%H:%M:%S", event->local_time)] = '\0'; #ifdef LOG_USE_COLOR fprintf(event->user_data, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", buffer, level_colors[event->severity_level], level_strings[event->severity_level], event->file, event->line); #else fprintf(event->user_data, "%s %-5s %s:%d: ", buffer, level_strings[event->severity_level], event->file, event->line); #endif vfprintf(event->user_data, event->format, event->argument_pointer); fprintf(event->user_data, "\n"); fflush(event->user_data); } static void file_callback(log_event_type *event) { char buffer[64]; buffer[strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", event->local_time)] = '\0'; fprintf(event->user_data, "%s %-5s %s:%d: ", buffer, level_strings[event->severity_level], event->file, event->line); vfprintf(event->user_data, event->format, event->argument_pointer); fprintf(event->user_data, "\n"); fflush(event->user_data); } static void lock(void) { if (global_log_data_struct.lock) { global_log_data_struct.lock(true, global_log_data_struct.user_data); } } static void unlock(void) { if (global_log_data_struct.lock) { global_log_data_struct.lock(false, global_log_data_struct.user_data); } } const char* log_level_string(int severity_level) { return level_strings[severity_level]; } void log_set_lock(lock_function function, void *user_data) { global_log_data_struct.lock = function; global_log_data_struct.user_data = user_data; } void log_set_level(int severity_level) { global_log_data_struct.severity_level = severity_level; } void log_set_quiet(bool should_be_quiet) { global_log_data_struct.quiet = should_be_quiet; } int log_add_callback(log_function function, void *user_data, int level) { for (int i = 0; i < MAX_CALLBACKS; i++) { if (global_log_data_struct.callbacks[i].function) { continue; } global_log_data_struct.callbacks[i] = (callback_type) { function, user_data, level }; return 0; } return -1; } int log_add_file_pointer(FILE *file_pointer, int level) { return log_add_callback(file_callback, file_pointer, level); } static void init_event(log_event_type *event, void *user_data) { if (!event->local_time) { time_t timer = time(NULL); event->local_time = localtime(&timer); } event->user_data = user_data; } void log_log(int severity_level, const char *file, int line, const char *format, ...) { log_event_type event = { .format = format, .file = file, .line = line, .severity_level = severity_level, }; lock(); if (!global_log_data_struct.quiet && severity_level >= global_log_data_struct.severity_level) { init_event(&event, stderr); va_start(event.argument_pointer, format); stdout_callback(&event); va_end(event.argument_pointer); } for (int i = 0; i < MAX_CALLBACKS && global_log_data_struct.callbacks[i].function; i++) { callback_type *callback_pointer = &global_log_data_struct.callbacks[i]; if (severity_level >= callback_pointer->severity_level) { init_event(&event, callback_pointer->user_data); va_start(event.argument_pointer, format); callback_pointer->function(&event); va_end(event.argument_pointer); } } unlock(); }