aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Angelilli <code@fedang.net>2025-04-13 15:07:20 +0200
committerFederico Angelilli <code@fedang.net>2025-04-13 15:07:20 +0200
commit9c9139d0eb4244c66123fdbfd211105ede8c4b42 (patch)
tree89455d52236362c3cf953392b1cdc96aa5194fb0
parent0aed18911e27b80a4dbe1a252f9485382a4073c7 (diff)
Add structured logging
-rw-r--r--example.lua30
-rw-r--r--src/any_log.h298
-rw-r--r--src/comet.c13
-rw-r--r--src/lua/log.c255
-rw-r--r--src/util.c1
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;
}
diff --git a/src/util.c b/src/util.c
index 718e6b5..c258b38 100644
--- a/src/util.c
+++ b/src/util.c
@@ -5,7 +5,6 @@
#include <math.h>
#include "util.h"
-#include "any_log.h"
char *color_to_string(color_t *color)
{