📝 Add record patch API docs

This commit is contained in:
Nanaloveyuki
2026-05-12 16:00:17 +08:00
parent dd895ac211
commit 474a1931c2
8 changed files with 562 additions and 0 deletions
+82
View File
@@ -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.
+82
View File
@@ -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.
+76
View File
@@ -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.
+7
View File
@@ -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
+79
View File
@@ -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.
+77
View File
@@ -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.
+80
View File
@@ -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.
+79
View File
@@ -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.