Authoring Fapilog Plugins
This guide covers entry points, required metadata, and the Plugin API versioning policy.
Entry Points
Declare entry points in pyproject.toml under one of the v3 groups per plugin type:
[project.entry-points."fapilog.sinks"]
"my_sink" = "my_package.my_sink"
[project.entry-points."fapilog.processors"]
"my_processor" = "my_package.my_processor"
[project.entry-points."fapilog.enrichers"]
"my_enricher" = "my_package.my_enricher"
[project.entry-points."fapilog.redactors"]
"my_redactor" = "my_package.my_redactor"
[project.entry-points."fapilog.filters"]
"my_filter" = "my_package.my_filter"
# Fallback generic group (type derived from PLUGIN_METADATA["plugin_type"]) when needed
[project.entry-points."fapilog.plugins"]
"legacy-plugin" = "my_package.legacy"
Naming Convention
Use lowercase with underscores (snake_case):
field_mask,rotating_file,runtime_info.Avoid type suffixes in names (
-sink,-processor, etc.); theplugin_typefield already captures that.The class
nameattribute andPLUGIN_METADATA["name"]must match.Hyphen variants are normalized automatically for compatibility, but prefer snake_case in docs/config.
PLUGIN_METADATA
Each module must export a PLUGIN_METADATA mapping with at least:
PLUGIN_METADATA = {
"name": "my_plugin",
"version": "1.2.3",
"plugin_type": "sink", # sink|processor|enricher|redactor|filter
"entry_point": "my_package.my_sink:Plugin",
"description": "...",
"author": "Your Name",
"compatibility": {"min_fapilog_version": "3.0.0"},
"api_version": "1.0", # Plugin API contract version
# Optional configuration docs
"config_schema": {...},
"default_config": {...},
}
Valid plugin types:
sink- Output destinations for log entriesprocessor- Transform serialized bytesenricher- Add fields to log eventsredactor- Sanitize sensitive datafilter- Drop or transform events before enrichment
API Versioning
Current API contract is defined at
fapilog.plugins.versioning.PLUGIN_API_VERSIONas a tuple (e.g.,(1, 0)).Plugins declare their version in
PLUGIN_METADATA["api_version"]as a string (e.g.,"1.0").Policy: compatible when declared major matches current major, and declared minor is less than or equal to current minor.
Utilities:
parse_api_version("1.0") -> (1, 0)andis_plugin_api_compatible((major, minor)) -> bool.
Protocols
Author implementations should satisfy the runtime-checkable Protocol for their type:
from fapilog.plugins import (
BaseSink,
BaseProcessor,
BaseEnricher,
BaseRedactor,
BaseFilter,
)
All interfaces are async-first and must contain errors rather than raising into the core pipeline.
Configuration Pattern
Use Pydantic v2 models for plugin configuration to get validation, coercion, and typo protection for free.
from pydantic import BaseModel, ConfigDict, Field
class MyPluginConfig(BaseModel):
model_config = ConfigDict(
frozen=True,
extra="forbid",
validate_default=True,
)
threshold: int = Field(default=100, ge=1)
enabled: bool = True
Parse configs with the shared helper so plugins accept config objects, dicts, kwargs, or loader-style nested {"config": {...}}:
from fapilog.plugins import parse_plugin_config
class MyPlugin:
name = "my_plugin"
def __init__(
self,
*,
config: MyPluginConfig | dict | None = None,
**kwargs: object,
) -> None:
cfg = parse_plugin_config(MyPluginConfig, config, **kwargs)
self._threshold = cfg.threshold
self._enabled = cfg.enabled
Supported inputs:
Config object:
MyPlugin(config=MyPluginConfig(threshold=50))Dict:
MyPlugin(config={"threshold": 50})Kwargs:
MyPlugin(threshold=50)Loader format:
MyPlugin(config={"config": {"threshold": 50}})
Validation behavior:
String numbers coerce to native types (
{"threshold": "5"}->5)Unknown keys raise
ValidationError(catches typos)Bounds in
Field(...)are enforced (e.g.,ge=0,le=1)