#include #include #include "log.h" static lua_CFunction lua_log_getinfo = NULL; static lua_CFunction lua_log_stringformat = NULL; static lua_CFunction lua_log_tostring = NULL; static void lua_log_debuginfo(lua_State *state, const char **module, const char **func, char buffer[], size_t size) { lua_pushcfunction(state, lua_log_getinfo); // Ignore the first frame (the c function itself) lua_pushnumber(state, 2); // S: source, l: currentline, n: name const char *query = #ifdef _RELEASE "Sl"; #else "Sln"; #endif lua_pushstring(state, query); if (lua_pcall(state, 2, 1, 0) == LUA_OK) { lua_getfield(state, -1, "short_src"); const char *shortsrc = lua_tostring(state, -1); lua_getfield(state, -2, "currentline"); int line = lua_tointeger(state, -1); #ifndef _RELEASE lua_getfield(state, -3, "name"); const char *name = lua_tostring(state, -1); if (name) *func = name; #endif if (!strncmp(shortsrc, "[string ", 8)) { int length = strlen(shortsrc); snprintf(buffer, size, "%.*s", length - 9, shortsrc + 8); } else { snprintf(buffer, size, "%s:%d", shortsrc, line); } *module = buffer; } 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 *module = "?", *func = ""; char buffer[4096]; lua_log_debuginfo(state, &module, &func, buffer, sizeof(buffer)); any_log_format(level, "lua", module, func, "%s", message); return 0; } 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_error(lua_State *state) { return lua_log_wrapper(state, 0, ANY_LOG_ERROR); } static int lua_log_warn(lua_State *state) { return lua_log_wrapper(state, 0, ANY_LOG_WARN); } static int lua_log_info(lua_State *state) { return lua_log_wrapper(state, 0, ANY_LOG_INFO); } static int lua_log_debug(lua_State *state) { return lua_log_wrapper(state, 0, ANY_LOG_DEBUG); } static int lua_log_trace(lua_State *state) { return lua_log_wrapper(state, 0, 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 *module = "?", *func = ""; char buffer[4096]; lua_log_debuginfo(state, &module, &func, buffer, sizeof(buffer)); any_log_value(level, "lua", module, 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[] = { // 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_getglobal(state, "tostring"); lua_log_tostring = lua_tocfunction(state, -1); lua_pop(state, 5); luaL_newlib(state, library); return 1; }