28 The Ridgun Book
The log Lib — Structured NDJSON Logging and Automatic Secret Redaction
28.1. Why Does the log Lib Exist? (The Real-World Problem & Ridgun Philosophy)
"Why do I need a
loglib written from scratch in pure Ridgun instead of just linking a legacy C library?"
That is the first question every systems engineer asks when meeting log. And the answer captures the entire philosophy of ridgun-libs:
Let's be honest: dumping request fields into a log line and realizing weeks later that password, api_key, or authorization headers were stored in cleartext in your log aggregator is a classic security incident.
The log lib provides severity filtering (DEBUG, INFO, WARN, ERROR), human-readable text and structured NDJSON formatters, and automatic secret redaction (password, secret, token, api_key, authorization, cookie, private_key $\rightarrow$ "[REDACTED]", mitigating VUL-LOG-001).
In a traditional ecosystem, solving LOG tasks means pulling in external C dependencies, wrestling with raw pointers, opaque type conversions, unchecked exceptions, or unpredictable garbage-collector pauses. In Ridgun, the log lib (src/main/ridgun/lib/log.rg) tackles the problem with four non-negotiable principles:
- Declare
import log(orimport log as alias) at the top of your file: every lib defines itsmodule :: lognamespace, and under--strictmulti-word functions are always called with natural spaces. - Chain your data pipelines left-to-right with
->: writeinput step one -> step two -> result, use(+ _ _ +)sequence masks to destructure tuples/lists, and omit_ =on sequential statements (or discard with-> ()). - Tail-Call Optimization (TCO): all internal loops in
logare tail-recursive and compile into native hardware loops using $O(1)$ stack space. - Deterministic Memory (
RMM1/RMM2): every intermediate allocation is reclaimed at the exact instruction where its last read finishes, with no Garbage Collector.
28.2. Native Artifacts, Imports, and Complete log API Catalog
In ridgun-libs, log is a first-class lib located at src/main/ridgun/lib/log.rg (domain LOG). When built with stable bundle, it compiles into three native artifacts:
| Artifact | Path | Purpose |
|---|---|---|
| Ridgun Bundle | target/bundles/log.rglib | Portable distribution and direct import log resolution |
| Compiled Archive | target/log.rgc | Type-checked intermediate artifact verified by ridgunc |
| ELF64 Shared Object | target/liblog.so | Native dynamic shared library with zero C or libc dependencies |
Every lib declares its own namespace via
module :: log: simply writeimport log(orimport log as alias) at the top and call its functions with natural spaces (my fn x) and left-to-right->pipelines (x my fn -> result).
28.2.1. Summary of log Functional Families
| Signature / Function / Type | Purpose & Usage |
|---|---|
log level rank / log should emit | Compare log levels (DEBUG=10, INFO=20, WARN=30, ERROR=40) against configured threshold |
log is sensitive key / log redact fields | Case-insensitively replace secret values with "[REDACTED]" |
log format text / log format json | Format single-line key=value text or RFC 8259 NDJSON with automatic secret redaction |
28.2.2. Exhaustive Catalog of Types and Signatures in src/main/ridgun/lib/log.rg
Below is the complete inventory of algebraic data types (type), mixfix operators (op ::), and function signatures (fn ::) defined in src/main/ridgun/lib/log.rg (0 types, 0 operators, and 21 functions):
| # | Function / Signature (fn ::) | Return Type |
|---|---|---|
1 | fn :: lg lower char String Int Int | String |
2 | fn :: lg to lower loop String Int Int | String |
3 | fn :: lg to lower String | String |
4 | fn :: lg rank from lower String | Int |
5 | fn :: log level rank String | Int |
6 | fn :: log level name Int | String |
7 | fn :: log should emit String String | Bool |
8 | fn :: lg check sens lower String | Bool |
9 | fn :: log is sensitive key String | Bool |
10 | fn :: lg pair key (String String) | String |
11 | fn :: lg pair val (String String) | String |
12 | fn :: lg redact pair (String String) | String String |
13 | fn :: log redact fields [String String] | [String String] |
14 | fn :: lg esc char String Int Int | String |
15 | fn :: lg esc loop String Int Int | String |
16 | fn :: lg esc str String | String |
17 | fn :: lg quote String | String |
18 | fn :: lg fmt text fields [String String] | String |
19 | fn :: log format text String String [String String] | String |
20 | fn :: lg fmt json fields [String String] | String |
21 | fn :: log format json String String [String String] | String |
28.3. Deep Dive into log: Step-by-Step Examples for Every Feature
A signature table alone is never enough: let's explore every capability of the log lib through progressive, runnable Ridgun examples. We will start with two everyday tabular recipes and then walk through every functional scenario, transformation, and boundary condition verified in src/test/ridgun/test log.rg.
28.3.1. Example 1: Core Tabular Workflow with log
Notice how in Ridgun the = signs and transformation steps using log align into a clean, scannable table without unnecessary mutable variables or hidden side effects:
import log
fn :: auditar login String String => String
auditar login usuario clave =
[("user", usuario), ("password", clave), ("ip", "10.0.0.1")] -> campos
log format json "INFO" "intento de acceso" campos
28.3.2. Example 2: Safe Composition and Boundary Handling
Every function in the log lib is designed to compose cleanly with boolean guards | and with the rest of the 32 libs in ridgun-libs:
import log
fn :: emitir si procede String String String => String
emitir si procede umbral nivel msg =
| log should emit umbral nivel = log format text nivel msg [("service", "solstice")]
| otherwise = ""
28.3.3. Example 3: Severity Level Hierarchy (DEBUG, INFO, WARN, ERROR) and Threshold Filtering (log enabled)
In this hands-on walkthrough we thoroughly exercise how log behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test log levels and filtering => ()
test log levels and filtering =
assert int equals (log level rank "debug") 10 "AC1: rank debug == 10"
assert int equals (log level rank "DEBUG") 10 "AC1: rank DEBUG == 10"
assert int equals (log level rank "info") 20 "AC1: rank info == 20"
assert int equals (log level rank "INFO") 20 "AC1: rank INFO == 20"
assert int equals (log level rank "warn") 30 "AC1: rank warn == 30"
assert int equals (log level rank "warning") 30 "AC1: rank warning alias == 30"
assert int equals (log level rank "error") 40 "AC1: rank error == 40"
assert int equals (log level rank "ERR") 40 "AC1: rank ERR alias == 40"
assert int equals (log level rank "unknown_level") 20 "AC1: unknown level defaults to INFO (20)"
assert str eq (log level name 10) "DEBUG" "AC1: level name 10 -> DEBUG"
assert str eq (log level name 20) "INFO" "AC1: level name 20 -> INFO"
assert str eq (log level name 30) "WARN" "AC1: level name 30 -> WARN"
assert str eq (log level name 40) "ERROR" "AC1: level name 40 -> ERROR"
assert true (log should emit "DEBUG" "DEBUG") "AC1: DEBUG emits DEBUG"
assert true (log should emit "DEBUG" "ERROR") "AC1: DEBUG emits ERROR"
assert true (log should emit "WARN" "ERROR") "AC1: WARN emits ERROR"
assert true (log should emit "WARN" "warning") "AC1: WARN emits warning"
assert false (log should emit "WARN" "INFO") "AC1: WARN suppresses INFO"
assert false (log should emit "ERROR" "WARN") "AC1: ERROR suppresses WARN"
assert false (log should emit "ERROR" "DEBUG") "AC1: ERROR suppresses DEBUG"
()
28.3.4. Example 4: Automatic Sensitive Key Detection (password, token, secret, api_key, jwt) and [REDACTED] Masking
In this hands-on walkthrough we thoroughly exercise how log behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test log sensitive key redaction => ()
test log sensitive key redaction =
assert true (log is sensitive key "password") "AC2: password"
assert true (log is sensitive key "user_passwd") "AC2: user_passwd"
assert true (log is sensitive key "CLIENT_SECRET") "AC2: CLIENT_SECRET"
assert true (log is sensitive key "access_token") "AC2: access_token"
assert true (log is sensitive key "X-Api-Key") "AC2: X-Api-Key"
assert true (log is sensitive key "api_key_id") "AC2: api_key_id"
assert true (log is sensitive key "apikey") "AC2: apikey"
assert true (log is sensitive key "Authorization") "AC2: Authorization"
assert true (log is sensitive key "session_cookie") "AC2: session_cookie"
assert true (log is sensitive key "ssh_private_key") "AC2: ssh_private_key"
assert true (log is sensitive key "private-key-pem") "AC2: private-key-pem"
assert false (log is sensitive key "username") "AC2: username safe"
assert false (log is sensitive key "request_id") "AC2: request_id safe"
assert false (log is sensitive key "status_code") "AC2: status_code safe"
()
28.3.5. Example 5: Formatting Log Entries as Structured Text (key=val) and Single-Line JSON (NDJSON) with ISO-8601 Timestamps
In this hands-on walkthrough we thoroughly exercise how log behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test log text and ndjson formatting => ()
test log text and ndjson formatting =
fields = [("user", "alice"), ("password", "s3cr3t"), ("auth_token", "jwt-123"), ("cookie", "sid=99")]
txt = log format text "info" "user login" fields
assert str eq txt "[INFO] user login user=alice password=[REDACTED] auth_token=[REDACTED] cookie=[REDACTED]" "AC3: log format text redacts all secret keys"
txt_empty = log format text "error" "disk full" []
assert str eq txt_empty "[ERROR] disk full" "AC3: log format text with empty fields"
-- Log injection defense: newlines, carriage returns, tabs, quotes, and backslashes in msg or values
inj_fields = [("path", "C:\\app\\bin"), ("note", "line1\nline2\r\t\"quoted\"")]
jsn = log format json "warn" "auth check\nforged_line" fields
assert str eq jsn "{\"level\":\"WARN\",\"msg\":\"auth check\\nforged_line\",\"user\":\"alice\",\"password\":\"[REDACTED]\",\"auth_token\":\"[REDACTED]\",\"cookie\":\"[REDACTED]\"}" "AC3: NDJSON escapes newline injection and redacts secrets"
jsn_esc = log format json "debug" "trace" inj_fields
assert str eq jsn_esc "{\"level\":\"DEBUG\",\"msg\":\"trace\",\"path\":\"C:\\\\app\\\\bin\",\"note\":\"line1\\nline2\\r\\t\\\"quoted\\\"\"}" "AC3: NDJSON escapes \\, \\n, \\r, \\t, and \\\""
()
28.4. Under the Hood: How log Is Built in Pure Ridgun (src/main/ridgun/lib/log.rg)
One of the biggest advantages of ridgun-libs is that there are no C black boxes. You can open src/main/ridgun/lib/log.rg and read exactly how every operation is implemented using pattern matching, | / |. guard trees, and tail-call optimization (TCO). Let's inspect the key building blocks of its real implementation:
28.4.1. Internal Implementation: Severity Levels (DEBUG, INFO, WARN, ERROR)
Observe how log.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
module :: log
fn :: lg rank from lower String => Int
lg rank from lower lv =
| lv == "debug" = 10
| lv == "info" = 20
| lv == "warn" or lv == "warning" = 30
| lv == "error" or lv == "err" = 40
| otherwise = 20
fn :: log level rank String => Int
log level rank level = level lg to lower -> lg rank from lower
fn :: log level name Int => String
log level name rank =
| rank <= 10 = "DEBUG"
| rank <= 20 = "INFO"
| rank <= 30 = "WARN"
| otherwise = "ERROR"
fn :: log should emit String String => Bool
log should emit min_level msg_level = (log level rank msg_level) >= (log level rank min_level)
28.4.2. Internal Implementation: Sensitive Key Detection & Redaction
Observe how log.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
fn :: lg check sens lower String => Bool
lg check sens lower lk =
| str contains lk "password" or str contains lk "passwd" = True
| str contains lk "secret" or str contains lk "token" = True
| str contains lk "api_key" or str contains lk "api-key" = True
| str contains lk "apikey" = True
| str contains lk "authorization" or str contains lk "cookie" = True
| str contains lk "private_key" or str contains lk "private-key" = True
| otherwise = False
fn :: log is sensitive key String => Bool
log is sensitive key key = key lg to lower -> lg check sens lower
fn :: lg pair key (String String) => String
lg pair key (k, _) = k
fn :: lg pair val (String String) => String
lg pair val (_, v) = v
fn :: lg redact pair (String String) => String String
lg redact pair (k, v) =
| log is sensitive key k = (k, "[REDACTED]")
| otherwise = (k, v)
fn :: log redact fields [String String] => [String String]
log redact fields [] = []
log redact fields (pair:rest) = [(lg redact pair pair)] :: (log redact fields rest)
28.4.3. Internal Implementation: Escaping & Text / NDJSON Formatting
Observe how log.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
fn :: lg esc char String Int Int => String
lg esc char s idx c =
| c == 34 = "\\\""
| c == 92 = "\\\\"
| c == 10 = "\\n"
| c == 13 = "\\r"
| c == 9 = "\\t"
| otherwise = str slice s idx (idx + 1)
fn :: lg esc loop String Int Int => String
lg esc loop s idx len =
| idx >= len = ""
| otherwise = (lg esc char s idx (str char at s idx)) + (lg esc loop s (idx + 1) len)
fn :: lg esc str String => String
lg esc str s = lg esc loop s 0 (str len s)
fn :: lg quote String => String
lg quote s = "\"" + (lg esc str s) + "\""
fn :: lg fmt text fields [String String] => String
lg fmt text fields [] = ""
lg fmt text fields (pair:rest) =
" " + (lg esc str (lg pair key pair)) + "=" + (lg esc str (lg pair val pair)) + (lg fmt text fields rest)
fn :: log format text String String [String String] => String
log format text level msg fields =
level log level rank -> log level name -> norm_lv
fields log redact fields -> lg fmt text fields -> fmt_f
"[" + norm_lv + "] " + (lg esc str msg) + fmt_f
fn :: lg fmt json fields [String String] => String
lg fmt json fields [] = ""
lg fmt json fields (pair:rest) =
"," + (lg quote (lg pair key pair)) + ":" + (lg quote (lg pair val pair)) + (lg fmt json fields rest)
fn :: log format json String String [String String] => String
log format json level msg fields =
level log level rank -> log level name -> norm_lv
fields log redact fields -> lg fmt json fields -> fmt_f
"{\"level\":" + (lg quote norm_lv) + ",\"msg\":" + (lg quote msg) + fmt_f + "}"
28.5. Algorithmic Guarantees, RMM1/RMM2 Memory, and the 5 Golden Rules of log
- Algorithmic & Safety Guarantee: Both
log format textandlog format jsonautomatically pass fields throughlog redact fieldsprior to serialization, ensuring secrets never leak by accident. - $O(1)$ Stack Complexity via TCO: Every recursive loop in
src/main/ridgun/lib/log.rgis in tail position, soridgunccompiles them into native jumps with zero stack growth. - Deterministic Memory (
RMM1/RMM2): Zero garbage-collector pauses; all intermediate allocations are reclaimed deterministically on scope exit. - Continuous Verification: Statically checked via
stable checkand tested by the automated suite insrc/test/ridgun/test log.rg.
The 5 Golden Rules When Programming with log
- Declare
import logat the top of your file and take advantage of space/underscore equivalence so your code reads like natural prose. - Align your data pipelines with
=: chainlogcalls into named intermediate steps and letstable formatalign the columns. - Leverage
log's total functions inside|guards: inspect return values or unwrap ADTs via pattern matching rather than assuming external input is valid. - Avoid cloning large structures when only reading: remember that under
RMM2, function input arguments incur zero reference-count overhead (+0/-0). - Pair
logwithtest(import test): always lock in your domain invariants withassert equalsorassert true.
In Chapter 29, we will explore the test lib in full depth (The test Lib — Unit and Functional Test Assertion Primitives (RG-011)).