diff options
| author | Federico Angelilli <code@fedang.net> | 2025-04-13 15:07:20 +0200 |
|---|---|---|
| committer | Federico Angelilli <code@fedang.net> | 2025-04-13 15:07:20 +0200 |
| commit | 9c9139d0eb4244c66123fdbfd211105ede8c4b42 (patch) | |
| tree | 89455d52236362c3cf953392b1cdc96aa5194fb0 | |
| parent | 0aed18911e27b80a4dbe1a252f9485382a4073c7 (diff) | |
Add structured logging
| -rw-r--r-- | example.lua | 30 | ||||
| -rw-r--r-- | src/any_log.h | 298 | ||||
| -rw-r--r-- | src/comet.c | 13 | ||||
| -rw-r--r-- | src/lua/log.c | 255 | ||||
| -rw-r--r-- | src/util.c | 1 |
5 files changed, 440 insertions, 157 deletions
diff --git a/example.lua b/example.lua index cba5308..3ae2144 100644 --- a/example.lua +++ b/example.lua @@ -2,7 +2,7 @@ log.info("CRAZY") function maybe() log.warn("does it work?") - return log.panic("it does") + return log.error("it does") end log.info("%d sus", 10000) @@ -11,4 +11,32 @@ maybe() log.info("%d BIGGER %f?", 0-1, 0/0) +log.format("info", "sus %d", 10) + +local co = coroutine.create(function() + print("Coroutine running") +end) + +print(type(debug.getinfo(maybe))) + +log.value_info("hello this", { + is = "wow", + structured = "logging", + "noname", + 10, + crazy = { + what = "is", + going = "on", + }, + what = log.format, +-- file = io.open("example.txt", "w"), + extra = co, + info = debug.getinfo(maybe), +}) + + +-- errors +log.value_info("help") +log.value_info("help", 10) +log.format("lmao", "sus %d", 10) log.warn("%u %d","a") 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 <stdio.h> +#include <stdbool.h> // 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 diff --git a/src/comet.c b/src/comet.c index e8dc5e5..9be94bf 100644 --- a/src/comet.c +++ b/src/comet.c @@ -12,17 +12,12 @@ #include "lua/api.h" #ifdef RELEASE -#define ANY_LOG_VALUE_BEFORE(level, module, func, message) \ - "[%s%s%s] %s%s%s: %s [", 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_FORMAT_BEFORE(level, module, func) \ - "[%s%s%s] %s%s%s: ", 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_FUNC_COLOR "" #define ANY_LOG_NO_TRACE #endif -#define ANY_LOG_VALUE_STRING(key, value) "%s=\"%s\"", key, value ? value : "(null)" +#define ANY_LOG_VALUE_STRING(stream, key, value) \ + fprintf(stream, "%s=\"%s\"", key, value ? value : "(null)") + #define ANY_LOG_IMPLEMENT #include "any_log.h" @@ -69,7 +64,7 @@ int main(int argc, char **argv) if (argc != 1 && !strcmp(argv[1], "--trace")) log_level = ANY_LOG_TRACE; - any_log_init(stdout, log_level); + any_log_init(log_level, stdout); config_t config; config_init(&config); diff --git a/src/lua/log.c b/src/lua/log.c index 4bf4e16..b49c003 100644 --- a/src/lua/log.c +++ b/src/lua/log.c @@ -1,37 +1,26 @@ #include <assert.h> #include <string.h> -#include "api.h" +#include "log.h" #include "../any_log.h" -static lua_CFunction lua_log_stringformat = NULL; - -static int lua_log_format(lua_State *state, any_log_level_t level) { - - // Use string.format to print safely - // - int n_args = lua_gettop(state); - lua_pushcfunction(state, lua_log_stringformat); +static lua_CFunction lua_log_getinfo = NULL; - for (int i = 1; i <= n_args; ++i) - lua_pushvalue(state, i); +static lua_CFunction lua_log_stringformat = NULL; - lua_call(state, n_args, 1); - const char *message = lua_tostring(state, -1); +static lua_CFunction lua_log_tostring = NULL; - lua_getglobal(state, "debug"); - lua_getfield(state, -1, "getinfo"); +static inline void lua_log_debuginfo(lua_State *state, const char **source, const char **func, + char buffer[], size_t size) +{ + lua_pushcfunction(state, lua_log_getinfo); - // Ignore the first frame (lua_log_format itself) + // Ignore the first frame (the c function itself) lua_pushnumber(state, 2); // S: source, l: currentline, n: name lua_pushstring(state, "Sln"); - const char *source = "?", *func = "?"; - char buffer[4096]; - if (lua_pcall(state, 2, 1, 0) == LUA_OK) { - lua_getfield(state, -1, "short_src"); const char *shortsrc = lua_tostring(state, -1); @@ -40,73 +29,239 @@ static int lua_log_format(lua_State *state, any_log_level_t level) { if (!strncmp(shortsrc, "[string ", 8)) { int length = strlen(shortsrc); - snprintf(buffer, sizeof(buffer), "lua %.*s", length - 9, shortsrc + 8); + snprintf(buffer, size, "lua %.*s", length - 9, shortsrc + 8); } else { - snprintf(buffer, sizeof(buffer), "lua %s:%d", shortsrc, line); + snprintf(buffer, size, "lua %s:%d", shortsrc, line); } lua_getfield(state, -3, "name"); const char *name = lua_tostring(state, -1); - source = buffer; - func = name ? name : "..."; + *source = buffer; + *func = name ? name : "..."; + } else { + log_trace("Failed to retrieve Lua debug information"); } lua_pop(state, 3); +} + +// Normal logging with any_log_format +// +static int lua_log_wrapper(lua_State *state, int skip_args, any_log_level_t level) +{ + // Use string.format to print safely + // + int n_args = lua_gettop(state); + lua_pushcfunction(state, lua_log_stringformat); + + for (int i = 1; i <= n_args; ++i) + lua_pushvalue(state, skip_args + i); + + lua_call(state, n_args, 1); + const char *message = lua_tostring(state, -1); + + const char *source = "?", *func = "?"; + char buffer[4096]; + lua_log_debuginfo(state, &source, &func, buffer, sizeof(buffer)); + any_log_format(level, source, func, "%s", message); return 0; } -static int lua_log_log(lua_State *state) { - const char *level = luaL_checkstring(state, 1); - lua_pop(state, 1); - any_log_level_t l = any_log_level_from_string(level); - assert(l > ANY_LOG_ALL); - return lua_log_format(state, l); +static int lua_log_format(lua_State *state) +{ + const char *param = luaL_checkstring(state, 1); + any_log_level_t level = any_log_level_from_string(param); + + if (level == ANY_LOG_ALL) + luaL_error(state, "Invalid log level, got \"%s\"", param); + + return lua_log_wrapper(state, 1, level); } -static int lua_log_panic(lua_State *state) { - return lua_log_format(state, ANY_LOG_PANIC); +static int lua_log_error(lua_State *state) +{ + return lua_log_wrapper(state, 0, ANY_LOG_ERROR); } -static int lua_log_error(lua_State *state) { - return lua_log_format(state, ANY_LOG_ERROR); +static int lua_log_warn(lua_State *state) +{ + return lua_log_wrapper(state, 0, ANY_LOG_WARN); } -static int lua_log_warn(lua_State *state) { - return lua_log_format(state, ANY_LOG_WARN); +static int lua_log_info(lua_State *state) +{ + return lua_log_wrapper(state, 0, ANY_LOG_INFO); } -static int lua_log_info(lua_State *state) { - return lua_log_format(state, ANY_LOG_INFO); +static int lua_log_debug(lua_State *state) +{ + return lua_log_wrapper(state, 0, ANY_LOG_DEBUG); } -static int lua_log_debug(lua_State *state) { - return lua_log_format(state, ANY_LOG_DEBUG); +static int lua_log_trace(lua_State *state) +{ + return lua_log_wrapper(state, 0, ANY_LOG_TRACE); } -static int lua_log_trace(lua_State *state) { - return lua_log_format(state, ANY_LOG_TRACE); +// Structured logging with any_log_value +// +static int lua_log_wrapperv(lua_State *state, any_log_level_t level) +{ + const char *message = lua_tostring(state, 1); + lua_remove(state, 1); + luaL_checktype(state, 1, LUA_TTABLE); + + const char *source = "?", *func = "?"; + char buffer[4096]; + lua_log_debuginfo(state, &source, &func, buffer, sizeof(buffer)); + + any_log_value(level, source, func, message, + "g:value", ANY_LOG_FORMATTER(lua_print_value), state, + NULL); + return 0; +} + +static int lua_log_formatv(lua_State *state) +{ + const char *param = luaL_checkstring(state, 1); + any_log_level_t level = any_log_level_from_string(param); + + if (level == ANY_LOG_ALL) + luaL_error(state, "Invalid log level, got \"%s\"", param); + + lua_remove(state, 1); + return lua_log_wrapperv(state, level); +} + +static int lua_log_errorv(lua_State *state) +{ + return lua_log_wrapperv(state, ANY_LOG_ERROR); +} + +static int lua_log_warnv(lua_State *state) +{ + return lua_log_wrapperv(state, ANY_LOG_WARN); +} + +static int lua_log_infov(lua_State *state) +{ + return lua_log_wrapperv(state, ANY_LOG_INFO); +} + +static int lua_log_debugv(lua_State *state) +{ + return lua_log_wrapperv(state, ANY_LOG_DEBUG); +} + +static int lua_log_tracev(lua_State *state) +{ + return lua_log_wrapperv(state, ANY_LOG_TRACE); +} + +void lua_print_value(FILE *stream, lua_State *state) +{ + lua_print_value_at(stream, state, 1); +} + +void lua_print_value_at(FILE *stream, lua_State *state, int index) +{ + switch (lua_type(state, index)) { + case LUA_TNIL: + fprintf(stream, "nil"); + break; + + case LUA_TNUMBER: + fprintf(stream, "%lf", lua_tonumber(state, index)); + break; + + case LUA_TBOOLEAN: + fprintf(stream, lua_toboolean(state, index) ? "true" : "false"); + break; + + case LUA_TSTRING: + fprintf(stream, "\"%s\"", lua_tostring(state, index)); + break; + + case LUA_TTABLE: + fputc('{', stream); + + bool first = true; + lua_pushnil(state); + while (lua_next(state, index) != 0) { + if (!first) { + fputc(',', stream); + fputc(' ', stream); + } + first = false; + + if (lua_isnumber(state, -2)) { + fprintf(stream, "[%lld] = ", lua_tointeger(state, -2)); + } else { + fprintf(stream, "%s = ", lua_tostring(state, -2)); + } + + lua_print_value_at(stream, state, lua_gettop(state)); + lua_pop(state, 1); + } + + fputc('}', stream); + break; + + + case LUA_TFUNCTION: + fprintf(stream, "function (%p)", lua_topointer(state, index)); + break; + + case LUA_TTHREAD: + fprintf(stream, "thread (%p)", lua_tothread(state, index)); + break; + + default: + lua_pushcfunction(state, lua_log_tostring); + lua_pushvalue(state, index); + lua_call(state, 1, 1); + fprintf(stream, "%s", lua_tostring(state, -1)); + lua_pop(state, 1); + break; + } } int lua_log_library(lua_State *state) { static const luaL_Reg library[] = { - { "log", lua_log_log }, - { "panic", lua_log_panic }, - { "error", lua_log_error }, - { "warn", lua_log_warn }, - { "info", lua_log_info }, - { "debug", lua_log_debug }, - { "trace", lua_log_trace }, + // TODO: Is it wise to allow panic from Lua? + //{ "panic", lua_log_panic }, + + { "format", lua_log_format }, + { "error", lua_log_error }, + { "warn", lua_log_warn }, + { "info", lua_log_info }, + { "debug", lua_log_debug }, + { "trace", lua_log_trace }, + + { "value_format", lua_log_formatv }, + { "value_error", lua_log_errorv }, + { "value_warn", lua_log_warnv }, + { "value_info", lua_log_infov }, + { "value_debug", lua_log_debugv }, + { "value_trace", lua_log_tracev }, { NULL, NULL }, }; + lua_getglobal(state, "debug"); + lua_getfield(state, -1, "getinfo"); + lua_log_getinfo = lua_tocfunction(state, -1); + lua_getglobal(state, "string"); lua_getfield(state, -1, "format"); lua_log_stringformat = lua_tocfunction(state, -1); - lua_pop(state, 2); + lua_getglobal(state, "tostring"); + lua_log_tostring = lua_tocfunction(state, -1); + + lua_pop(state, 5); luaL_newlib(state, library); return 1; } @@ -5,7 +5,6 @@ #include <math.h> #include "util.h" -#include "any_log.h" char *color_to_string(color_t *color) { |
