📝 Add filter predicate API docs

This commit is contained in:
Nanaloveyuki
2026-05-12 15:56:30 +08:00
parent 9248135b9c
commit dd895ac211
9 changed files with 644 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
---
name: all-of
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that requires every nested predicate to pass.
key-word:
- combine
- filter
- predicate
- public
---
## All-of
Create a `RecordPredicate` that returns `true` only when every predicate in the array returns `true`. This helper is the standard way to build strict multi-condition filters.
### Interface
```moonbit
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {}
```
#### input
- `predicates : Array[RecordPredicate]` - Predicates that must all succeed for a record to match.
#### output
- `RecordPredicate` - Predicate that returns `true` only when every nested predicate returns `true`.
### Explanation
Detailed rules explaining key parameters and behaviors
- Predicates are evaluated in array order.
- Evaluation stops early on the first predicate that returns `false`.
- If the array is empty, the combined predicate returns `true` because no condition failed.
- This helper is ideal for combining namespace, level, and field requirements into one reusable rule.
### How to Use
Here are some specific examples provided.
#### When Require Several Conditions
When routing should be both target- and level-aware:
```moonbit
let predicate = all_of([
target_has_prefix("service.api"),
level_at_least(Level::Warn),
])
```
In this example, records must satisfy both conditions before they pass.
#### When Add Field Constraints
When only contextual failures should remain:
```moonbit
let predicate = all_of([
message_contains("failed"),
has_field("request_id"),
not_(field_equals("tenant", "internal")),
])
```
In this example, the filter stays readable even though the rule has several parts.
### Error Case
e.g.:
- If `predicates` is empty, the returned predicate always evaluates to `true`.
- If one nested predicate is too strict, the whole combination may reject more records than expected.
### Notes
1. Put the cheapest or most selective predicates earlier when evaluation cost matters.
2. `all_of(...)` is usually easier to maintain than a custom inline predicate closure.
+82
View File
@@ -0,0 +1,82 @@
---
name: any-of
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that passes when any nested predicate matches.
key-word:
- combine
- filter
- predicate
- public
---
## Any-of
Create a `RecordPredicate` that returns `true` when at least one predicate in the array returns `true`. This helper is useful for routing several independent cases through the same path.
### Interface
```moonbit
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {}
```
#### input
- `predicates : Array[RecordPredicate]` - Predicates where any successful match should admit the record.
#### output
- `RecordPredicate` - Predicate that returns `true` when at least one nested predicate returns `true`.
### Explanation
Detailed rules explaining key parameters and behaviors
- Predicates are evaluated in array order.
- Evaluation stops early on the first predicate that returns `true`.
- If the array is empty, the combined predicate returns `false` because no predicate matched.
- This helper is useful when several targets, levels, or field signatures should share one sink.
### How to Use
Here are some specific examples provided.
#### When Accept Several Target Paths
When multiple subsystems should share one route:
```moonbit
let predicate = any_of([
target_is("audit"),
target_has_prefix("security"),
])
```
In this example, either matching branch is enough for the record to pass.
#### When Combine Different Diagnostic Conditions
When several independent signals are interesting:
```moonbit
let predicate = any_of([
level_at_least(Level::Error),
message_contains("timeout"),
field_equals("retryable", "true"),
])
```
In this example, one satisfied condition is enough to keep the record visible.
### Error Case
e.g.:
- If `predicates` is empty, the returned predicate always evaluates to `false`.
- If one nested predicate is too broad, it may shadow the intent of the other branches.
### Notes
1. Put the most common or cheapest success path earlier when evaluation cost matters.
2. Use `any_of(...)` when a single sink should accept multiple independent match patterns.
+80
View File
@@ -0,0 +1,80 @@
---
name: field-equals
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that matches a field by exact key and value.
key-word:
- field
- filter
- predicate
- public
---
## Field-equals
Create a `RecordPredicate` that returns `true` when a record contains a field whose key and value both match exactly. Use it for stable attribute-based routing.
### Interface
```moonbit
pub fn field_equals(key : String, value : String) -> RecordPredicate {}
```
#### input
- `key : String` - Field key to inspect.
- `value : String` - Exact value expected for that key.
#### output
- `RecordPredicate` - Predicate that matches records containing a field with the expected key and value.
### Explanation
Detailed rules explaining key parameters and behaviors
- Matching requires both `field.key == key` and `field.value == value`.
- The predicate returns `true` on the first matching field.
- This helper is useful for routing records by environment, tenant, operation name, or fixed tags.
- It is stricter than `has_field(...)` because presence alone is not enough.
### How to Use
Here are some specific examples provided.
#### When Keep One Tenant Stream
When log routing should isolate one tenant:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(field_equals("tenant", "acme"))
```
In this example, records for other tenants are excluded.
#### When Combine With Exact Target Matching
When field and target must both match:
```moonbit
let predicate = all_of([
target_is("billing"),
field_equals("region", "cn"),
])
```
In this example, only billing records tagged for the `cn` region remain.
### Error Case
e.g.:
- If `key` or `value` is empty, matching still uses exact equality and may produce no results unless records contain the same empty string.
- If a record contains the key with multiple values, any one exact match is enough for the predicate to return `true`.
### Notes
1. Prefer exact field matching over message substring matching for long-term routing rules.
2. Keep field naming stable across producers if this predicate is reused in shared configs.
+79
View File
@@ -0,0 +1,79 @@
---
name: has-field
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that checks whether a field key exists.
key-word:
- field
- filter
- predicate
- public
---
## Has-field
Create a `RecordPredicate` that returns `true` when a record contains at least one field with the given key. Use it when presence alone matters more than a specific field value.
### Interface
```moonbit
pub fn has_field(key : String) -> RecordPredicate {}
```
#### input
- `key : String` - Field key that should exist in the record.
#### output
- `RecordPredicate` - Predicate that matches records containing a field whose key equals `key`.
### Explanation
Detailed rules explaining key parameters and behaviors
- The predicate scans `rec.fields` in order and stops on the first matching key.
- Only the key is checked; the field value is ignored.
- This helper works well for optional metadata such as `request_id`, `tenant`, or `trace_id`.
- It can be combined with `field_equals(...)` when some records need stricter field matching.
### How to Use
Here are some specific examples provided.
#### When Keep Records Carrying Correlation Data
When only records with tracing context should pass:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(has_field("trace_id"))
```
In this example, records without `trace_id` are filtered out.
#### When Combine With Severity Rules
When contextual records should also meet a level threshold:
```moonbit
let predicate = all_of([
has_field("request_id"),
level_at_least(Level::Warn),
])
```
In this example, only warning-or-higher records with request context remain.
### Error Case
e.g.:
- If `key` is empty, the predicate only matches fields whose key is also empty.
- If the same key appears multiple times, the predicate still returns `true` after the first match.
### Notes
1. Use this helper when field presence is meaningful even if the actual value changes every time.
2. Field ordering does not change the result, only the early return cost.
+8
View File
@@ -51,7 +51,15 @@ BitLogger API navigation.
## Predicates and helpers ## Predicates and helpers
- [level-at-least.md](./level-at-least.md)
- [target-is.md](./target-is.md)
- [target-has-prefix.md](./target-has-prefix.md) - [target-has-prefix.md](./target-has-prefix.md)
- [message-contains.md](./message-contains.md)
- [has-field.md](./has-field.md)
- [field-equals.md](./field-equals.md)
- [not.md](./not.md)
- [all-of.md](./all-of.md)
- [any-of.md](./any-of.md)
## Config build flow ## Config build flow
+79
View File
@@ -0,0 +1,79 @@
---
name: level-at-least
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that keeps records at or above a minimum level.
key-word:
- level
- filter
- predicate
- public
---
## Level-at-least
Create a `RecordPredicate` that returns `true` when a record level is greater than or equal to a minimum threshold. This is the main helper for severity-based filtering.
### Interface
```moonbit
pub fn level_at_least(min_level : Level) -> RecordPredicate {}
```
#### input
- `min_level : Level` - The lowest level that should remain enabled.
#### output
- `RecordPredicate` - Predicate that keeps records whose `priority()` is at least the given minimum.
### Explanation
Detailed rules explaining key parameters and behaviors
- Matching is based on `Level::priority()`, not string comparison.
- Records at the exact same level as `min_level` are included.
- This helper is useful when a sink or child logger needs a stricter threshold than the parent logger.
- It composes cleanly with `all_of(...)` and `any_of(...)`.
### How to Use
Here are some specific examples provided.
#### When Keep Warnings And Errors
When a logger should keep only important records:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(level_at_least(Level::Warn))
```
In this example, `Warn` and `Error` records continue through the filter.
#### When Combine With Target Routing
When both severity and namespace matter:
```moonbit
let predicate = all_of([
target_has_prefix("service.api"),
level_at_least(Level::Info),
])
```
In this example, the predicate can be reused by multiple loggers or sinks.
### Error Case
e.g.:
- If `min_level` is `Level::Trace`, the predicate effectively allows all normal log records.
- If `min_level` is higher than the record level, the predicate returns `false` without modifying the record.
### Notes
1. Use this helper when you want filtering logic to remain explicit instead of embedding level checks inline.
2. This predicate is separate from a logger's own `min_level`, so both can be combined.
+79
View File
@@ -0,0 +1,79 @@
---
name: message-contains
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that matches records by message fragment.
key-word:
- message
- filter
- predicate
- public
---
## Message-contains
Create a `RecordPredicate` that returns `true` when a log message contains the given fragment. This helper is mainly useful for diagnostics, temporary routing, and focused debugging.
### Interface
```moonbit
pub fn message_contains(fragment : String) -> RecordPredicate {}
```
#### input
- `fragment : String` - Substring expected to appear in `rec.message`.
#### output
- `RecordPredicate` - Predicate that matches records whose message contains the fragment.
### Explanation
Detailed rules explaining key parameters and behaviors
- Matching uses `String::contains(...)` on the full message text.
- The predicate is case-sensitive unless the message was normalized earlier.
- This helper is convenient for ad hoc triage when targets or fields are not enough.
- It should usually complement more stable filters rather than replace them in long-term configs.
### How to Use
Here are some specific examples provided.
#### When Watch A Temporary Error Pattern
When a debugging session focuses on one phrase:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(message_contains("retry failed"))
```
In this example, only messages carrying that fragment remain visible.
#### When Combine With Target Filtering
When the same message should be isolated inside one subsystem:
```moonbit
let predicate = all_of([
target_has_prefix("worker.sync"),
message_contains("timeout"),
])
```
In this example, unrelated timeout messages from other targets are ignored.
### Error Case
e.g.:
- If `fragment` is empty, the predicate effectively matches every message because every string contains an empty substring.
- If messages are localized or reformatted frequently, fragment-based matching can become brittle.
### Notes
1. Prefer field- or target-based filtering for stable production rules.
2. Message substring filters are most valuable during debugging and migration periods.
+76
View File
@@ -0,0 +1,76 @@
---
name: not
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that negates another predicate.
key-word:
- negate
- filter
- predicate
- public
---
## Not
Create a `RecordPredicate` that returns the logical inverse of another predicate. This helper is the standard way to express exclusion rules without rewriting predicate bodies.
### Interface
```moonbit
pub fn not_(predicate : RecordPredicate) -> RecordPredicate {}
```
#### input
- `predicate : RecordPredicate` - Existing predicate to invert.
#### output
- `RecordPredicate` - Predicate that returns `true` when the original predicate returns `false`.
### Explanation
Detailed rules explaining key parameters and behaviors
- `not_(...)` delegates evaluation to the original predicate and flips the boolean result.
- It does not change the record or short-circuit upstream logic outside the wrapped predicate.
- This helper is useful for excluding known noisy targets, fields, or message patterns.
- It composes well with `all_of(...)` and `any_of(...)` for more expressive routing rules.
### How to Use
Here are some specific examples provided.
#### When Exclude One Target Prefix
When everything except one noisy subsystem should pass:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(not_(target_has_prefix("debug.spam")))
```
In this example, records under `debug.spam...` are filtered out.
#### When Invert A Field Match
When one tenant should be excluded from a shared stream:
```moonbit
let predicate = not_(field_equals("tenant", "internal"))
```
In this example, all non-internal tenant records continue through the filter.
### Error Case
e.g.:
- If the wrapped predicate always returns `true`, the negated predicate always returns `false`.
- If the wrapped predicate is too broad, the inversion may unintentionally admit unrelated records.
### Notes
1. Prefer `not_(...)` over duplicating inverse logic inline because the intent is easier to read.
2. Negation is most maintainable when the wrapped predicate is itself narrow and well named.
+79
View File
@@ -0,0 +1,79 @@
---
name: target-is
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that matches a single exact target.
key-word:
- target
- filter
- predicate
- public
---
## Target-is
Create a `RecordPredicate` that returns `true` only when a record target exactly matches the expected string. Use it when one specific logical source should pass through a filter.
### Interface
```moonbit
pub fn target_is(target : String) -> RecordPredicate {}
```
#### input
- `target : String` - Exact target value expected from the record.
#### output
- `RecordPredicate` - Predicate that matches records whose `rec.target` equals `target`.
### Explanation
Detailed rules explaining key parameters and behaviors
- Matching uses direct string equality.
- This helper does not perform prefix or fuzzy matching.
- It is useful for isolating one subsystem when targets are intentionally stable.
- Combine it with level predicates when a single target needs stricter routing rules.
### How to Use
Here are some specific examples provided.
#### When Keep One Exact Target
When only one service target should pass:
```moonbit
let logger = Logger::new(console_sink())
.with_filter(target_is("gateway.http"))
```
In this example, records from sibling targets such as `gateway.ws` are excluded.
#### When Route A Single Target To A Sink
When a split path needs an exact match:
```moonbit
let predicate = all_of([
target_is("audit"),
level_at_least(Level::Info),
])
```
In this example, the filter remains predictable because it does not rely on naming prefixes.
### Error Case
e.g.:
- If `target` is empty, only records whose target is also an empty string will match.
- If record targets are generated dynamically, exact matching may be too strict and lead to no matches.
### Notes
1. Prefer `target_has_prefix(...)` when targets are hierarchical and you want subtree matching.
2. Exact target filters are easiest to maintain when target naming is stable across the project.