Skip to content

Filters

Two logging.Filter implementations tame a logger that says the same thing too often. Both attach to any stdlib logger, so they work with every GREL_LOG_BACKEND.

For dropping Kubernetes probe lines from the access log, see Quieting health probes.

Deduplicating Noisy Logs

DuplicateFilter silences repeated log records.

from logging import getLogger

from grelmicro.log import DuplicateFilter

logger = getLogger("grelmicro.health")
logger.addFilter(DuplicateFilter())

After 5 identical records, the filter silently drops any further occurrences. It tracks up to 100 distinct keys in an LRU cache.

key_mode="template" (default) uses the raw format string as the key, so %-style calls with different arguments share one counter. It is also about 3 times faster than rendered keying. DuplicateFilter and RateLimitFilter share the same five key_mode values:

key_mode Counter scope Good for
"logger" One counter per logger name Collapse every record from a noisy logger
"level" One counter per log level Collapse all records at a level
"global" One shared counter Collapse everything into one budget
"template" (default) One counter per (logger, level, str(record.msg)) Shares across arg values of the same template
"rendered" One counter per (logger, level, record.getMessage()) Distinguishes fully-rendered messages

Use key_mode="rendered" to track each rendered message separately, or pass key= for a custom fingerprint:

logger.addFilter(DuplicateFilter(key_mode="rendered"))
logger.addFilter(DuplicateFilter(key=lambda r: (r.name, r.exc_info)))

Set ttl to re-emit a burst of allowed_repetitions records every window during sustained floods, so operators continue to receive periodic reminders:

logger.addFilter(DuplicateFilter(allowed_repetitions=5, ttl=300))

State is in-process only. There is no cross-process sharing and no explicit reset API: construct a new filter if you need to wipe counters.

Tip

For code using from loguru import logger or structlog.get_logger() directly, use those libraries' native filtering.

Rate-Limiting Noisy Logs

RateLimitFilter drops records when a token bucket is empty. It allows bursts: up to capacity records can pass through at once, and the bucket then refills at refill_rate records per second.

from logging import getLogger

from grelmicro.log import RateLimitFilter

# Allow a burst of 10 records per logger, then 1 record/sec sustained.
logger = getLogger("grelmicro.ingest")
logger.addFilter(RateLimitFilter(capacity=10, refill_rate=1))

By default the filter buckets per logger: each logger has its own burst budget. Swap key_mode for different grouping:

key_mode Bucket scope Good for
"logger" (default) One bucket per logger name Noisy third-party libraries that flood a single logger
"level" One bucket per log level Throttle all WARNING/ERROR across the app
"global" One shared bucket App-wide safety net on the root handler
"template" One bucket per (logger, level, str(record.msg)) Shares across arg values of the same template
"rendered" One bucket per (logger, level, record.getMessage()) Distinguishes fully-rendered messages
from logging import getLogger

from grelmicro.log import RateLimitFilter

# One shared bucket across every record the handler sees.
# Useful on the root or app-level handler as a global safety net.
root = getLogger()
root.addFilter(RateLimitFilter(capacity=100, refill_rate=10, key_mode="global"))

Pass a custom key= callable for any other grouping:

logger.addFilter(
    RateLimitFilter(
        capacity=20,
        refill_rate=2,
        key=lambda r: f"{r.name}|{r.exc_info is not None}",
    )
)

Use cost= when a record should spend multiple tokens (e.g. on a verbose-level handler):

logger.addFilter(RateLimitFilter(capacity=100, refill_rate=10, cost=2))

State is in-process only, backed by MemoryTokenBucket. Call filter.reset(key) to clear one key, or construct a new filter to wipe all state.

Tip

RateLimitFilter and DuplicateFilter compose well: attach the dedup filter first to collapse true duplicates, then the rate-limit filter to cap the sustained flow.