Skip to content

Filter Features

Logly provides multiple filtering mechanisms to control which log records reach each sink. Filters are applied per-sink and can be combined for complex routing logic.

Level Filtering

The simplest filter is the level parameter on add(). Only records at or above the specified level are dispatched to the sink.

python
from logly import logger

# Only WARNING and above
logger.add("warnings.log", level="WARNING")

# All levels (TRACE and above)
logger.add("debug.log", level="TRACE")

# Only ERROR and above
logger.add("errors.log", level="ERROR")

# Only CRITICAL and FATAL
logger.add("critical.log", level="CRITICAL")

Level Hierarchy

LevelNumeric
TRACE5
DEBUG10
INFO20
NOTICE25
SUCCESS30
WARNING40
ERROR50
FAIL55
CRITICAL60
FATAL70
python
from logly import logger

messages = []

def capture(msg: str) -> None:
    messages.append(msg)

# Only WARNING and above pass through
sink_id = logger.add(capture, level="WARNING")
logger.info("filtered out")
logger.warning("passes through")
logger.error("passes through")
logger.remove(sink_id)

print(len(messages))  # 2

Prefix Filtering

Filter by logger name prefix using a string value:

python
from logly import logger

# Only records from loggers starting with "myapp."
logger.add("myapp.log", filter={"name": "myapp."})

# Create a logger with the matching name
myapp = logger.bind(name="myapp.core")
myapp.info("This appears in myapp.log")

Multiple Prefix Filters

python
from logly import logger

# Route different modules to different files
logger.add("auth.log", filter={"name": "auth."})
logger.add("db.log", filter={"name": "database."})
logger.add("http.log", filter={"name": "http."})

# Usage
logger.bind(name="auth.login").info("User logged in")      # -> auth.log
logger.bind(name="database.query").info("SELECT *")        # -> db.log
logger.bind(name="http.request").info("GET /api/users")    # -> http.log

Callable Filtering

Custom filter functions receive the record dict and return True to allow or False to reject.

Signature

python
def my_filter(record: dict[str, object]) -> bool:
    level = record.get("level", "")
    message = record.get("message", "")
    extra = record.get("extra", {})
    # Return True to allow, False to reject
    return True

Filter by Level

python
from logly import logger

def only_errors(record: dict[str, object]) -> bool:
    return record.get("level") in {"ERROR", "CRITICAL", "FAIL"}

logger.add("errors.log", filter=only_errors)

Filter by Message Content

python
from logly import logger

def important_messages(record: dict[str, object]) -> bool:
    msg = str(record.get("message", "")).lower()
    return "critical" in msg or "urgent" in msg

logger.add("important.log", filter=important_messages)

Filter by Extra Fields

python
from logly import logger

def production_only(record: dict[str, object]) -> bool:
    extra = record.get("extra", {})
    return extra.get("env") == "production"

logger.add("prod.log", filter=production_only)

Filter by Module

python
from logly import logger

def module_filter(record: dict[str, object]) -> bool:
    module = record.get("module", "")
    return module in {"auth", "security"}

logger.add("security.log", filter=module_filter)

Block All Records

python
from logly import logger

def block_all(record: dict[str, object]) -> bool:
    return False

logger.add("disabled.log", filter=block_all)

Dictionary/Mapping Filtering

Filter by matching extra field values in a dictionary.

Single Field

python
from logly import logger

# Only records with extra["channel"] == "audit"
logger.add("audit.log", filter={"channel": "audit"})

# Usage
logger.bind(channel="audit").info("Permission changed")  # -> audit.log
logger.bind(channel="access").info("Page viewed")        # Not routed

Multiple Fields (AND Logic)

python
from logly import logger

# All fields must match
logger.add(
    "filtered.log",
    filter={"env": "production", "service": "api"},
)

# Usage
logger.bind(env="production", service="api").info("Request processed")   # -> filtered.log
logger.bind(env="production", service="web").info("Page rendered")       # Not routed
logger.bind(env="staging", service="api").info("Test request")          # Not routed

Mapping Filter with Patch

python
from logly import logger

def add_context(record: dict[str, object]) -> None:
    record.setdefault("extra", {})["service"] = "api"
    record.setdefault("extra", {})["env"] = "production"

sink_id = logger.add(
    "api.log",
    filter={"service": "api"},
    patch=add_context,
)

Combining Filters

Level + Callable Filter

python
from logly import logger

# Only production errors
logger.add(
    "production-errors.log",
    level="ERROR",
    filter=lambda r: r.get("extra", {}).get("env") == "production",
)

Level + Mapping Filter

python
from logly import logger

# API warnings and above
logger.add(
    "api-warnings.log",
    level="WARNING",
    filter={"service": "api"},
)

Multiple Filters on Different Sinks

python
from logly import logger

# All messages at DEBUG
logger.add("all.log", level="DEBUG")

# Only errors
logger.add("errors.log", level="ERROR")

# Only API service logs
logger.add("api.log", filter={"service": "api"})

# Important warnings
logger.add(
    "important.log",
    level="WARNING",
    filter=lambda r: "critical" in r.get("message", "").lower(),
)

Level + Mapping + Callable

python
from logly import logger

