diff options
Diffstat (limited to 'src/log.c')
| -rw-r--r-- | src/log.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..7dec534 --- /dev/null +++ b/src/log.c @@ -0,0 +1,345 @@ +#include "ecex.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +static int ecex_env_enabled(const char *name) { + const char *v; + if (!name) return 0; + v = getenv(name); + return v && v[0] && v[0] != '0'; +} + +static int ecex_log_enabled(void) { + return ecex_env_enabled("ECEX_LOG") || + ecex_env_enabled("ECEX_TRACE_EVAL") || + ecex_env_enabled("ECEX_TRACE_CALLBACKS") || + ecex_env_enabled("ECEX_TRACE_TETRIS"); +} + +static char ecex_last_log_line[1024]; +static unsigned long ecex_last_log_count = 0; +static int ecex_last_log_depth = 0; +static int ecex_log_group_depth = 0; +static int ecex_frame_group_open = 0; +static int ecex_frame_group_depth = 0; +static char ecex_frame_group_start[1024]; +static char ecex_frame_repeat_start[1024]; +static char ecex_frame_repeat_end[1024]; +static unsigned long ecex_frame_repeat_count = 0; + +static size_t ecex_strn_copy(char *out, size_t out_cap, const char *in) { + size_t i = 0; + if (!out || out_cap == 0) return 0; + if (!in) in = "(null)"; + while (i + 1 < out_cap && in[i]) { + out[i] = in[i]; + ++i; + } + out[i] = '\0'; + return i; +} + +static int ecex_streq(const char *a, const char *b) { + size_t i = 0; + if (!a || !b) return 0; + while (a[i] && b[i]) { + if (a[i] != b[i]) return 0; + ++i; + } + return a[i] == b[i]; +} + +static int ecex_starts_with(const char *text, const char *prefix) { + size_t i = 0; + if (!text || !prefix) return 0; + while (prefix[i]) { + if (text[i] != prefix[i]) return 0; + ++i; + } + return 1; +} + +static size_t ecex_append_char(char *out, size_t len, size_t cap, char c) { + if (len + 1 < cap) { + out[len++] = c; + out[len] = '\0'; + } + return len; +} + +static size_t ecex_append_cstr(char *out, size_t len, size_t cap, const char *text) { + size_t i = 0; + if (!text) text = "(null)"; + while (text[i] && len + 1 < cap) { + out[len++] = text[i++]; + } + if (cap) out[len < cap ? len : cap - 1] = '\0'; + return len; +} + +static size_t ecex_append_ulong(char *out, size_t len, size_t cap, unsigned long value) { + char tmp[32]; + size_t n = 0; + if (value == 0) return ecex_append_char(out, len, cap, '0'); + while (value && n < sizeof(tmp)) { + tmp[n++] = (char)('0' + (value % 10)); + value /= 10; + } + while (n > 0) len = ecex_append_char(out, len, cap, tmp[--n]); + return len; +} + +static size_t ecex_append_int(char *out, size_t len, size_t cap, int value) { + unsigned int mag; + if (value < 0) { + len = ecex_append_char(out, len, cap, '-'); + mag = (unsigned int)(-(value + 1)) + 1u; + } else { + mag = (unsigned int)value; + } + return ecex_append_ulong(out, len, cap, (unsigned long)mag); +} + +static size_t ecex_append_ptr(char *out, size_t len, size_t cap, const void *ptr) { + uintptr_t value = (uintptr_t)ptr; + char tmp[sizeof(uintptr_t) * 2]; + size_t n = 0; + len = ecex_append_cstr(out, len, cap, "0x"); + if (value == 0) return ecex_append_char(out, len, cap, '0'); + while (value && n < sizeof(tmp)) { + int digit = (int)(value & 0xfu); + tmp[n++] = (char)(digit < 10 ? '0' + digit : 'a' + digit - 10); + value >>= 4; + } + while (n > 0) len = ecex_append_char(out, len, cap, tmp[--n]); + return len; +} + +static size_t ecex_append_double(char *out, size_t len, size_t cap, double value) { + long whole; + unsigned long frac; + int i; + if (value < 0.0) { + len = ecex_append_char(out, len, cap, '-'); + value = -value; + } + whole = (long)value; + frac = (unsigned long)((value - (double)whole) * 1000000.0 + 0.5); + len = ecex_append_ulong(out, len, cap, (unsigned long)whole); + len = ecex_append_char(out, len, cap, '.'); + for (i = 100000; i > 0; i /= 10) { + len = ecex_append_char(out, len, cap, (char)('0' + (frac / (unsigned long)i) % 10)); + } + return len; +} + +static void ecex_write_all(const char *text, size_t len) { + while (len > 0) { + ssize_t written = write(STDERR_FILENO, text, len); + if (written <= 0) return; + text += written; + len -= (size_t)written; + } +} + +static void ecex_log_emit_raw_depth(const char *line, int depth) { + size_t len = 0; + int i; + while (line && line[len]) ++len; + ecex_write_all("ecex-log: ", 10); + for (i = 0; i < depth; i++) { + ecex_write_all(" ", 2); + } + ecex_write_all(line ? line : "(null)", line ? len : 6); + ecex_write_all("\n", 1); +} + +static void ecex_log_emit_raw_counted(const char *line, int depth, unsigned long count) { + char counted[1200]; + if (count > 1) { + snprintf(counted, sizeof(counted), "%s [%lu]", line ? line : "(null)", count); + ecex_log_emit_raw_depth(counted, depth); + } else { + ecex_log_emit_raw_depth(line, depth); + } +} + +static void ecex_log_flush_frame_repeats(void) { + if (ecex_frame_repeat_count == 0) return; + ecex_log_emit_raw_counted(ecex_frame_repeat_start, 0, ecex_frame_repeat_count); + ecex_log_emit_raw_counted(ecex_frame_repeat_end, 0, ecex_frame_repeat_count); + ecex_frame_repeat_count = 0; + ecex_frame_repeat_start[0] = '\0'; + ecex_frame_repeat_end[0] = '\0'; +} + +static void ecex_log_flush_open_frame_group(void) { + if (!ecex_frame_group_open) return; + ecex_log_emit_raw_depth(ecex_frame_group_start, ecex_frame_group_depth); + ecex_frame_group_open = 0; + ecex_frame_group_start[0] = '\0'; + ecex_frame_group_depth = 0; +} + +static int ecex_log_is_frame_start(const char *line) { + return ecex_starts_with(line, "frame start "); +} + +static int ecex_log_is_frame_end(const char *line) { + return ecex_streq(line, "frame end"); +} + +static void ecex_log_flush_repeat(void) { + char line[64]; + size_t len = 0; + if (ecex_last_log_count > 1) { + len = ecex_append_cstr(line, len, sizeof(line), "... ["); + len = ecex_append_ulong(line, len, sizeof(line), ecex_last_log_count); + len = ecex_append_char(line, len, sizeof(line), ']'); + ecex_log_emit_raw_depth(line, ecex_last_log_depth); + } + ecex_last_log_count = 0; + ecex_last_log_line[0] = '\0'; + ecex_last_log_depth = 0; +} + +void ecex_log_flush(void) { + ecex_log_flush_open_frame_group(); + ecex_log_flush_frame_repeats(); + ecex_log_flush_repeat(); +} + +static void ecex_log_write_line_depth(const char *line, int depth) { + char safe_line[1024]; + if (depth < 0) depth = 0; + ecex_strn_copy(safe_line, sizeof(safe_line), line); + + ecex_log_flush_open_frame_group(); + ecex_log_flush_frame_repeats(); + + if (ecex_last_log_count > 0 && + ecex_last_log_depth == depth && + ecex_streq(ecex_last_log_line, safe_line)) { + ecex_last_log_count++; + return; + } + + ecex_log_flush_repeat(); + ecex_strn_copy(ecex_last_log_line, sizeof(ecex_last_log_line), safe_line); + ecex_last_log_depth = depth; + ecex_last_log_count = 1; + ecex_log_emit_raw_depth(safe_line, depth); +} + +static void ecex_log_write_line(const char *line) { + ecex_log_write_line_depth(line, ecex_log_group_depth); +} + +void ecex_log(const char *message) { + if (!ecex_log_enabled()) return; + ecex_log_write_line(message ? message : "(null)"); +} + +void ecex_logf(const char *fmt, ...) { + char line[1024]; + va_list ap; + if (!ecex_log_enabled()) return; + if (!fmt) { + ecex_log_write_line("(null)"); + return; + } + va_start(ap, fmt); + vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + ecex_log_write_line(line); +} + +void ecex_log_group_begin(const char *message) { + if (!ecex_log_enabled()) return; + if (ecex_log_group_depth == 0 && ecex_log_is_frame_start(message)) { + ecex_log_flush_repeat(); + if (ecex_frame_repeat_count > 0 && + !ecex_streq(ecex_frame_repeat_start, message ? message : "group start")) { + ecex_log_flush_frame_repeats(); + } + ecex_strn_copy(ecex_frame_group_start, + sizeof(ecex_frame_group_start), + message ? message : "group start"); + ecex_frame_group_depth = ecex_log_group_depth; + ecex_frame_group_open = 1; + ecex_log_group_depth++; + return; + } + + ecex_log_flush_open_frame_group(); + ecex_log_flush_frame_repeats(); + ecex_log_flush_repeat(); + ecex_log_write_line_depth(message ? message : "group start", ecex_log_group_depth); + ecex_log_group_depth++; +} + +void ecex_log_group_end(const char *message) { + if (!ecex_log_enabled()) return; + ecex_log_flush_repeat(); + if (ecex_log_group_depth > 0) ecex_log_group_depth--; + + if (ecex_frame_group_open && + ecex_log_group_depth == ecex_frame_group_depth && + ecex_log_is_frame_end(message)) { + const char *end = message ? message : "group end"; + if (ecex_frame_repeat_count > 0 && + ecex_streq(ecex_frame_repeat_start, ecex_frame_group_start) && + ecex_streq(ecex_frame_repeat_end, end)) { + ecex_frame_repeat_count++; + } else { + ecex_log_flush_frame_repeats(); + ecex_strn_copy(ecex_frame_repeat_start, + sizeof(ecex_frame_repeat_start), + ecex_frame_group_start); + ecex_strn_copy(ecex_frame_repeat_end, + sizeof(ecex_frame_repeat_end), + end); + ecex_frame_repeat_count = 1; + } + ecex_frame_group_open = 0; + ecex_frame_group_start[0] = '\0'; + ecex_frame_group_depth = 0; + return; + } + + ecex_log_flush_open_frame_group(); + ecex_log_flush_frame_repeats(); + ecex_log_write_line_depth(message ? message : "group end", ecex_log_group_depth); +} + +void ecex_log_int(const char *message, int value) { + char line[1024]; + size_t len = 0; + if (!ecex_log_enabled()) return; + len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); + ecex_append_int(line, len, sizeof(line), value); + ecex_log_write_line(line); +} + +void ecex_log_double(const char *message, double value) { + char line[1024]; + size_t len = 0; + if (!ecex_log_enabled()) return; + len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); + ecex_append_double(line, len, sizeof(line), value); + ecex_log_write_line(line); +} + +void ecex_log_ptr(const char *message, const void *ptr) { + char line[1024]; + size_t len = 0; + if (!ecex_log_enabled()) return; + len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); + ecex_append_ptr(line, len, sizeof(line), ptr); + ecex_log_write_line(line); +} |
