Softanza
DémarrerEnglish
Softanza
Recherche · narration

Structured Logging with stzLog

Most logging ends the same way: a wall of text, and a grep the next morning. The trouble is that the moment you format an entry into a line, you throw away its structure — the level, the category, the fields — and spend the rest of its life trying to parse it back. stzLog keeps the structure. A log is a stream of records, and while your program runs you query it: "how many errors?", "every entry about part=api". This narration walks that. Every code block is real, and every output block is its actual output.

par Mansour Ayouni · dans le dépôt depuis le 2026-07-23 · série · la série performance · domaine Performance

Cet article est écrit en anglais, comme la bibliothèque.

A log you can query while the program is still running


A log is a stream of leveled entries

Haro
oLog = new stzLog("deploy")
oLog.Info("build started")
oLog.Warn("low disk")
oLog.Record(:error, "compile failed", [ [ :part, "api" ], [ :code, 2 ] ])

? oLog.AsText()

illustration

Sortie promise
1784766158766 INFO deploy: build started
1784766158766 WARN deploy: low disk
1784766158766 ERROR deploy: compile failed  {part=api, code=2}

Info / Warn / Error (and Trace / Debug / Fatal) are the everyday shortcuts. Record(level, msg, fields) is the general form — it carries fields, the structured context that turns a message into a queryable record. Every entry is timestamped (epoch ms) and tagged with the log's category (deploy). Writes return the log, so they chain: oLog.Info("a").Warn("b").

The structure is the point — query it

Because each entry is a record, the log answers questions directly, in the same program, with no parsing:

Haro
? "errors: " + oLog.CountOfLevel(:error)
? "about the api part: " + len(oLog.Where(:part, "api"))

exécuté le 2026-10-08 20:42 au commit 4a184e6f0 : a tenu sa promesse

Sortie promise
errors: 1
about the api part: 1

CountOfLevel / EntriesOfLevel count and filter by level; Where(key, value) finds every entry carrying a field; Since(ms) slices by time; LastEntry() gives the most recent. This is the difference from a text log: you interrogate it as it stands, not after shipping strings somewhere and rebuilding the structure.

A level threshold keeps production quiet

Levels are ordered — trace < debug < info < warn < error < fatal — and a threshold (default info) drops anything below it:

Haro
oLog.Debug("verbose detail")     # dropped at the default info threshold -- nothing recorded
oLog.SetLevelQ(:trace)           # now trace/debug are recorded too
oLog.Debug("now visible")

illustration

So the same code logs debug freely; a production run at info ignores it, a diagnostic run at trace keeps it — no call sites change.

Render for humans, or for a pipeline

The text form is for reading. The JSON form is for shipping — fields become top-level keys, so it drops straight into any log stack:

Haro
? oLog.AsJson()

illustration

Json, montré pour comparaison, non exécuté
[
  { "ts": 1784766158766, "level": "info",  "category": "deploy", "message": "build started" },
  { "ts": 1784766158766, "level": "warn",  "category": "deploy", "message": "low disk" },
  { "ts": 1784766158766, "level": "error", "category": "deploy", "message": "compile failed", "part": "api", "code": "2" }
]

WriteToFile / WriteJsonToFile persist it; SetEchoQ(TRUE) also mirrors each recorded entry to the console; SetCapQ(n) bounds memory with FIFO eviction.

Where it earns its keep: the delivery plane

You don't have to reach for stzLog by hand to benefit — the delivery envelopes keep their own structured logs. A production deploy is then an inspectable record, not a flat list:

Haro
oDep = oDelivery.Deploy(:Production)      # drives the deployment, logging each step

oDep.Log().EntriesOfLevel(:error)         # what failed
oDep.Log().Where(:part, "api")            # the api part's whole story -- store, launch, verify
? oDep.Log().AsJson()                     # ship the run to your pipeline

illustration

A committed run logs each step done and ends run complete; a failed run logs the failing step as an error, each rollback as a warning, and the whole thing stays queryable by the failing part. The same holds for stzDelivery.Deploy (the phases), stzDeployment.Run (the steps), and stzPlatform.Build/Deploy (build-by-language and the governance refusals) — each exposes Log().


The log stopped being something you read after the fact and became something you query during. "Every error about part=api" is a method call, not a regex over yesterday's file.

Runnable guard: libraries/stzlib/base/test/common/log_narrated.ring (20/20). Design: SOFTANZA_LOGGING.md(s'ouvre dans un nouvel onglet).

Prouvé par

L'article nomme ces gardes de scénario de la bibliothèque :

Lectures liées

Citer

Mansour Ayouni. « Structured Logging with stzLog ». Softanza narrations, 2026. https://mayouni.github.io/stzsite/fr/narrations/stz-structured-logging-narration.html

Exécuté au nom de Haro le 2026-10-08 20:42 dans la bibliothèque au commit 4a184e6f0, bloc après bloc dans un seul processus : 1 bloc(s) sur 5 ont tenu leur promesse ; les autres sont des illustrations. Le code des articles est du code Haro. Là où un article a été écrit avant que la langue prenne son nom actuel, le nom a été changé et rien d'autre. Genre, domaine et série lus dans le nom du fichier, année tirée du premier commit : dérivés, jusqu'à ce que l'article porte son propre en-tête. Le fichier sur GitHub(s'ouvre dans un nouvel onglet)