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}