Canonical Event¶
The CanonicalEvent is the single schema contract shared by every service in the pipeline. All sources (Jira, Prometheus, Azure DevOps, webhooks, uploaded files) are normalized into this shape before entering Kafka.
Why a canonical shape?¶
| Problem | Solution |
|---|---|
| Each source emits a different format | Single Pydantic model in services/libs/common |
| New connectors are expensive to add | Connectors only map → canonical, pipeline is untouched |
| Breaking schema changes break consumers | schema_version field + backward-compatible validation |
Structure¶
# services/libs/common/schemas/canonical_event.py
class Environment(str, Enum):
PROD = "prod"; PRODUCTION = "production"
STAGING = "staging"; DEV = "dev"
class EventType(str, Enum):
LOG = "log"; METRIC = "metric"
TRACE = "trace"; EVENT = "event"
class Severity(str, Enum):
DEBUG = "DEBUG"; INFO = "INFO"; WARNING = "WARNING"
ERROR = "ERROR"; CRITICAL = "CRITICAL"
class CanonicalEvent(BaseModel):
schema_version: int = 1
timestamp: datetime
source: str = Field(..., min_length=1)
environment: Environment
type: EventType
severity: Severity
message: str = Field(..., min_length=1)
summary: str | None = None
template_id: int | None = None # filled after Drain3
host: str | None = None
pod_name: str | None = None
trace_id: str | None = None
span_id: str | None = None
occurrence_count: int = Field(default=1, ge=1)
tags: dict = Field(default_factory=dict)
dedup_key: str | None = None
# model_config: use_enum_values=True, extra="forbid"
Enrichment fields (
severity_rank,is_error_or_worse,priority,priority_confidence,priority_source,model_version) are not part of the canonical contract — they are added by downstream pipeline stages before storage.
Field Conventions¶
| Field | Convention |
|---|---|
timestamp |
ISO 8601 UTC; source timezone converted to UTC at ingest |
severity |
Uppercase enum; mapped from source-specific levels |
type |
One of log / metric / trace / event |
message |
PII-stripped before any downstream processing |
tags |
Free-form JSONB; reserved keys prefixed with correction_ |
dedup_key |
Populated by the Deduplicator |
template_id |
int, filled after Drain3 parsing |
Lifecycle¶
flowchart LR
RAW[Raw event<br/>source format] --> MAP[Connector map]
MAP --> NORM[Normalizer<br/>Pydantic validation + PII]
NORM --> CANON[(CanonicalEvent)]
CANON --> KAFKA[Kafka topic]
KAFKA --> PIPE[Pipeline stages]
PIPE --> PG[(events table)]
- Ingest — connector maps raw fields to canonical fields
- Validate —
CanonicalEventmodel rejects malformed events → DLQ - Normalize — PII stripping, severity mapping, defaults applied
- Enrich — dedup key, template_id, priority added by later stages
- Persist — stored as a row in the
eventstable
Example¶
Validation Failures¶
Events that fail validation are routed to the DLQ (Kafka dlq topic + dlq_events table) with a reason explaining the failure — they are never silently dropped.
Related¶
- Normalizer — where normalization happens
- Database Schema — how it maps to SQL