summaryrefslogtreecommitdiff
path: root/src/logging.c
diff options
context:
space:
mode:
authorConnor Thomson <blumatrikz@gmail.com>2026-09-08 18:27:39 -0700
committerConnor Thomson <blumatrikz@gmail.com>2026-09-08 18:27:39 -0700
commitf1ddc12b68b4e4c8087962de25260470e6df2bea (patch)
tree7d0440a6402a9b0ef75ded23fa64b68534255bba /src/logging.c
parent6025860a61386bf767b29c3ec5fd0d31285f1056 (diff)
Add tuxanci2 files
Diffstat (limited to 'src/logging.c')
-rw-r--r--src/logging.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/src/logging.c b/src/logging.c
new file mode 100644
index 0000000..cdaa6bb
--- /dev/null
+++ b/src/logging.c
@@ -0,0 +1,227 @@
+/*
+ * Tuxánci 2 - A first person shooter
+ * Copyright (C) 2007-2010 Tuxánci Development Team
+ * Copyright (C) 2025-2026 Connor Thomson
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ta_sdl.h"
+
+#ifdef UNIX_LIKE
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <elfutils/libdwfl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/time.h>
+
+#define LOG_TIMESTAMP_SIZE 30
+
+static Dwfl *ta_dwfl_open_self(void) {
+ static char *debuginfo_path;
+ static const Dwfl_Callbacks callbacks = {
+ .find_elf = dwfl_linux_proc_find_elf,
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+ };
+
+ Dwfl *dwfl = dwfl_begin(&callbacks);
+ if (dwfl == NULL) {
+ return NULL;
+ }
+
+ if (dwfl_linux_proc_report(dwfl, getpid()) != 0) {
+ dwfl_end(dwfl);
+ return NULL;
+ }
+
+ if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
+ dwfl_end(dwfl);
+ return NULL;
+ }
+
+ return dwfl;
+}
+
+static char *ta_get_proc_name(unw_cursor_t *cursor) {
+ char *buf = NULL;
+ size_t cap = 0;
+ unw_word_t proc_offset = 0;
+
+ while (1) {
+ size_t grow_to = (cap == 0) ? 64 : (cap * 2);
+ char *grown = realloc(buf, grow_to);
+ if (grown == NULL) {
+ free(buf);
+ return NULL;
+ }
+ buf = grown;
+ cap = grow_to;
+
+ int ret = unw_get_proc_name(cursor, buf, cap, &proc_offset);
+ if (ret == 0) {
+ return buf;
+ }
+ if (ret != -UNW_ENOMEM) {
+ free(buf);
+ return NULL;
+ }
+ }
+}
+
+static void ta_get_timestamp(char *buf, size_t buf_size) {
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+
+ struct tm tm_info;
+ localtime_r(&ts.tv_sec, &tm_info);
+
+ size_t len = strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", &tm_info);
+ snprintf(buf + len, buf_size - len, ".%03ld", ts.tv_nsec / 1000000);
+}
+
+static char *ta_format_message(const char *format, va_list args) {
+ va_list args_copy;
+ va_copy(args_copy, args);
+ int needed = vsnprintf(NULL, 0, format, args_copy);
+ va_end(args_copy);
+
+ if (needed < 0) {
+ return NULL;
+ }
+
+ char *message = malloc((size_t)needed + 1);
+ if (message == NULL) {
+ return NULL;
+ }
+
+ vsnprintf(message, (size_t)needed + 1, format, args);
+ return message;
+}
+
+void ta_log(const char *format, ...) {
+ unw_cursor_t cursor;
+ unw_context_t context;
+ int code;
+
+ code = unw_getcontext(&context);
+ if (code != 0) {
+ SDL_Log("unw_getcontext failed with code %d\n", code);
+ return;
+ }
+
+ code = unw_init_local(&cursor, &context);
+ if (code != 0) {
+ SDL_Log("unw_init_local failed with code %d\n", code);
+ return;
+ }
+
+ code = unw_step(&cursor);
+ if (code <= 0) {
+ SDL_Log("unw_step failed with code %d\n", code);
+ return;
+ }
+
+ unw_word_t ip = 0;
+ if (unw_get_reg(&cursor, UNW_REG_IP, &ip) != 0 || ip == 0) {
+ SDL_Log("unw_get_reg failed to resolve caller IP\n");
+ return;
+ }
+
+ const char *file = NULL;
+ int src_line = 0;
+ const char *func_name = NULL;
+ char *proc_name_buf = NULL;
+
+ Dwfl *dwfl = ta_dwfl_open_self();
+ if (dwfl != NULL) {
+ Dwfl_Module *mod = dwfl_addrmodule(dwfl, (Dwarf_Addr)ip);
+ if (mod != NULL) {
+ func_name = dwfl_module_addrname(mod, (GElf_Addr)ip);
+
+ Dwfl_Line *dwfl_line = dwfl_module_getsrc(mod, (Dwarf_Addr)ip);
+ if (dwfl_line != NULL) {
+ Dwarf_Addr src_addr;
+ file = dwfl_lineinfo(dwfl_line, &src_addr, &src_line, NULL, NULL, NULL);
+ }
+ }
+ }
+
+ if (func_name == NULL) {
+ proc_name_buf = ta_get_proc_name(&cursor);
+ func_name = proc_name_buf;
+ }
+
+ va_list args;
+ va_start(args, format);
+ char *message = ta_format_message(format, args);
+ va_end(args);
+
+ const char *out_message = (message != NULL) ? message : format;
+
+ /*
+ * PATCH:
+ * Remove leading '/'
+ * I cannot find a way to make it not do it with the compiler.
+ * This uses extra cpu cycles and makes the binary larger then it has to be.
+ * Make a script to trim it from the DWARF info.
+ */
+ if (file != NULL) {
+ const char *basename = strrchr(file, '/');
+ if (basename != NULL) {
+ file = basename + 1;
+ }
+ }
+
+ char timestamp[LOG_TIMESTAMP_SIZE];
+ ta_get_timestamp(timestamp, sizeof(timestamp));
+
+ if (file != NULL && src_line > 0 && func_name != NULL) {
+ printf("[%s] %s:%d:%s(): %s\n", timestamp, file, src_line, func_name, out_message);
+ } else if (func_name != NULL) {
+ printf("[%s] %s(): %s\n", timestamp, func_name, out_message);
+ } else {
+ printf("[%s] 0x%lx: %s\n", timestamp, (unsigned long)ip, out_message);
+ }
+
+ free(message);
+ free(proc_name_buf);
+
+ if (dwfl != NULL) {
+ dwfl_end(dwfl);
+ }
+}
+
+#else // UNIX_LIKE
+
+void ta_log(const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ SDL_LogMessageV(
+ SDL_LOG_CATEGORY_APPLICATION,
+ SDL_LOG_PRIORITY_INFO,
+ fmt,
+ args
+ );
+
+ va_end(args);
+}
+
+#endif // UNIX_LIKE \ No newline at end of file