From 9c9139d0eb4244c66123fdbfd211105ede8c4b42 Mon Sep 17 00:00:00 2001 From: Federico Angelilli Date: Sun, 13 Apr 2025 15:07:20 +0200 Subject: Add structured logging --- src/any_log.h | 298 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 202 insertions(+), 96 deletions(-) (limited to 'src/any_log.h') diff --git a/src/any_log.h b/src/any_log.h index 1cb298a..5667f03 100644 --- a/src/any_log.h +++ b/src/any_log.h @@ -1,4 +1,4 @@ -// any_log +// any_log v0.3.0 // // A single-file library that provides a simple and somewhat opinionated // interface for logging and structured logging. @@ -21,22 +21,23 @@ #define ANY_LOG_INCLUDE #include +#include // These values represent the decreasing urgency of a log invocation. // -// panic: indicates a fatal error and using it will result in -// the program termination (see any_log_panic) +// - panic: indicates a fatal error and using it will result in +// the program termination (see any_log_panic) // -// error: indicates a (non-fatal) error +// - error: indicates a (non-fatal) error // -// warn: indicates a warning +// - warn: indicates a warning // -// info: indicates an information (potentially useful to the user) +// - info: indicates an information (potentially useful to the user) // -// debug: indicates debugging information +// - debug: indicates debugging information // -// trace: indicates verbose debugging information and can be completely -// disabled by defining ANY_LOG_NO_TRACE before including +// - trace: indicates verbose debugging information and can be completely +// disabled by defining ANY_LOG_NO_TRACE before including // // NOTE: The value ANY_LOG_ALL is not an actual level and it is used as // a sentinel to indicate the last value of any_log_level_t @@ -52,7 +53,7 @@ typedef enum { } any_log_level_t; // The value of ANY_LOG_MODULE is used to indicate the current module. -// By default it is defined as __FILE__, so that it will coincide with the +// By default it is defined as __FILE__, which should expand to the // source file path (relative to the compiler cwd). // // You can customize ANY_LOG_MODULE before including the header by simply @@ -70,6 +71,10 @@ typedef enum { #define ANY_LOG_FUNC __func__ #endif +#ifndef ANY_LOG_LINE +#define ANY_LOG_LINE __LINE__ +#endif + // log_panic is implemented with the function any_log_panic, which takes // some extra parameters compared with the other log levels. This way we can // include as many information as possible for identifying fatal errors. @@ -77,10 +82,13 @@ typedef enum { // You can change the format string and exit function in the implementation // (see ANY_LOG_EXIT, ANY_LOG_PANIC_BEFORE and ANY_LOG_PANIC_AFTER). // +// This function was made with a different interface compared to the other +// logging functions in order to be marked as *noreturn*. +// // NOTE: log_panic will always terminate the program and should be used only -// for non recoverable situations! For normal errors just use log_error +// for non recoverable situations! For normal errors just use log_error. // -#define log_panic(...) any_log_panic(__FILE__, __LINE__, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__) +#define log_panic(...) any_log_panic(__FILE__, ANY_LOG_LINE, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__) // log_[level] provide normal printf style logging. // @@ -165,22 +173,42 @@ typedef enum { // // In the implementation you can customize the format of every key-value pair // and of the message. This is useful if you want to adhere to a structured -// logging format like JSON. For example +// logging format. An example implementation for JSON follows: // // #define ANY_LOG_IMPLEMENT -// #define ANY_LOG_VALUE_BEFORE(level, module, func, message) -// "{\"module\": \"%s\", \"function\": \"%s\", \"level\": \"%s\", \"message\": \"%s\", ", -// module, func, any_log_level_strings[level], message -// -// #define ANY_LOG_VALUE_BOOL(key, value) "\"%s\": %s", key, (value ? "true" : "false") -// #define ANY_LOG_VALUE_INT(key, value) "\"%s\": %d", key, value -// #define ANY_LOG_VALUE_HEX(key, value) "\"%s\": %u", key, value -// #define ANY_LOG_VALUE_LONG(key, value) "\"%s\": %ld", key, value -// #define ANY_LOG_VALUE_PTR(key, value) "\"%s\": \"%p\"", key, value -// #define ANY_LOG_VALUE_DOUBLE(key, value) "\"%s\": %lf", key, value -// #define ANY_LOG_VALUE_STRING(key, value) "\"%s \": \"%s\"", key, value -// #define ANY_LOG_VALUE_AFTER(level, module, func, message) "}\n" // #define ANY_LOG_NO_GENERIC +// +// #define ANY_LOG_VALUE_BEFORE(stream, level, module, func, message) +// fprintf(stream, "{\"module\": \"%s\", \"function\": \"%s\", \"level\": \"%s\", \"message\": \"%s\", ", +// module, func, any_log_level_strings[level], message) +// +// #define ANY_LOG_VALUE_BOOL(stream, key, value) +// fprintf(stream, "\"%s\": %s", key, value ? "true" : "false") +// +// #define ANY_LOG_VALUE_INT(stream, key, value) +// fprintf(stream, "\"%s\": %d", key, value) +// +// #define ANY_LOG_VALUE_HEX(stream, key, value) +// fprintf(stream, "\"%s\": %u", key, value) +// +// #define ANY_LOG_VALUE_LONG(stream, key, value) +// fprintf(stream, "\"%s\": %ld", key, value) +// +// #define ANY_LOG_VALUE_PTR(stream, key, value) +// do { +// if (value == NULL) fprintf(stream, "\"%s\": none", key); +// else fprintf(stream, "\"%s\": %lu", key, (uintptr_t)value); +// } while (false) +// +// #define ANY_LOG_VALUE_DOUBLE(stream, key, value) +// fprintf(stream, "\"%s\": %lf", key, value) +// +// #define ANY_LOG_VALUE_STRING(stream, key, value) +// fprintf(stream, "\"%s \": \"%s\"", key, value) +// +// #define ANY_LOG_VALUE_AFTER(stream, level, module, func, message) +// fprintf(stream, "}\n") +// // #include "any_log.h" // // As with log_trace and log_debug, log_value_trace and log_value_debug can be @@ -217,18 +245,54 @@ typedef void (*any_log_formatter_t)(FILE *stream, ANY_LOG_VALUE_GENERIC_TYPE val #endif +// In a multithreaded application you may encounter interleaved writes when +// different threads try to log at the same time. +// Stream locking can be used to prevent such problems. +// +// The macro ANY_LOG_FLOCK is called before any use of fprintf and should +// lock the logging stream for the current thread, while ANY_LOG_FUNLOCK +// will be called at the end of the writes to unlock it. +// +// You can define ANY_LOG_LOCKING to automatically define the aforementioned +// macros using flockfile(3) (from POSIX 2001). +// +#ifdef ANY_LOG_LOCKING + +#ifndef ANY_LOG_FLOCK +#define ANY_LOG_FLOCK(stream) flockfile(stream) +#define ANY_LOG_FUNLOCK(stream) funlockfile(stream) +#endif + +#else + +// Disable file locking if the macros are not defined. +// +#ifndef ANY_LOG_FLOCK +#define ANY_LOG_FLOCK(...) +#define ANY_LOG_FUNLOCK(...) +#endif + +#endif + +// This is a wrapper for GCC-style attributes, used by the functions below. +// #ifdef __GNUC__ #define ANY_LOG_ATTRIBUTE(...) __attribute__((__VA_ARGS__)) #else #define ANY_LOG_ATTRIBUTE(...) #endif -// All log functions will output to the file stream specified by any_log_stream. +#ifndef ANY_LOG_NORETURN +#define ANY_LOG_NORETURN ANY_LOG_ATTRIBUTE(noreturn) +#endif + +// All log functions will output to the file streams specified by any_log_streams, +// depending on their log level. // -// You should always set this global to a valid stream (eg in main) before +// You should always initialize these to valid streams (eg in main) before // invoking any_log macros or functions! // -extern FILE *any_log_stream; +extern FILE *any_log_streams[ANY_LOG_ALL]; // All log functions will ignore the message if the level is below the // threshold specified in any_log_level. @@ -242,10 +306,12 @@ extern any_log_level_t any_log_level; // This is a simple utility function that sets both any_log_level and // any_log_stream with a single call. // +// The streams for all log levels will be set to the provided one. +// // Call this function before any use of log_* (for example in main) to // correctly initialize the library! // -void any_log_init(FILE *stream, any_log_level_t level); +void any_log_init(any_log_level_t level, FILE *stream); // An array containing the strings corresponding to the log levels. // @@ -265,15 +331,17 @@ any_log_level_t any_log_level_from_string(const char *string); // The default format macros for all logging function uses the global // any_log_color to get the color sequence to use when printing the logs. // -// By default this global points to any_log_colors_default, but you can +// By default this global points to any_log_colors_enabled, but you can // set it to any_log_colors_disabled or to a custom array of your choice. // -// The array you give should have length ANY_LOG_ALL + 3 and this organization +// The array you give should have length ANY_LOG_ALL + 3 and the following layout +// +// - from ANY_LOG_PANIC to ANY_LOG_TRACE: the colors indexed by log levels +// - ANY_LOG_ALL: the color reset sequence +// - ANY_LOG_ALL + 1: the color for the module +// - ANY_LOG_ALL + 2: the color for the function // -// from ANY_LOG_PANIC to ANY_LOG_TRACE: the colors indexed by log levels -// ANY_LOG_ALL: the color reset sequence -// ANY_LOG_ALL + 1: the color for the module -// ANY_LOG_ALL + 2: the color for the function +// If the macro ANY_LOG_NO_COLOR by default colors will be disabled. // // NOTE: If you changed the default format in the implementation // (by redefining ANY_LOG_FORMAT_*, ANY_LOG_VALUE_* and ANY_LOG_PANIC_*), @@ -287,7 +355,7 @@ extern const char **any_log_colors; // See ANY_LOG_[level]_COLOR, ANY_LOG_RESET_COLOR, ANY_LOG_MODULE_COLOR and // ANY_LOG_FUNC_COLOR in the implementation. // -extern const char *any_log_colors_default[ANY_LOG_ALL + 3]; +extern const char *any_log_colors_enabled[ANY_LOG_ALL + 3]; // This array contains empty strings. extern const char *any_log_colors_disabled[ANY_LOG_ALL + 3]; @@ -296,17 +364,17 @@ extern const char *any_log_colors_disabled[ANY_LOG_ALL + 3]; // See the above explanations on how to use logging. ANY_LOG_ATTRIBUTE(format(printf, 4, 5)) -ANY_LOG_ATTRIBUTE(nonnull(4)) +ANY_LOG_ATTRIBUTE(nonnull(2, 3, 4)) void any_log_format(any_log_level_t level, const char *module, const char *func, const char *format, ...); -ANY_LOG_ATTRIBUTE(nonnull(4)) +ANY_LOG_ATTRIBUTE(nonnull(2, 3, 4)) void any_log_value(any_log_level_t level, const char *module, const char *func, const char *message, ...); -ANY_LOG_ATTRIBUTE(noreturn) +ANY_LOG_NORETURN ANY_LOG_ATTRIBUTE(format(printf, 5, 6)) -ANY_LOG_ATTRIBUTE(nonnull(1, 4)) +ANY_LOG_ATTRIBUTE(nonnull(1, 3, 4, 5)) void any_log_panic(const char *file, int line, const char *module, const char *func, const char *format, ...); @@ -322,9 +390,9 @@ void any_log_panic(const char *file, int line, const char *module, // For the C standard we can't assign stdout or any other streams here, // since they are not constant. // -// Thus it is imperative to set this variable to a valid FILE * before using it! +// This variable MUST be properly initialized before doing any logging! // -FILE *any_log_stream = NULL; +FILE *any_log_streams[ANY_LOG_ALL] = { 0 }; // The default value for any_log_level #ifndef ANY_LOG_LEVEL_DEFAULT @@ -334,10 +402,12 @@ FILE *any_log_stream = NULL; any_log_level_t any_log_level = ANY_LOG_LEVEL_DEFAULT; // Utility function to initialize the library -void any_log_init(FILE *stream, any_log_level_t level) +// +void any_log_init(any_log_level_t level, FILE *stream) { - any_log_stream = stream; any_log_level = level; + for (int i = 0; i < ANY_LOG_ALL; i++) + any_log_streams[i] = stream; } // Log level strings @@ -372,7 +442,7 @@ const char *any_log_level_strings[ANY_LOG_ALL] = { const char *any_log_level_to_string(any_log_level_t level) { return level >= ANY_LOG_PANIC && level <= ANY_LOG_TRACE - ? any_log_level_strings[level] : ""; + ? any_log_level_strings[level] : NULL; } any_log_level_t any_log_level_from_string(const char *string) @@ -389,7 +459,15 @@ any_log_level_t any_log_level_from_string(const char *string) // interface for setting the colors. If you decide to change the default // log format macros, feel free to ignore all this variables. // -const char **any_log_colors = any_log_colors_default; +// If the macro ANY_LOG_NO_COLOR is defined, any_log_colors_disabled +// will be used instead of any_log_colors_enabled. +// +const char **any_log_colors = +#ifdef ANY_LOG_NO_COLOR + any_log_colors_disabled; +#else + any_log_colors_enabled; +#endif // Log colors indexed by log level, with the addition of special colors // for func, module and reset sequence. @@ -422,7 +500,7 @@ const char **any_log_colors = any_log_colors_default; #define ANY_LOG_FUNC_COLOR "\x1b[1m" #endif -const char *any_log_colors_default[ANY_LOG_ALL + 3] = { +const char *any_log_colors_enabled[ANY_LOG_ALL + 3] = { ANY_LOG_PANIC_COLOR, ANY_LOG_ERROR_COLOR, ANY_LOG_WARN_COLOR, @@ -440,14 +518,16 @@ const char *any_log_colors_disabled[ANY_LOG_ALL + 3] = { // Format for any_log_format (used at the start) #ifndef ANY_LOG_FORMAT_BEFORE -#define ANY_LOG_FORMAT_BEFORE(level, module, func) \ - "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \ - func, any_log_colors[ANY_LOG_ALL], any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL] +#define ANY_LOG_FORMAT_BEFORE(stream, level, module, func) \ + fprintf(stream, "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[ANY_LOG_ALL + 2], func, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL]) #endif // Format for any_log_format (used at the end) #ifndef ANY_LOG_FORMAT_AFTER -#define ANY_LOG_FORMAT_AFTER(level, module, func) "\n" +#define ANY_LOG_FORMAT_AFTER(stream, level, module, func) \ + fprintf(stream, "\n") #endif void any_log_format(any_log_level_t level, const char *module, @@ -456,14 +536,19 @@ void any_log_format(any_log_level_t level, const char *module, if (level > any_log_level) return; - fprintf(any_log_stream, ANY_LOG_FORMAT_BEFORE(level, module, func)); + FILE *stream = any_log_streams[level]; + ANY_LOG_FLOCK(stream); + + ANY_LOG_FORMAT_BEFORE(stream, level, module, func); va_list args; va_start(args, format); - vfprintf(any_log_stream, format, args); + vfprintf(stream, format, args); va_end(args); - fprintf(any_log_stream, ANY_LOG_FORMAT_AFTER(level, module, func)); + ANY_LOG_FORMAT_AFTER(stream, level, module, func); + + ANY_LOG_FUNLOCK(stream); // NOTE: Suppress compiler warning if the user customizes the format string // and doesn't use these values in it @@ -480,58 +565,67 @@ void any_log_format(any_log_level_t level, const char *module, // Format for any_log_value (used at the start) #ifndef ANY_LOG_VALUE_BEFORE -#define ANY_LOG_VALUE_BEFORE(level, module, func, message) \ - "[%s%s%s %s%s%s] %s%s%s: %s [", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \ - func, any_log_colors[ANY_LOG_ALL], any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL], message +#define ANY_LOG_VALUE_BEFORE(stream, level, module, func, message) \ + fprintf(stream, "[%s%s%s %s%s%s] %s%s%s: %s [", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[ANY_LOG_ALL + 2], func, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL], message) #endif // Format for any_log_value (used at the end) #ifndef ANY_LOG_VALUE_AFTER -#define ANY_LOG_VALUE_AFTER(level, module, func, message) "]\n" +#define ANY_LOG_VALUE_AFTER(stream, level, module, func, message) \ + fprintf(stream, "]\n") #endif // Format for pairs with a bool value // // NOTE: C automatically promotes boolean types to int #ifndef ANY_LOG_VALUE_BOOL -#define ANY_LOG_VALUE_BOOL(key, value) "%s=%s", key, (value ? "true" : "false") +#define ANY_LOG_VALUE_BOOL(stream, key, value) \ + fprintf(stream, "%s=%s", key, value ? "true" : "false") #endif // Format for pairs with an int value #ifndef ANY_LOG_VALUE_INT -#define ANY_LOG_VALUE_INT(key, value) "%s=%d", key, value +#define ANY_LOG_VALUE_INT(stream, key, value) \ + fprintf(stream, "%s=%d", key, value) #endif -// Format for pairs with an unsinged int value (hex by default) +// Format for pairs with an unsigned int value (hex by default) #ifndef ANY_LOG_VALUE_HEX -#define ANY_LOG_VALUE_HEX(key, value) "%s=%#x", key, value +#define ANY_LOG_VALUE_HEX(stream, key, value) \ + fprintf(stream, "%s=%#x", key, value) #endif // Format for pairs with a long int value #ifndef ANY_LOG_VALUE_LONG -#define ANY_LOG_VALUE_LONG(key, value) "%s=%ld", key, value +#define ANY_LOG_VALUE_LONG(stream, key, value) \ + fprintf(stream, "%s=%ld", key, value) #endif -// Format for pairs with a pointer value +// Format for pairs with a pointer value (void *) #ifndef ANY_LOG_VALUE_PTR -#define ANY_LOG_VALUE_PTR(key, value) "%s=%p", key, value +#define ANY_LOG_VALUE_PTR(stream, key, value) \ + fprintf(stream, "%s=%p", key, value) #endif // Format for pairs with a double value #ifndef ANY_LOG_VALUE_DOUBLE -#define ANY_LOG_VALUE_DOUBLE(key, value) "%s=%lf", key, value +#define ANY_LOG_VALUE_DOUBLE(stream, key, value) \ + fprintf(stream, "%s=%lf", key, value) #endif -// Format for pairs with a string value +// Format for pairs with a string value (char *) #ifndef ANY_LOG_VALUE_STRING -#define ANY_LOG_VALUE_STRING(key, value) "%s=\"%s\"", key, value +#define ANY_LOG_VALUE_STRING(stream, key, value) \ + fprintf(stream, "%s=\"%s\"", key, value) #endif #ifndef ANY_LOG_NO_GENERIC // Format custom types with the given formatter function #ifndef ANY_LOG_VALUE_GENERIC -#define ANY_LOG_VALUE_GENERIC(key, stream, formatter, value) \ +#define ANY_LOG_VALUE_GENERIC(stream, key, value, formatter) \ do { \ fprintf(stream, "%s=", key); \ formatter(stream, value); \ @@ -540,9 +634,11 @@ void any_log_format(any_log_level_t level, const char *module, #endif -// The default is to use string +// By default values will be interpreted as strings. +// Define ANY_LOG_VALUE_DEFAULT and ANY_LOG_VALUE_DEFAULT_TYPE to change this. +// #ifndef ANY_LOG_VALUE_DEFAULT -#define ANY_LOG_VALUE_DEFAULT(key, value) ANY_LOG_VALUE_STRING(key, value) +#define ANY_LOG_VALUE_DEFAULT(stream, key, value) ANY_LOG_VALUE_STRING(stream, key, value) #define ANY_LOG_VALUE_DEFAULT_TYPE char * #endif @@ -551,65 +647,67 @@ void any_log_format(any_log_level_t level, const char *module, #define ANY_LOG_VALUE_PAIR_SEP ", " #endif +// NOTE: This function should be called with at least one pair void any_log_value(any_log_level_t level, const char *module, const char *func, const char *message, ...) { if (level > any_log_level) return; - fprintf(any_log_stream, ANY_LOG_VALUE_BEFORE(level, module, func, message)); + FILE *stream = any_log_streams[level]; + ANY_LOG_FLOCK(stream); + + ANY_LOG_VALUE_BEFORE(stream, level, module, func, message); va_list args; va_start(args, message); - // NOTE: This function should be called with at least one pair char *key = va_arg(args, char *); - while (key != NULL) { if (key[0] != '\0' && key[1] == ANY_LOG_VALUE_TYPE_SEP) { key += 2; switch (tolower(key[-2])) { case 'b': { int value = va_arg(args, int); - fprintf(any_log_stream, ANY_LOG_VALUE_BOOL(key, value)); + ANY_LOG_VALUE_BOOL(stream, key, value); break; } case 'd': case 'i': { int value = va_arg(args, int); - fprintf(any_log_stream, ANY_LOG_VALUE_INT(key, value)); + ANY_LOG_VALUE_INT(stream, key, value); break; } case 'x': case 'u': { unsigned int value = va_arg(args, unsigned int); - fprintf(any_log_stream, ANY_LOG_VALUE_HEX(key, value)); + ANY_LOG_VALUE_HEX(stream, key, value); break; } case 'l': { long int value = va_arg(args, long int); - fprintf(any_log_stream, ANY_LOG_VALUE_LONG(key, value)); + ANY_LOG_VALUE_LONG(stream, key, value); break; } case 'p': { void *value = va_arg(args, void *); - fprintf(any_log_stream, ANY_LOG_VALUE_PTR(key, value)); + ANY_LOG_VALUE_PTR(stream, key, value); break; } case 'f': { double value = va_arg(args, double); - fprintf(any_log_stream, ANY_LOG_VALUE_DOUBLE(key, value)); + ANY_LOG_VALUE_DOUBLE(stream, key, value); break; } case 's': { char *value = va_arg(args, char *); - fprintf(any_log_stream, ANY_LOG_VALUE_STRING(key, value)); + ANY_LOG_VALUE_STRING(stream, key, value); break; } @@ -617,7 +715,7 @@ void any_log_value(any_log_level_t level, const char *module, case 'g': { any_log_formatter_t formatter = va_arg(args, any_log_formatter_t); ANY_LOG_VALUE_GENERIC_TYPE value = va_arg(args, ANY_LOG_VALUE_GENERIC_TYPE); - ANY_LOG_VALUE_GENERIC(key, any_log_stream, formatter, value); + ANY_LOG_VALUE_GENERIC(stream, key, value, formatter); break; } #endif @@ -625,20 +723,22 @@ void any_log_value(any_log_level_t level, const char *module, goto tdefault; } } else { -tdefault: +tdefault:; ANY_LOG_VALUE_DEFAULT_TYPE value = va_arg(args, ANY_LOG_VALUE_DEFAULT_TYPE); - fprintf(any_log_stream, ANY_LOG_VALUE_DEFAULT(key, value)); + ANY_LOG_VALUE_DEFAULT(stream, key, value); } key = va_arg(args, char *); if (key == NULL) break; - fprintf(any_log_stream, ANY_LOG_VALUE_PAIR_SEP); + fprintf(stream, ANY_LOG_VALUE_PAIR_SEP); } va_end(args); - fprintf(any_log_stream, ANY_LOG_VALUE_AFTER(level, module, func, message)); + + ANY_LOG_VALUE_AFTER(stream, level, module, func, message); + ANY_LOG_FUNLOCK(stream); (void)module; (void)func; @@ -658,16 +758,17 @@ tdefault: // Format for any_log_panic (used at the start) #ifndef ANY_LOG_PANIC_BEFORE -#define ANY_LOG_PANIC_BEFORE(file, line, module, func) \ - "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \ - func, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_PANIC], any_log_level_strings[ANY_LOG_PANIC], any_log_colors[ANY_LOG_ALL] +#define ANY_LOG_PANIC_BEFORE(stream, file, line, module, func) \ + fprintf(stream, "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[ANY_LOG_ALL + 2], func, any_log_colors[ANY_LOG_ALL], \ + any_log_colors[ANY_LOG_PANIC], any_log_level_strings[ANY_LOG_PANIC], any_log_colors[ANY_LOG_ALL]) #endif // Format for any_log_panic (used at the start) #ifndef ANY_LOG_PANIC_AFTER -#define ANY_LOG_PANIC_AFTER(file, line, module, func) \ - "\n%spanic was invoked from%s %s:%d (%s%s%s)\n", any_log_colors[ANY_LOG_PANIC], any_log_colors[ANY_LOG_ALL], \ - file, line, any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL] +#define ANY_LOG_PANIC_AFTER(stream, file, line, module, func) \ + fprintf(stream, "\n%spanic was invoked from%s %s:%d (%s%s%s)\n", any_log_colors[ANY_LOG_PANIC], \ + any_log_colors[ANY_LOG_ALL], file, line, any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL]) #endif // NOTE: This function *exceptionally* gets more location information @@ -676,14 +777,19 @@ tdefault: void any_log_panic(const char *file, int line, const char *module, const char *func, const char *format, ...) { - fprintf(any_log_stream, ANY_LOG_PANIC_BEFORE(file, line, module, func)); + FILE *stream = any_log_streams[ANY_LOG_PANIC]; + ANY_LOG_FLOCK(stream); + + ANY_LOG_PANIC_BEFORE(stream, file, line, module, func); va_list args; va_start(args, format); - vfprintf(any_log_stream, format, args); + vfprintf(stream, format, args); va_end(args); - fprintf(any_log_stream, ANY_LOG_PANIC_AFTER(file, line, module, func)); + ANY_LOG_PANIC_AFTER(stream, file, line, module, func); + + ANY_LOG_FUNLOCK(stream); (void)module; (void)func; @@ -700,7 +806,7 @@ void any_log_panic(const char *file, int line, const char *module, // MIT License // -// Copyright (c) 2024 Federico Angelilli +// Copyright (c) 2024-2025 Federico Angelilli // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal -- cgit v1.2.3