code wiki / (root) / nx_log_jsonl.nx

nx_log_jsonl.nx source

↩ module page · 361 lines · 13710 B

1// nx_log_jsonl.nx -- structured JSONL log line emitter. 2// 3// One line per log entry, JSON-encoded. Consumable by any log 4// aggregator: Loki / Splunk / Elasticsearch / Datadog / `jq`. 5// Standard S-class hosting log shape. 6// 7// Per cardinal feedback-no-third-party-trust-native-or-nothing: 8// substrate's own log emitter; no logrus, no log4j, no syslog 9// SDK. Substrate produces JSON; any consumer reads. 10// 11// Per cardinal feedback-defensive-at-boundaries-trusting-internally: 12// log values are escaped for JSON safety (string escape per RFC 8259 13// §7) so log-injection attacks via newlines or quotes in user input 14// are structurally prevented. 15// 16// One log line shape: 17// 18// {"ts_ms":12345,"level":"INFO","module":"nx_audit","msg":"hello"}\n 19// 20// Caller can add k=v fields via nx_log_field_str / _int after 21// opening the entry + before closing. Fields are encoded inline: 22// 23// {"ts_ms":1,"level":"WARN","module":"x","msg":"y","retry":3}\n 24// 25// nx_capability_claims: 26// needs: [sealed_enum, bounded_buffer, json_string_escape] 27// provides: [structured_log_emit, log_injection_prevention] 28// safety: [no_unchecked_deref, no_floating_point, no_syscall, 29// bounded_iteration, json_value_escape_at_boundary] 30// verdict: [sealed_enum_4_state_severity + sealed_enum_4_verdict] 31// license: ORIGINAL 32// kind: racing_crew_specialist 33// layer: L3 (algorithm: structured emit) 34 35// ---- Sealed enum: log severity ----------------------------------- 36 37const NXL_DEBUG: i64 = 0 38const NXL_INFO: i64 = 1 39const NXL_WARN: i64 = 2 40const NXL_ERROR: i64 = 3 41const NXL_LEVEL_N: i64 = 4 42 43func nxl_level_is_valid(L: i64) -> i64 { 44 if L < 0 { return 0 } 45 if L >= NXL_LEVEL_N { return 0 } 46 return 1 47} 48 49func nxl_level_name(L: i64) -> *u8 { 50 if L == NXL_DEBUG { return "DEBUG" as *u8 } 51 if L == NXL_INFO { return "INFO" as *u8 } 52 if L == NXL_WARN { return "WARN" as *u8 } 53 if L == NXL_ERROR { return "ERROR" as *u8 } 54 return "INVALID" as *u8 55} 56 57func nxl_level_name_len(L: i64) -> i64 { 58 if L == NXL_DEBUG { return 5 } 59 if L == NXL_INFO { return 4 } 60 if L == NXL_WARN { return 4 } 61 if L == NXL_ERROR { return 5 } 62 return 7 63} 64 65// ---- Sealed enum: emit verdict ----------------------------------- 66 67const NXL_OK: i64 = 0 68const NXL_OOM_BUFFER: i64 = 1 69const NXL_BAD_LEVEL: i64 = 2 70const NXL_BAD_ARG: i64 = 3 71const NXL_VERDICT_N: i64 = 4 72 73func nxl_verdict_is_valid(v: i64) -> i64 { 74 if v < 0 { return 0 } 75 if v >= NXL_VERDICT_N { return 0 } 76 return 1 77} 78 79func nxl_verdict_name(v: i64) -> *u8 { 80 if v == NXL_OK { return "OK" as *u8 } 81 if v == NXL_OOM_BUFFER { return "OOM_BUFFER" as *u8 } 82 if v == NXL_BAD_LEVEL { return "BAD_LEVEL" as *u8 } 83 if v == NXL_BAD_ARG { return "BAD_ARG" as *u8 } 84 return "INVALID" as *u8 85} 86 87// ---- Byte emit helpers ------------------------------------------- 88 89func nxl_put(out: *u8, off: *i64, cap: i64, b: i64) -> i64 { 90 if *off >= cap { return NXL_OOM_BUFFER } 91 out[*off] = b as u8 92 *off = *off + 1 93 return NXL_OK 94} 95 96func nxl_put_cstr(out: *u8, off: *i64, cap: i64, s: *u8) -> i64 { 97 var i: i64 = 0 98 while s[i] != 0 { 99 let rc: i64 = nxl_put(out, off, cap, s[i] as i64) 100 if rc != NXL_OK { return rc } 101 i = i + 1 102 } 103 return NXL_OK 104} 105 106func nxl_put_bytes(out: *u8, off: *i64, cap: i64, src: *u8, n: i64) -> i64 { 107 var i: i64 = 0 108 while i < n { 109 let rc: i64 = nxl_put(out, off, cap, src[i] as i64) 110 if rc != NXL_OK { return rc } 111 i = i + 1 112 } 113 return NXL_OK 114} 115 116// ---- JSON string escape (RFC 8259 §7) ---------------------------- 117// 118// Required escapes: 119// " -> \" 120// \ -> \\ 121// / -> / (optional but tolerated; spec allows both) 122// <0x20 control chars -> \uXXXX (we use the short forms where 123// defined: \b \t \n \r \f) 124// 125// We emit \uXXXX for any other 0x00..0x1f byte. High-bit bytes 126// pass through verbatim (assumed UTF-8; substrate doesn't decode). 127 128func nxl_put_json_str_bytes(out: *u8, off: *i64, cap: i64, 129 src: *u8, n: i64) -> i64 { 130 var i: i64 = 0 131 while i < n { 132 let b: i64 = src[i] as i64 133 var rc: i64 = NXL_OK 134 if b == 0x22 { 135 rc = nxl_put_cstr(out, off, cap, "\\\"" as *u8) 136 } else { 137 if b == 0x5c { 138 rc = nxl_put_cstr(out, off, cap, "\\\\" as *u8) 139 } else { 140 if b == 0x08 { 141 rc = nxl_put_cstr(out, off, cap, "\\b" as *u8) 142 } else { 143 if b == 0x09 { 144 rc = nxl_put_cstr(out, off, cap, "\\t" as *u8) 145 } else { 146 if b == 0x0a { 147 rc = nxl_put_cstr(out, off, cap, "\\n" as *u8) 148 } else { 149 if b == 0x0c { 150 rc = nxl_put_cstr(out, off, cap, "\\f" as *u8) 151 } else { 152 if b == 0x0d { 153 rc = nxl_put_cstr(out, off, cap, "\\r" as *u8) 154 } else { 155 if b >= 0 && b < 0x20 { 156 // \u00XX 157 rc = nxl_put_cstr(out, off, cap, "\\u00" as *u8) 158 if rc != NXL_OK { return rc } 159 let hi: i64 = (b >> 4) & 0xf 160 let lo: i64 = b & 0xf 161 var hex_hi: i64 = 0x30 + hi 162 if hi >= 10 { hex_hi = 0x61 + (hi - 10) } 163 var hex_lo: i64 = 0x30 + lo 164 if lo >= 10 { hex_lo = 0x61 + (lo - 10) } 165 let rh: i64 = nxl_put(out, off, cap, hex_hi) 166 if rh != NXL_OK { return rh } 167 rc = nxl_put(out, off, cap, hex_lo) 168 } else { 169 rc = nxl_put(out, off, cap, b) 170 } 171 } 172 } 173 } 174 } 175 } 176 } 177 } 178 if rc != NXL_OK { return rc } 179 i = i + 1 180 } 181 return NXL_OK 182} 183 184// i64 -> ASCII decimal using caller-supplied scratch. 185func nxl_put_dec(out: *u8, off: *i64, cap: i64, 186 scratch: *u8, v: i64) -> i64 { 187 if v == 0 { return nxl_put(out, off, cap, 0x30) } 188 var n: i64 = v 189 var sign: i64 = 0 190 if n < 0 { sign = 1; n = 0 - n } 191 var k: i64 = 0 192 while n > 0 { 193 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8 194 n = n / 10 195 k = k + 1 196 } 197 if sign == 1 { 198 let rcs: i64 = nxl_put(out, off, cap, 0x2d) 199 if rcs != NXL_OK { return rcs } 200 } 201 var ri: i64 = k - 1 202 while ri >= 0 { 203 let rc: i64 = nxl_put(out, off, cap, scratch[ri] as i64) 204 if rc != NXL_OK { return rc } 205 ri = ri - 1 206 } 207 return NXL_OK 208} 209 210// ---- Entry: emit a complete log line with no extra fields ------- 211// 212// Output: 213// {"ts_ms":1234,"level":"INFO","module":"nx_audit","msg":"started"}\n 214 215func nx_log_emit_line( 216 out: *u8, off: *i64, cap: i64, 217 ts_ms: i64, 218 level: i64, 219 module: *u8, module_n: i64, 220 msg: *u8, msg_n: i64, 221 scratch: *u8) -> i64 { 222 if out == (0 as *u8) { return NXL_BAD_ARG } 223 if off == (0 as *i64) { return NXL_BAD_ARG } 224 if module == (0 as *u8) { return NXL_BAD_ARG } 225 if msg == (0 as *u8) { return NXL_BAD_ARG } 226 if scratch == (0 as *u8) { return NXL_BAD_ARG } 227 if cap <= 0 { return NXL_BAD_ARG } 228 if module_n <= 0 { return NXL_BAD_ARG } 229 if msg_n < 0 { return NXL_BAD_ARG } 230 if nxl_level_is_valid(level) != 1 { return NXL_BAD_LEVEL } 231 232 let r1: i64 = nxl_put_cstr(out, off, cap, "{\"ts_ms\":" as *u8) 233 if r1 != NXL_OK { return r1 } 234 let r2: i64 = nxl_put_dec(out, off, cap, scratch, ts_ms) 235 if r2 != NXL_OK { return r2 } 236 let r3: i64 = nxl_put_cstr(out, off, cap, ",\"level\":\"" as *u8) 237 if r3 != NXL_OK { return r3 } 238 let r4: i64 = nxl_put_bytes(out, off, cap, 239 nxl_level_name(level), 240 nxl_level_name_len(level)) 241 if r4 != NXL_OK { return r4 } 242 let r5: i64 = nxl_put_cstr(out, off, cap, "\",\"module\":\"" as *u8) 243 if r5 != NXL_OK { return r5 } 244 let r6: i64 = nxl_put_json_str_bytes(out, off, cap, module, module_n) 245 if r6 != NXL_OK { return r6 } 246 let r7: i64 = nxl_put_cstr(out, off, cap, "\",\"msg\":\"" as *u8) 247 if r7 != NXL_OK { return r7 } 248 let r8: i64 = nxl_put_json_str_bytes(out, off, cap, msg, msg_n) 249 if r8 != NXL_OK { return r8 } 250 return nxl_put_cstr(out, off, cap, "\"}\n" as *u8) 251} 252 253// ---- Entry: emit a log line with a single string-valued extra field ---- 254// 255// Output: 256// {"ts_ms":1234,"level":"INFO","module":"x","msg":"y","<field>":"<val>"}\n 257 258func nx_log_emit_line_with_str( 259 out: *u8, off: *i64, cap: i64, 260 ts_ms: i64, level: i64, 261 module: *u8, module_n: i64, 262 msg: *u8, msg_n: i64, 263 field: *u8, field_n: i64, 264 val: *u8, val_n: i64, 265 scratch: *u8) -> i64 { 266 if field == (0 as *u8) { return NXL_BAD_ARG } 267 if field_n <= 0 { return NXL_BAD_ARG } 268 if val == (0 as *u8) { return NXL_BAD_ARG } 269 if val_n < 0 { return NXL_BAD_ARG } 270 if out == (0 as *u8) { return NXL_BAD_ARG } 271 if off == (0 as *i64) { return NXL_BAD_ARG } 272 if module == (0 as *u8) { return NXL_BAD_ARG } 273 if msg == (0 as *u8) { return NXL_BAD_ARG } 274 if scratch == (0 as *u8) { return NXL_BAD_ARG } 275 if cap <= 0 { return NXL_BAD_ARG } 276 if module_n <= 0 { return NXL_BAD_ARG } 277 if msg_n < 0 { return NXL_BAD_ARG } 278 if nxl_level_is_valid(level) != 1 { return NXL_BAD_LEVEL } 279 280 let r1: i64 = nxl_put_cstr(out, off, cap, "{\"ts_ms\":" as *u8) 281 if r1 != NXL_OK { return r1 } 282 let r2: i64 = nxl_put_dec(out, off, cap, scratch, ts_ms) 283 if r2 != NXL_OK { return r2 } 284 let r3: i64 = nxl_put_cstr(out, off, cap, ",\"level\":\"" as *u8) 285 if r3 != NXL_OK { return r3 } 286 let r4: i64 = nxl_put_bytes(out, off, cap, 287 nxl_level_name(level), 288 nxl_level_name_len(level)) 289 if r4 != NXL_OK { return r4 } 290 let r5: i64 = nxl_put_cstr(out, off, cap, "\",\"module\":\"" as *u8) 291 if r5 != NXL_OK { return r5 } 292 let r6: i64 = nxl_put_json_str_bytes(out, off, cap, module, module_n) 293 if r6 != NXL_OK { return r6 } 294 let r7: i64 = nxl_put_cstr(out, off, cap, "\",\"msg\":\"" as *u8) 295 if r7 != NXL_OK { return r7 } 296 let r8: i64 = nxl_put_json_str_bytes(out, off, cap, msg, msg_n) 297 if r8 != NXL_OK { return r8 } 298 let r9: i64 = nxl_put_cstr(out, off, cap, "\",\"" as *u8) 299 if r9 != NXL_OK { return r9 } 300 let r10: i64 = nxl_put_json_str_bytes(out, off, cap, field, field_n) 301 if r10 != NXL_OK { return r10 } 302 let r11: i64 = nxl_put_cstr(out, off, cap, "\":\"" as *u8) 303 if r11 != NXL_OK { return r11 } 304 let r12: i64 = nxl_put_json_str_bytes(out, off, cap, val, val_n) 305 if r12 != NXL_OK { return r12 } 306 return nxl_put_cstr(out, off, cap, "\"}\n" as *u8) 307} 308 309// ---- Entry: emit a log line with a single int-valued extra field ---- 310// 311// Output: 312// {"ts_ms":1234,"level":"WARN","module":"x","msg":"y","<field>":<int>}\n 313 314func nx_log_emit_line_with_int( 315 out: *u8, off: *i64, cap: i64, 316 ts_ms: i64, level: i64, 317 module: *u8, module_n: i64, 318 msg: *u8, msg_n: i64, 319 field: *u8, field_n: i64, 320 val: i64, 321 scratch: *u8) -> i64 { 322 if field == (0 as *u8) { return NXL_BAD_ARG } 323 if field_n <= 0 { return NXL_BAD_ARG } 324 if out == (0 as *u8) { return NXL_BAD_ARG } 325 if off == (0 as *i64) { return NXL_BAD_ARG } 326 if module == (0 as *u8) { return NXL_BAD_ARG } 327 if msg == (0 as *u8) { return NXL_BAD_ARG } 328 if scratch == (0 as *u8) { return NXL_BAD_ARG } 329 if cap <= 0 { return NXL_BAD_ARG } 330 if module_n <= 0 { return NXL_BAD_ARG } 331 if msg_n < 0 { return NXL_BAD_ARG } 332 if nxl_level_is_valid(level) != 1 { return NXL_BAD_LEVEL } 333 334 let r1: i64 = nxl_put_cstr(out, off, cap, "{\"ts_ms\":" as *u8) 335 if r1 != NXL_OK { return r1 } 336 let r2: i64 = nxl_put_dec(out, off, cap, scratch, ts_ms) 337 if r2 != NXL_OK { return r2 } 338 let r3: i64 = nxl_put_cstr(out, off, cap, ",\"level\":\"" as *u8) 339 if r3 != NXL_OK { return r3 } 340 let r4: i64 = nxl_put_bytes(out, off, cap, 341 nxl_level_name(level), 342 nxl_level_name_len(level)) 343 if r4 != NXL_OK { return r4 } 344 let r5: i64 = nxl_put_cstr(out, off, cap, "\",\"module\":\"" as *u8) 345 if r5 != NXL_OK { return r5 } 346 let r6: i64 = nxl_put_json_str_bytes(out, off, cap, module, module_n) 347 if r6 != NXL_OK { return r6 } 348 let r7: i64 = nxl_put_cstr(out, off, cap, "\",\"msg\":\"" as *u8) 349 if r7 != NXL_OK { return r7 } 350 let r8: i64 = nxl_put_json_str_bytes(out, off, cap, msg, msg_n) 351 if r8 != NXL_OK { return r8 } 352 let r9: i64 = nxl_put_cstr(out, off, cap, "\",\"" as *u8) 353 if r9 != NXL_OK { return r9 } 354 let r10: i64 = nxl_put_json_str_bytes(out, off, cap, field, field_n) 355 if r10 != NXL_OK { return r10 } 356 let r11: i64 = nxl_put_cstr(out, off, cap, "\":" as *u8) 357 if r11 != NXL_OK { return r11 } 358 let r12: i64 = nxl_put_dec(out, off, cap, scratch, val) 359 if r12 != NXL_OK { return r12 } 360 return nxl_put_cstr(out, off, cap, "}\n" as *u8) 361}