code wiki / (root) / nx_log_v1.nx

nx_log_v1.nx source

↩ module page · 148 lines · 4810 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 27// nx_safety_envelope: 28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 29// sil_target: SIL1 30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34import "nx_timefmt.nx" 35const LOG_MAGIC_1024: i64 = 1024 36 37const LOG_TRACE: i64 = 0 38const LOG_DEBUG: i64 = 1 39const LOG_INFO: i64 = 2 40const LOG_WARN: i64 = 3 41const LOG_ERROR: i64 = 4 42 43// Threshold storage in a heap slot so set_threshold persists. 44func log_threshold_slot() -> *i64 { 45 let raw: *u8 = sys_mmap(16) 46 let p: *i64 = raw as *i64 47 *p = LOG_INFO // default: drop TRACE / DEBUG 48 return p 49} 50 51// Module-level state placeholder. Each call to log_set_threshold 52// allocates fresh storage; the LAST call's slot wins because 53// log_threshold uses the caller's slot pointer. In practice 54// this is fine for single-process apps where the threshold gets 55// set once at startup. 56 57// Level tag for prefix. 58func log_level_tag(level: i64) -> *u8 { 59 if level == LOG_TRACE { return "TRACE" } 60 if level == LOG_DEBUG { return "DEBUG" } 61 if level == LOG_INFO { return "INFO" } 62 if level == LOG_WARN { return "WARN" } 63 if level == LOG_ERROR { return "ERROR" } 64 return "UNK" 65} 66 67// Append a null-terminated cstring; bounds-check via cap. 68func log_append(buf: *u8, pos: *i64, cap: i64, s: *u8) -> i64 { 69 let p0: i64 = *pos 70 var i: i64 = 0 71 while s[i] != 0 { 72 if p0 + i >= cap { return -1 } 73 buf[p0 + i] = s[i] 74 i = i + 1 75 } 76 *pos = p0 + i 77 return 0 78} 79 80// Append n bytes of raw data. 81func log_append_bytes(buf: *u8, pos: *i64, cap: i64, src: *u8, n: i64) -> i64 { 82 let p0: i64 = *pos 83 if p0 + n > cap { return -1 } 84 var i: i64 = 0 85 while i < n { buf[p0 + i] = src[i]; i = i + 1 } 86 *pos = p0 + n 87 return 0 88} 89 90// Write a log line to stderr. Level filter applied; below- 91// threshold lines no-op. `unix_time` is caller-supplied epoch 92// seconds (or pass 0 to skip the timestamp). 93// 94// Module name appears between timestamp and message; helps grep 95// by subsystem. 96func log_write(threshold: *i64, level: i64, 97 unix_time: i64, 98 module: *u8, msg: *u8, msg_len: i64) -> i64 { 99 if level < *threshold { return 0 } 100 let buf: *u8 = sys_mmap(LOG_MAGIC_1024) 101 let pos_raw: *u8 = sys_mmap(16) 102 let pos: *i64 = pos_raw as *i64 103 *pos = 0 104 105 log_append(buf, pos, LOG_MAGIC_1024, "[") 106 log_append(buf, pos, LOG_MAGIC_1024, log_level_tag(level)) 107 log_append(buf, pos, LOG_MAGIC_1024, "] ") 108 109 if unix_time > 0 { 110 let ts: *u8 = sys_mmap(32) 111 format_rfc3339(unix_time, ts) 112 log_append_bytes(buf, pos, LOG_MAGIC_1024, ts, 20) 113 log_append(buf, pos, LOG_MAGIC_1024, " ") 114 } 115 116 log_append(buf, pos, LOG_MAGIC_1024, module) 117 log_append(buf, pos, LOG_MAGIC_1024, ": ") 118 log_append_bytes(buf, pos, LOG_MAGIC_1024, msg, msg_len) 119 120 // Ensure trailing LF. 121 if *pos < LOG_MAGIC_1024 { 122 if buf[*pos - 1] != 0x0A { 123 buf[*pos] = 0x0A 124 *pos = *pos + 1 125 } 126 } 127 128 sys_write(2, buf, *pos) 129 return 0 130} 131 132// Convenience: unwrap a cstring message length. 133func log_msg(threshold: *i64, level: i64, unix_time: i64, 134 module: *u8, msg: *u8) -> i64 { 135 var n: i64 = 0 136 while msg[n] != 0 { n = n + 1 } 137 return log_write(threshold, level, unix_time, module, msg, n) 138} 139 140// Compile-only smoke. Skips real stderr writes by passing a 141// threshold that drops all levels. 142func main() -> i64 { 143 let raw: *u8 = sys_mmap(16) 144 let thr: *i64 = raw as *i64 145 *thr = LOG_ERROR + 1 // drop everything 146 log_msg(thr, LOG_INFO, 0, "test", "hello") 147 return 0 148}