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/lua/log.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 205 insertions(+), 50 deletions(-) (limited to 'src/lua/log.c') 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 #include -#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; } -- cgit v1.2.3