code wiki / (root) / log.nx

log.nx source

↩ module page · 141 lines · 4561 B

1// log.nx -- structured logging with levels. 2// 3// Five-level hierarchy (TRACE < DEBUG < INFO < WARN < ERROR). 4// Output goes to stderr (fd 2) so stdout stays clean for pipe/ 5// redirect workflows. Each line is prefixed with a level tag 6// + RFC 3339 timestamp + caller-supplied module name. 7// 8// Format: 9// [LEVEL] <timestamp> <module>: <message> 10// e.g. [INFO] 2026-04-21T10:30:42Z http: serving / (200) 11// 12// Used by: any service that needs operational visibility without 13// pulling in a real logging framework. Pairs with timefmt.nx 14// for the timestamp. 15// 16// Invariants: 17// L1 Output is line-oriented (one line per call); LF appended 18// even if caller's message lacks one. 19// L2 Below-threshold calls are CHEAP -- short-circuit early 20// before any formatting work. 21// L3 Multi-thread safe at the SYSCALL level (sys_write is 22// atomic up to PIPE_BUF on POSIX); concurrent log calls 23// never interleave at the byte level for short lines. 24// L4 No allocations on the hot path; uses a single sys_mmap 25// buffer reused across calls. 26 27import "syscalls.nx" 28import "timefmt.nx" 29 30const LOG_TRACE: i64 = 0 31const LOG_DEBUG: i64 = 1 32const LOG_INFO: i64 = 2 33const LOG_WARN: i64 = 3 34const LOG_ERROR: i64 = 4 35 36// Threshold storage in a heap slot so set_threshold persists. 37func log_threshold_slot() -> *i64 { 38 let raw: *u8 = sys_mmap(16) 39 let p: *i64 = raw as *i64 40 *p = LOG_INFO // default: drop TRACE / DEBUG 41 return p 42} 43 44// Module-level state placeholder. Each call to log_set_threshold 45// allocates fresh storage; the LAST call's slot wins because 46// log_threshold uses the caller's slot pointer. In practice 47// this is fine for single-process apps where the threshold gets 48// set once at startup. 49 50// Level tag for prefix. 51func log_level_tag(level: i64) -> *u8 { 52 if level == LOG_TRACE { return "TRACE" } 53 if level == LOG_DEBUG { return "DEBUG" } 54 if level == LOG_INFO { return "INFO" } 55 if level == LOG_WARN { return "WARN" } 56 if level == LOG_ERROR { return "ERROR" } 57 return "UNK" 58} 59 60// Append a null-terminated cstring; bounds-check via cap. 61func log_append(buf: *u8, pos: *i64, cap: i64, s: *u8) -> i64 { 62 let p0: i64 = *pos 63 var i: i64 = 0 64 while s[i] != 0 { 65 if p0 + i >= cap { return -1 } 66 buf[p0 + i] = s[i] 67 i = i + 1 68 } 69 *pos = p0 + i 70 return 0 71} 72 73// Append n bytes of raw data. 74func log_append_bytes(buf: *u8, pos: *i64, cap: i64, src: *u8, n: i64) -> i64 { 75 let p0: i64 = *pos 76 if p0 + n > cap { return -1 } 77 var i: i64 = 0 78 while i < n { buf[p0 + i] = src[i]; i = i + 1 } 79 *pos = p0 + n 80 return 0 81} 82 83// Write a log line to stderr. Level filter applied; below- 84// threshold lines no-op. `unix_time` is caller-supplied epoch 85// seconds (or pass 0 to skip the timestamp). 86// 87// Module name appears between timestamp and message; helps grep 88// by subsystem. 89func log_write(threshold: *i64, level: i64, 90 unix_time: i64, 91 module: *u8, msg: *u8, msg_len: i64) -> i64 { 92 if level < *threshold { return 0 } 93 let buf: *u8 = sys_mmap(1024) 94 let pos_raw: *u8 = sys_mmap(16) 95 let pos: *i64 = pos_raw as *i64 96 *pos = 0 97 98 log_append(buf, pos, 1024, "[") 99 log_append(buf, pos, 1024, log_level_tag(level)) 100 log_append(buf, pos, 1024, "] ") 101 102 if unix_time > 0 { 103 let ts: *u8 = sys_mmap(32) 104 format_rfc3339(unix_time, ts) 105 log_append_bytes(buf, pos, 1024, ts, 20) 106 log_append(buf, pos, 1024, " ") 107 } 108 109 log_append(buf, pos, 1024, module) 110 log_append(buf, pos, 1024, ": ") 111 log_append_bytes(buf, pos, 1024, msg, msg_len) 112 113 // Ensure trailing LF. 114 if *pos < 1024 { 115 if buf[*pos - 1] != 0x0A { 116 buf[*pos] = 0x0A 117 *pos = *pos + 1 118 } 119 } 120 121 sys_write(2, buf, *pos) 122 return 0 123} 124 125// Convenience: unwrap a cstring message length. 126func log_msg(threshold: *i64, level: i64, unix_time: i64, 127 module: *u8, msg: *u8) -> i64 { 128 var n: i64 = 0 129 while msg[n] != 0 { n = n + 1 } 130 return log_write(threshold, level, unix_time, module, msg, n) 131} 132 133// Compile-only smoke. Skips real stderr writes by passing a 134// threshold that drops all levels. 135func main() -> i64 { 136 let raw: *u8 = sys_mmap(16) 137 let thr: *i64 = raw as *i64 138 *thr = LOG_ERROR + 1 // drop everything 139 log_msg(thr, LOG_INFO, 0, "test", "hello") 140 return 0 141}