mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
📝 Add record patch API docs
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -60,6 +60,13 @@ BitLogger API navigation.
|
|||||||
- [not.md](./not.md)
|
- [not.md](./not.md)
|
||||||
- [all-of.md](./all-of.md)
|
- [all-of.md](./all-of.md)
|
||||||
- [any-of.md](./any-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
|
## Config build flow
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
Reference in New Issue
Block a user