mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
📝 Add filter predicate API docs
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -51,7 +51,15 @@ BitLogger API navigation.
|
||||
|
||||
## 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)
|
||||
- [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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user