def complex_filter(record: dict[str, object]) -> bool:
    extra = record.get("extra", {})
    # Allow production API errors or any critical message
    if extra.get("env") == "production" and extra.get("service") == "api":
        return True
    if record.get("level") in {"CRITICAL", "FATAL"}:
        return True
    return False

logger.add(
    "complex.log",
    level="ERROR",
    filter=complex_filter,
)

Advanced Filtering Patterns

Module-Based Routing

python
from logly import logger

# Route by module name
logger.add("auth.log", filter={"name": "auth."})
logger.add("db.log", filter={"name": "database."})
logger.add("http.log", filter={"name": "http."})
logger.add("cache.log", filter={"name": "cache."})

# Usage
logger.bind(name="auth.core").info("Login successful")       # -> auth.log
logger.bind(name="database.pool").info("Connection acquired") # -> db.log
logger.bind(name="http.server").info("Request handled")       # -> http.log
logger.bind(name="cache.redis").info("Cache hit")             # -> cache.log

Extra-Based Routing

python
from logly import logger

# Route by extra fields
logger.add("requests.log", filter={"type": "request"})
logger.add("background.log", filter={"type": "background"})
logger.add("audit.log", filter={"channel": "audit"})

# Usage
logger.bind(type="request").info("GET /api")           # -> requests.log
logger.bind(type="background").info("Job completed")   # -> background.log
logger.bind(channel="audit").info("User modified")     # -> audit.log

Message Content Routing

python
from logly import logger

def security_filter(record: dict[str, object]) -> bool:
    msg = str(record.get("message", "")).lower()
    return any(word in msg for word in ["unauthorized", "forbidden", "breach", "intrusion"])

def performance_filter(record: dict[str, object]) -> bool:
    msg = str(record.get("message", "")).lower()
    return any(word in msg for word in ["slow query", "timeout", "latency", "memory"])

logger.add("security.log", filter=security_filter)
logger.add("performance.log", filter=performance_filter)

Combined Pattern

python
from logly import logger

# Complex filter combining multiple criteria
def production_api_errors(record: dict[str, object]) -> bool:
    level = record.get("level", "")
    extra = record.get("extra", {})
    message = str(record.get("message", "")).lower()

    # Must be production environment
    if extra.get("env") != "production":
        return False

    # Must be API service
    if extra.get("service") != "api":
        return False

    # Must be error or above, OR contain "critical"
    if level in {"ERROR", "CRITICAL", "FAIL"}:
        return True
    if "critical" in message:
        return True

    return False

logger.add("prod-api-errors.log", filter=production_api_errors)

Performance Notes

INFO

  • Level filtering is the fastest filter type - checked first and short-circuits
  • Prefix filtering is fast - simple string comparison
  • Mapping filtering is fast - dictionary key lookup
  • Callable filtering is slower - invokes Python function per record
  • For high-throughput logging, prefer level and mapping filters over callable filters

High-Performance Setup

python
from logly import logger

# Fast: level + mapping filters
logger.add(
    "production.log",
    level="INFO",           # Fast level check
    filter={"env": "prod"}, # Fast mapping check
)

# Slower: callable filter (use only when needed)
logger.add(
    "custom.log",
    filter=lambda r: "important" in r.get("message", ""),
)

Filter Evaluation Order

Filters are evaluated in this order:

  1. Level filter (level parameter) - checked first
  2. Prefix/Mapping filter (filter parameter) - checked if level passes
  3. Callable filter (filter parameter) - checked last
python
from logly import logger

# Evaluation order:
# 1. level="ERROR" (fast check)
# 2. filter={"service": "api"} (fast check)
# 3. callable filter (slower check)
logger.add(
    "filtered.log",
    level="ERROR",
    filter={"service": "api"},
    filter=lambda r: "timeout" in r.get("message", ""),
)

Complete Example

python
from logly import logger

# Route by module
logger.add("auth.log", filter={"name": "auth."})
logger.add("db.log", filter={"name": "database."})
logger.add("http.log", filter={"name": "http."})

# Route by severity
logger.add("stdout", level="INFO")
logger.add("stderr", level="ERROR")
logger.add("debug.log", level="TRACE")

# Route by context
logger.add("requests.log", filter={"type": "request"})
logger.add("background.log", filter={"type": "background"})

# Complex filter
def critical_filter(record: dict[str, object]) -> bool:
    level = record.get("level", "")
    extra = record.get("extra", {})
    return (
        level in {"ERROR", "CRITICAL", "FAIL"}
        or extra.get("priority") == "critical"
    )

logger.add("critical.log", filter=critical_filter)

# Production-only errors with callable
logger.add(
    "prod-errors.log",
    level="ERROR",
    filter=lambda r: r.get("extra", {}).get("env") == "production",
)

# Usage examples
logger.bind(name="auth.core").info("Login successful")                    # -> auth.log, stdout
logger.bind(name="database.pool").error("Connection failed")              # -> db.log, stderr
logger.bind(name="http.server").info("GET /api")                          # -> http.log, stdout
logger.bind(type="request").info("Processing request")                    # -> requests.log
logger.bind(env="production").error("Service down")                       # -> prod-errors.log, stderr
logger.opt(exception=True).error("Critical failure")                      # -> critical.log, stderr

logger.complete()

Released under the MIT License.