code wiki / (root) / nx_obs.nx

nx_obs.nx source

↩ module page · 145 lines · 4901 B

1// obs.nx -- observability primitives (spans, structured logs). 2// 3// EFFICIENCY_ROADMAP ยง5.2. OpenTelemetry-style distributed 4// tracing + structured logging. Today's observability is 5// ad-hoc sys_write; this module lands a proper API with 6// trace IDs, parent-child span relationships, + KV attributes. 7// 8// Phase A: runtime spans + log records serialised to stdout as 9// JSON (or future Protobuf via protobuf_wire.nx). Phase B: 10// `@trace` function attribute auto-instruments every call. 11// 12// Designed to match OpenTelemetry data model so future bridges 13// to Jaeger / Honeycomb / Datadog are clean byte-copies. 14// 15// Invariants: 16// OB1 Span IDs are 128-bit; collision probability at one 17// million spans/sec for a year < 1 in 10^30. 18// OB2 Parent-child relationships form a tree per trace_id 19// (enforced by caller; runtime doesn't verify). 20// OB3 Log records are structured: all fields get key+value; 21// no printf-style interpolation (bad for aggregation). 22 23// nx_safety_envelope: 24// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 25// sil_target: SIL1 26// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 27// verdict: NOT_YET_EVALUATED 28 29import "nx_syscalls.nx" 30import "nx_rand.nx" 31const LOG_MAGIC_1000000000: i64 = 1000000000 32const LOG_MAGIC_1000100000: i64 = 1000100000 33const LOG_MAGIC_1000200000: i64 = 1000200000 34const LOG_MAGIC_100000: i64 = 100000 35const LOG_MAGIC_1000500000: i64 = 1000500000 36const LOG_MAGIC_500000: i64 = 500000 37 38// Severity levels (matches syslog + tracing standards). 39const LOG_TRACE: i64 = 0 40const LOG_DEBUG: i64 = 1 41const LOG_INFO: i64 = 2 42const LOG_WARN: i64 = 3 43const LOG_ERROR: i64 = 4 44const LOG_FATAL: i64 = 5 45 46struct Span { 47 // 128-bit IDs stored as pairs of i64 for simplicity. 48 trace_id_hi: i64, trace_id_lo: i64, 49 span_id: i64, 50 parent_id: i64, 51 start_ns: i64, // caller-supplied clock reading 52 end_ns: i64, // 0 until closed 53 status: i64, // 0 ok, nonzero = error code 54 name_off: *u8, 55 name_len: i64, 56} 57 58func span_new(name: *u8, name_len: i64, 59 trace_hi: i64, trace_lo: i64, 60 parent_id: i64, start_ns: i64) -> *Span { 61 let raw: *u8 = sys_mmap(96) 62 let s: *Span = raw as *Span 63 s.trace_id_hi = trace_hi 64 s.trace_id_lo = trace_lo 65 66 // span_id = 64-bit random. rand_bytes gives crypto entropy. 67 let buf: *u8 = sys_mmap(16) 68 rand_bytes(buf, 8) 69 var sid: i64 = 0 70 var i: i64 = 0 71 while i < 8 { 72 sid = (sid << 8) | buf[i] 73 i = i + 1 74 } 75 s.span_id = sid & 0x7FFFFFFFFFFFFFFF 76 77 s.parent_id = parent_id 78 s.start_ns = start_ns 79 s.end_ns = 0 80 s.status = 0 81 s.name_off = name 82 s.name_len = name_len 83 return s 84} 85 86// Fresh root span -- generates new trace_id + parent=0. 87func span_new_root(name: *u8, name_len: i64, start_ns: i64) -> *Span { 88 let buf: *u8 = sys_mmap(32) 89 rand_bytes(buf, 16) 90 var hi: i64 = 0; var lo: i64 = 0 91 var i: i64 = 0 92 while i < 8 { hi = (hi << 8) | buf[i]; i = i + 1 } 93 while i < 16 { lo = (lo << 8) | buf[i]; i = i + 1 } 94 return span_new(name, name_len, hi & 0x7FFFFFFFFFFFFFFF, 95 lo & 0x7FFFFFFFFFFFFFFF, 0, start_ns) 96} 97 98// Close a span by recording end time + status. 99func span_close(s: *Span, end_ns: i64, status: i64) -> i64 { 100 s.end_ns = end_ns 101 s.status = status 102 return 0 103} 104 105func span_duration_ns(s: *Span) -> i64 { 106 return s.end_ns - s.start_ns 107} 108 109// === structured log records ========================================== 110 111// Write a log record to fd. Phase A uses a compact tab-delim 112// format; Phase B emits proper JSON / Protobuf. 113func log_write(fd: i64, level: i64, 114 msg: *u8, msg_len: i64) -> i64 { 115 // Format: "L<level>\t<msg>\n" 116 let buf: *u8 = sys_mmap(msg_len + 16) 117 buf[0] = 0x4C // 'L' 118 buf[1] = 0x30 + level // digit 119 buf[2] = 0x09 // tab 120 var i: i64 = 0 121 while i < msg_len { buf[3 + i] = msg[i]; i = i + 1 } 122 buf[3 + msg_len] = 0x0A // newline 123 sys_write(fd, buf, msg_len + 4) 124 return 0 125} 126 127// Compile-only smoke. 128func main() -> i64 { 129 let root: *Span = span_new_root("test", 4, LOG_MAGIC_1000000000) 130 if root.parent_id != 0 { return 1 } 131 if root.start_ns != LOG_MAGIC_1000000000 { return 2 } 132 133 let child: *Span = span_new("child", 5, 134 root.trace_id_hi, root.trace_id_lo, 135 root.span_id, LOG_MAGIC_1000100000) 136 if child.parent_id != root.span_id { return 3 } 137 if child.trace_id_hi != root.trace_id_hi { return 4 } 138 139 span_close(child, LOG_MAGIC_1000200000, 0) 140 if span_duration_ns(child) != LOG_MAGIC_100000 { return 5 } 141 142 span_close(root, LOG_MAGIC_1000500000, 0) 143 if span_duration_ns(root) != LOG_MAGIC_500000 { return 6 } 144 return 0 145}