From 474a1931c2ba4cd445503249d83458baa689fabb Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Tue, 12 May 2026 16:00:17 +0800 Subject: [PATCH] :memo: Add record patch API docs --- docs/api/append-fields.md | 82 +++++++++++++++++++++++++++++++++++++ docs/api/compose-patches.md | 82 +++++++++++++++++++++++++++++++++++++ docs/api/identity-patch.md | 76 ++++++++++++++++++++++++++++++++++ docs/api/index.md | 7 ++++ docs/api/prefix-message.md | 79 +++++++++++++++++++++++++++++++++++ docs/api/redact-field.md | 77 ++++++++++++++++++++++++++++++++++ docs/api/redact-fields.md | 80 ++++++++++++++++++++++++++++++++++++ docs/api/set-target.md | 79 +++++++++++++++++++++++++++++++++++ 8 files changed, 562 insertions(+) create mode 100644 docs/api/append-fields.md create mode 100644 docs/api/compose-patches.md create mode 100644 docs/api/identity-patch.md create mode 100644 docs/api/prefix-message.md create mode 100644 docs/api/redact-field.md create mode 100644 docs/api/redact-fields.md create mode 100644 docs/api/set-target.md diff --git a/docs/api/append-fields.md b/docs/api/append-fields.md new file mode 100644 index 0000000..f282813 --- /dev/null +++ b/docs/api/append-fields.md @@ -0,0 +1,82 @@ +--- +name: append-fields +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that appends extra fields to the record. +key-word: + - patch + - fields + - transform + - public +--- + +## Append-fields + +Create a `RecordPatch` that appends extra fields to `rec.fields`. Use it when records should be enriched with stable metadata before reaching sinks. + +### Interface + +```moonbit +pub fn append_fields(extra_fields : Array[Field]) -> RecordPatch {} +``` + +#### input + +- `extra_fields : Array[Field]` - Fields appended after the record's existing field list. + +#### output + +- `RecordPatch` - Patch that returns a record with appended fields. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- If `extra_fields` is empty, the patch returns the original record unchanged. +- If the original record has no fields, the appended fields become the new field list. +- Otherwise, the original fields stay first and `extra_fields` are appended afterward. +- This helper is useful for environment tags, service metadata, and bridge-layer context. + +### How to Use + +Here are some specific examples provided. + +#### When Add Service Metadata + +When every record should carry shared context: +```moonbit +let logger = Logger::new(console_sink()) + .with_patch(append_fields([ + field("service", "billing"), + field("region", "cn"), + ])) +``` + +In this example, the extra fields are added to every emitted record. + +#### When Compose With Message Rewriting + +When both visible and structured context are needed: +```moonbit +let patch = compose_patches([ + prefix_message("[api] "), + append_fields([field("component", "gateway")]), +]) +``` + +In this example, the record gains both textual and structured enrichment. + +### Error Case + +e.g.: +- If `extra_fields` is empty, the patch behaves like a no-op. + +- If appended field keys duplicate existing keys, both copies remain in the field list. + +### Notes + +1. This helper appends fields; it does not deduplicate or overwrite existing entries. + +2. Field order can matter for downstream formatting or inspection, so keep appended context intentional. + diff --git a/docs/api/compose-patches.md b/docs/api/compose-patches.md new file mode 100644 index 0000000..ac1288a --- /dev/null +++ b/docs/api/compose-patches.md @@ -0,0 +1,82 @@ +--- +name: compose-patches +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch pipeline from several ordered patches. +key-word: + - patch + - compose + - pipeline + - public +--- + +## Compose-patches + +Create a `RecordPatch` that applies several patches in order. This helper is the standard way to build deterministic record transformation pipelines. + +### Interface + +```moonbit +pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch {} +``` + +#### input + +- `patches : Array[RecordPatch]` - Patches applied sequentially from first to last. + +#### output + +- `RecordPatch` - Patch that returns the final record after every nested patch has run. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- Composition is ordered: each patch receives the result of the previous patch. +- Later patches can overwrite changes made by earlier patches. +- If the patch array is empty, the returned patch behaves like `identity_patch()`. +- This helper is useful for combining normalization, enrichment, and redaction into one reusable unit. + +### How to Use + +Here are some specific examples provided. + +#### When Build A Stable Rewrite Pipeline + +When a logger needs several transformations in one place: +```moonbit +let patch = compose_patches([ + set_target("api.gateway"), + prefix_message("[safe] "), + redact_fields(["token", "password"]), +]) +``` + +In this example, target rewrite, message prefixing, and field redaction happen in fixed order. + +#### When Reuse The Same Policy Across Loggers + +When several sinks should share one transformation bundle: +```moonbit +let shared_patch = compose_patches([ + append_fields([field("service", "billing")]), + redact_field("secret"), +]) +``` + +In this example, the composed patch can be attached to multiple loggers without repeating inline closures. + +### Error Case + +e.g.: +- If `patches` is empty, the returned patch leaves records unchanged. + +- If several patches rewrite the same property, the last patch in the array determines the final value. + +### Notes + +1. Keep patch order explicit because composition semantics are intentionally sequential. + +2. Prefer one composed patch over many ad hoc inline wrappers when the transformation policy is shared. + diff --git a/docs/api/identity-patch.md b/docs/api/identity-patch.md new file mode 100644 index 0000000..db251d2 --- /dev/null +++ b/docs/api/identity-patch.md @@ -0,0 +1,76 @@ +--- +name: identity-patch +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that returns records unchanged. +key-word: + - patch + - transform + - record + - public +--- + +## Identity-patch + +Create a `RecordPatch` that returns the input record unchanged. This helper is useful as a neutral default, a placeholder in configuration, or a composition baseline. + +### Interface + +```moonbit +pub fn identity_patch() -> RecordPatch {} +``` + +#### output + +- `RecordPatch` - Patch function that returns the original record without modification. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- The returned patch is a pure pass-through transformation. +- It does not allocate new fields or rewrite existing properties. +- This helper is useful when a patch slot must be provided but no mutation is currently needed. +- It also works as a safe baseline inside `compose_patches(...)` pipelines. + +### How to Use + +Here are some specific examples provided. + +#### When Need A Neutral Patch + +When a logger pipeline expects a patch but no rewrite is required yet: +```moonbit +let logger = Logger::new(console_sink()) + .with_patch(identity_patch()) +``` + +In this example, the logger shape stays consistent while records remain unchanged. + +#### When Build Patches Conditionally + +When a branch may or may not enable a real patch: +```moonbit +let patch = if enable_redaction { + redact_field("token") +} else { + identity_patch() +} +``` + +In this example, caller code can still treat the result as a normal `RecordPatch`. + +### Error Case + +e.g.: +- `identity_patch()` has no failure path; it simply returns the original record. + +- If a caller expects visible transformation, using this patch will intentionally produce none. + +### Notes + +1. Use this helper when you want a no-op patch with explicit intent instead of a custom inline closure. + +2. It is especially useful in conditional or generated patch pipelines. + diff --git a/docs/api/index.md b/docs/api/index.md index 8355f3d..f67f5db 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -60,6 +60,13 @@ BitLogger API navigation. - [not.md](./not.md) - [all-of.md](./all-of.md) - [any-of.md](./any-of.md) +- [identity-patch.md](./identity-patch.md) +- [set-target.md](./set-target.md) +- [prefix-message.md](./prefix-message.md) +- [append-fields.md](./append-fields.md) +- [redact-field.md](./redact-field.md) +- [redact-fields.md](./redact-fields.md) +- [compose-patches.md](./compose-patches.md) ## Config build flow diff --git a/docs/api/prefix-message.md b/docs/api/prefix-message.md new file mode 100644 index 0000000..b3b6f1c --- /dev/null +++ b/docs/api/prefix-message.md @@ -0,0 +1,79 @@ +--- +name: prefix-message +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that prepends text to the message. +key-word: + - patch + - message + - transform + - public +--- + +## Prefix-message + +Create a `RecordPatch` that prepends a fixed prefix to `rec.message`. This helper is useful for tagging records with stable context without changing every call site. + +### Interface + +```moonbit +pub fn prefix_message(prefix : String) -> RecordPatch {} +``` + +#### input + +- `prefix : String` - Text inserted before the original message. + +#### output + +- `RecordPatch` - Patch that returns a record whose message starts with `prefix`. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- The patch preserves the original message content after the inserted prefix. +- It does not touch target, level, timestamp, or fields. +- This helper is useful for environment tags, subsystem labels, and compatibility shims. +- When combined with other patches, composition order controls whether the prefix is applied before or after other message rewrites. + +### How to Use + +Here are some specific examples provided. + +#### When Add A Stable Channel Marker + +When console output should show one visible label: +```moonbit +let logger = Logger::new(console_sink()) + .with_patch(prefix_message("[worker] ")) +``` + +In this example, every emitted message starts with `[worker] `. + +#### When Compose With Redaction + +When records need both labeling and field protection: +```moonbit +let patch = compose_patches([ + prefix_message("[safe] "), + redact_field("token"), +]) +``` + +In this example, message text is rewritten while fields remain independently patchable. + +### Error Case + +e.g.: +- If `prefix` is empty, the patch behaves like a pass-through message rewrite. + +- If several patches prepend text, the final message reflects composition order. + +### Notes + +1. Prefer this helper over concatenating prefixes at every log call site. + +2. Keep prefixes short so they do not drown out the original message text. + diff --git a/docs/api/redact-field.md b/docs/api/redact-field.md new file mode 100644 index 0000000..3879641 --- /dev/null +++ b/docs/api/redact-field.md @@ -0,0 +1,77 @@ +--- +name: redact-field +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that redacts one field key. +key-word: + - patch + - redact + - fields + - public +--- + +## Redact-field + +Create a `RecordPatch` that replaces the value of every field whose key matches the given key. Use it when one sensitive field must be masked before records reach sinks. + +### Interface + +```moonbit +pub fn redact_field(key : String, placeholder~ : String = "***") -> RecordPatch {} +``` + +#### input + +- `key : String` - Field key whose matching values should be replaced. +- `placeholder : String` - Replacement value written into each matching field. + +#### output + +- `RecordPatch` - Patch that returns a record with matching field values redacted. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- The patch maps over the full field list and rewrites each field whose key equals `key`. +- Non-matching fields are preserved unchanged. +- All matching entries are redacted, not just the first one. +- The default placeholder is `"***"`, but callers can provide a different mask string. + +### How to Use + +Here are some specific examples provided. + +#### When Mask One Sensitive Field + +When authentication logs may carry a token: +```moonbit +let logger = Logger::new(console_sink(), target="auth") + .with_patch(redact_field("token")) +``` + +In this example, every `token` field value is replaced before output. + +#### When Use A Custom Placeholder + +When compliance rules require a specific visible marker: +```moonbit +let patch = redact_field("password", placeholder="[redacted]") +``` + +In this example, matching field values become `[redacted]` instead of the default mask. + +### Error Case + +e.g.: +- If the record has no matching field key, the patch returns a structurally identical field list. + +- If `placeholder` is empty, matching values are replaced by an empty string. + +### Notes + +1. This helper only rewrites fields; it does not search message text. + +2. Use `redact_fields(...)` when several keys should share the same masking rule. + diff --git a/docs/api/redact-fields.md b/docs/api/redact-fields.md new file mode 100644 index 0000000..ca3a08f --- /dev/null +++ b/docs/api/redact-fields.md @@ -0,0 +1,80 @@ +--- +name: redact-fields +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that redacts multiple field keys. +key-word: + - patch + - redact + - fields + - public +--- + +## Redact-fields + +Create a `RecordPatch` that replaces the value of every field whose key appears in a provided key list. Use it for shared masking rules across several sensitive fields. + +### Interface + +```moonbit +pub fn redact_fields(keys : Array[String], placeholder~ : String = "***") -> RecordPatch {} +``` + +#### input + +- `keys : Array[String]` - Field keys whose values should be redacted. +- `placeholder : String` - Replacement value written into each matching field. + +#### output + +- `RecordPatch` - Patch that returns a record with matching field values redacted. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- The patch maps over the record field list and rewrites any field whose key is contained in `keys`. +- Non-matching fields are preserved. +- The same placeholder is applied to every matching key in the set. +- This helper is useful for masking bundles such as `token`, `password`, and `secret` with one patch. + +### How to Use + +Here are some specific examples provided. + +#### When Mask Several Sensitive Keys + +When one logger must protect multiple credentials: +```moonbit +let logger = Logger::new(console_sink()) + .with_patch(redact_fields(["token", "password", "secret"])) +``` + +In this example, every matching field value is masked before the sink sees it. + +#### When Share One Custom Mask + +When the output should show one explicit policy marker: +```moonbit +let patch = redact_fields([ + "access_key", + "session_key", +], placeholder="[hidden]") +``` + +In this example, all matching fields use the same replacement text. + +### Error Case + +e.g.: +- If `keys` is empty, the patch behaves like a pass-through field mapper. + +- If the same key appears multiple times in `keys`, matching behavior is unchanged because membership is what matters. + +### Notes + +1. This helper is better than stacking many single-key patches when all keys share the same placeholder policy. + +2. It only rewrites structured fields, not text inside the message body. + diff --git a/docs/api/set-target.md b/docs/api/set-target.md new file mode 100644 index 0000000..922ccc7 --- /dev/null +++ b/docs/api/set-target.md @@ -0,0 +1,79 @@ +--- +name: set-target +group: api +category: patching +update-time: 20260512 +description: Create a reusable record patch that rewrites the target field. +key-word: + - patch + - target + - transform + - public +--- + +## Set-target + +Create a `RecordPatch` that rewrites `rec.target` to a fixed string. Use it when records should be reclassified or routed under a normalized target. + +### Interface + +```moonbit +pub fn set_target(target : String) -> RecordPatch {} +``` + +#### input + +- `target : String` - Target value that should replace the original record target. + +#### output + +- `RecordPatch` - Patch that returns a record with the new target. + +### Explanation + +Detailed rules explaining key parameters and behaviors + +- The patch copies the original record and replaces only its `target`. +- Message, level, timestamp, and fields are preserved. +- This helper is useful when several call sites should appear under one logical namespace. +- It can be combined with `prefix_message(...)` or `append_fields(...)` in ordered pipelines. + +### How to Use + +Here are some specific examples provided. + +#### When Normalize Child Targets + +When several producers should share one target label: +```moonbit +let logger = Logger::new(console_sink(), target="worker") + .with_patch(set_target("worker.batch")) +``` + +In this example, downstream filters and sinks only see `worker.batch`. + +#### When Redirect Records Before Fanout + +When a routing layer expects one canonical target: +```moonbit +let patch = compose_patches([ + set_target("audit"), + append_fields([field("source", "legacy")]), +]) +``` + +In this example, reclassification and enrichment happen in a predictable order. + +### Error Case + +e.g.: +- If `target` is empty, the patch still rewrites the record target to an empty string. + +- If later patches also rewrite the target, the last applied patch wins. + +### Notes + +1. Use this helper for target normalization, not for filtering decisions. + +2. Prefer explicit patch order when multiple target rewrites are possible. +