code wiki / (root) / obs.nx

obs.nx source

↩ module page · 133 lines · 4453 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 23import "syscalls.nx" 24import "rand.nx" 25 26// Severity levels (matches syslog + tracing standards). 27const LOG_TRACE: i64 = 0 28const LOG_DEBUG: i64 = 1 29const LOG_INFO: i64 = 2 30const LOG_WARN: i64 = 3 31const LOG_ERROR: i64 = 4 32const LOG_FATAL: i64 = 5 33 34struct Span { 35 // 128-bit IDs stored as pairs of i64 for simplicity. 36 trace_id_hi: i64, trace_id_lo: i64, 37 span_id: i64, 38 parent_id: i64, 39 start_ns: i64, // caller-supplied clock reading 40 end_ns: i64, // 0 until closed 41 status: i64, // 0 ok, nonzero = error code 42 name_off: *u8, 43 name_len: i64, 44} 45 46func span_new(name: *u8, name_len: i64, 47 trace_hi: i64, trace_lo: i64, 48 parent_id: i64, start_ns: i64) -> *Span { 49 let raw: *u8 = sys_mmap(96) 50 let s: *Span = raw as *Span 51 s.trace_id_hi = trace_hi 52 s.trace_id_lo = trace_lo 53 54 // span_id = 64-bit random. rand_bytes gives crypto entropy. 55 let buf: *u8 = sys_mmap(16) 56 rand_bytes(buf, 8) 57 var sid: i64 = 0 58 var i: i64 = 0 59 while i < 8 { 60 sid = (sid << 8) | buf[i] 61 i = i + 1 62 } 63 s.span_id = sid & 0x7FFFFFFFFFFFFFFF 64 65 s.parent_id = parent_id 66 s.start_ns = start_ns 67 s.end_ns = 0 68 s.status = 0 69 s.name_off = name 70 s.name_len = name_len 71 return s 72} 73 74// Fresh root span -- generates new trace_id + parent=0. 75func span_new_root(name: *u8, name_len: i64, start_ns: i64) -> *Span { 76 let buf: *u8 = sys_mmap(32) 77 rand_bytes(buf, 16) 78 var hi: i64 = 0; var lo: i64 = 0 79 var i: i64 = 0 80 while i < 8 { hi = (hi << 8) | buf[i]; i = i + 1 } 81 while i < 16 { lo = (lo << 8) | buf[i]; i = i + 1 } 82 return span_new(name, name_len, hi & 0x7FFFFFFFFFFFFFFF, 83 lo & 0x7FFFFFFFFFFFFFFF, 0, start_ns) 84} 85 86// Close a span by recording end time + status. 87func span_close(s: *Span, end_ns: i64, status: i64) -> i64 { 88 s.end_ns = end_ns 89 s.status = status 90 return 0 91} 92 93func span_duration_ns(s: *Span) -> i64 { 94 return s.end_ns - s.start_ns 95} 96 97// === structured log records ========================================== 98 99// Write a log record to fd. Phase A uses a compact tab-delim 100// format; Phase B emits proper JSON / Protobuf. 101func log_write(fd: i64, level: i64, 102 msg: *u8, msg_len: i64) -> i64 { 103 // Format: "L<level>\t<msg>\n" 104 let buf: *u8 = sys_mmap(msg_len + 16) 105 buf[0] = 0x4C // 'L' 106 buf[1] = 0x30 + level // digit 107 buf[2] = 0x09 // tab 108 var i: i64 = 0 109 while i < msg_len { buf[3 + i] = msg[i]; i = i + 1 } 110 buf[3 + msg_len] = 0x0A // newline 111 sys_write(fd, buf, msg_len + 4) 112 return 0 113} 114 115// Compile-only smoke. 116func main() -> i64 { 117 let root: *Span = span_new_root("test", 4, 1000000000) 118 if root.parent_id != 0 { return 1 } 119 if root.start_ns != 1000000000 { return 2 } 120 121 let child: *Span = span_new("child", 5, 122 root.trace_id_hi, root.trace_id_lo, 123 root.span_id, 1000100000) 124 if child.parent_id != root.span_id { return 3 } 125 if child.trace_id_hi != root.trace_id_hi { return 4 } 126 127 span_close(child, 1000200000, 0) 128 if span_duration_ns(child) != 100000 { return 5 } 129 130 span_close(root, 1000500000, 0) 131 if span_duration_ns(root) != 500000 { return 6 } 132 return 0 133}