log0
Architecture

Kafka Event Contracts (AsyncAPI)

AsyncAPI specification for all Kafka topics in the log0 pipeline - raw-logs, normalized-logs, incident-events, notification-events, and raw-logs-dlq. Every message schema, producer, and consumer documented in one place.

What This Page Covers

log0's pipeline is entirely event-driven. Every stage communicates by publishing and consuming Kafka messages - there are no direct service-to-service HTTP calls in the core pipeline.

This page documents the contracts for all five Kafka topics: what flows through each topic, who produces it, who consumes it, and the exact schema of every message.

The contracts are maintained as an AsyncAPI specification - the event-driven equivalent of OpenAPI/Swagger - in docs/asyncapi/asyncapi.yaml in the log0-services repository.


Interactive Docs

The full AsyncAPI spec renders as an interactive docs page. Open it to browse all topics, expand message schemas, and see field-level descriptions and examples:


Pipeline Overview

ingestion-gateway
      │  publishes
      ▼
  raw-logs ──────────────────────────────────────────┐
      │  consumed by                                  │ on failure
      ▼                                               ▼
normalization-service                           raw-logs-dlq
      │  publishes
      ▼
  normalized-logs
      │  consumed by
      ▼
clustering-service
      │  publishes (when threshold crossed)
      ▼
  incident-events
      │  consumed by
      ▼
incident-service
      │  publishes (on lifecycle transitions)
      ▼
  notification-events
      │  consumed by
      ▼
notification-service
      │  sends
      ▼
   Slack

Every service that consumes from Kafka uses manual offset acknowledgment - the offset is only committed after the message is successfully processed. On failure, the message is forwarded to raw-logs-dlq before the offset is committed, so no event is silently lost.


Topics at a Glance

TopicProducerConsumerKafka KeyMessage
raw-logsingestion-gatewaynormalization-servicetenantIdRawLogEvent
normalized-logsnormalization-serviceclustering-servicetenantIdNormalizedLogEvent
incident-eventsclustering-serviceincident-servicetenantIdIncidentEvent
notification-eventsincident-servicenotification-servicetenantIdNotificationEvent
raw-logs-dlqall services(future DLQ monitor)eventIdDlqEvent

All topics use tenantId as the Kafka partition key - this guarantees that all events for a given tenant land on the same partition and are consumed in order.


Message Schemas (Summary)

RawLogEvent

Published by ingestion-gateway to raw-logs after a POST /api/v1/logs request passes validation.

FieldTypeRequiredDescription
eventIdUUID✅Platform-assigned unique ID for this log event
tenantIdUUID✅Tenant that owns this event. Partition key.
serviceNamestring✅Application service that generated the log
environmentstring✅Deployment environment (production, staging, dev)
receivedAtdatetime✅When the ingestion-gateway received the request
logTimestampdatetime⬜Timestamp from the original log payload (nullable)
levelstring✅Raw log level as sent by the client
messagestring✅Log message body (max 10,000 chars)
tracestring⬜Stack trace or trace context (max 100,000 chars)

NormalizedLogEvent

Published by normalization-service to normalized-logs. Adds a deterministic SHA-256 fingerprint used for deduplication.

FieldTypeRequiredDescription
eventIdUUID✅Carried through from RawLogEvent
tenantIdUUID✅Partition key
serviceNamestring✅
environmentstring✅
timestampdatetime✅logTimestamp if present, else receivedAt
levelstring✅Normalized (uppercased, defaults to INFO)
messagestring✅Trimmed log message
messageTemplatestring✅Dynamic values replaced: UUIDs → <uuid>, IPs → <ip>, numbers → <number>
fingerprintstring✅SHA-256 of service|messageTemplate|exceptionType|firstStackFrame
attributesobject⬜Structured attributes (reserved for future enrichment)
traceIdstring⬜Distributed trace ID for APM correlation
schemaVersionstring✅Always "v1" currently

IncidentEvent

Published by clustering-service to incident-events when a fingerprint's occurrence count crosses the configured threshold within a 5-minute tumbling window.

FieldTypeRequiredDescription
tenantIdUUID✅Partition key
fingerprintstring✅SHA-256 fingerprint - deduplication key in incident-service
serviceNamestring✅
environmentstring✅
severityenum✅HIGH (ERROR/FATAL), MEDIUM (WARN), LOW (INFO/DEBUG)
occurrenceCountinteger✅Number of matching events in the current window
firstSeenAtdatetime✅First event in the window
lastSeenAtdatetime✅Most recent event in the window
topMessagesstring[]✅Up to 10 distinct messages (used as AI prompt context)

NotificationEvent

Published by incident-service to notification-events on incident lifecycle transitions. Consumed by notification-service to send Slack alerts.

FieldTypeRequiredDescription
tenantIdUUID✅Partition key
incidentIdUUID✅PostgreSQL ID of the incident
fingerprintstring✅
serviceNamestring✅
environmentstring✅
severityenum✅HIGH, MEDIUM, LOW
statusenum✅NEW, ASSIGNED, ACKNOWLEDGED, RESOLVED
occurrenceCountinteger✅
firstSeenAtdatetime✅
lastSeenAtdatetime✅
topMessagesstring[]✅
aiSummarystring⬜AI-generated summary. Null on INCIDENT_CREATED (async).
notificationTypeenum✅INCIDENT_CREATED, INCIDENT_ASSIGNED, INCIDENT_RESOLVED
assignedToUserIdUUID⬜Set only when notificationType is INCIDENT_ASSIGNED

DlqEvent

Published to raw-logs-dlq by any service that catches a processing failure. Preserves the original event with error context.

FieldTypeRequiredDescription
originalEventobject✅The failed event payload (type varies by publisher)
errorMessagestring✅Exception message or cause
failedAtstring✅Name of the service that caught the error
failedAtTsdatetime✅When the failure occurred

failedAt values: ingestion-gateway, normalization-service, clustering-service, incident-service, notification-service


Keeping the Spec Up to Date

The source file lives at docs/asyncapi/asyncapi.yaml in the log0-services repository.

When a Kafka event class changes, update asyncapi.yaml in the same PR. Then regenerate the HTML docs.

Step 1 - Install the AsyncAPI CLI (once)

npm install -g @asyncapi/cli
npm install -g @asyncapi/cli
npm install -g @asyncapi/cli

Step 2 - Regenerate the HTML docs

Run this from the root of the log0-services repository:

asyncapi generate fromTemplate docs/asyncapi/asyncapi.yaml \
  @asyncapi/html-template \
  -o <path-to-log0-website>/public/asyncapi \
  --force-write
asyncapi generate fromTemplate docs/asyncapi/asyncapi.yaml `
  @asyncapi/html-template `
  -o <path-to-log0-website>\public\asyncapi `
  --force-write
asyncapi generate fromTemplate docs/asyncapi/asyncapi.yaml @asyncapi/html-template -o <path-to-log0-website>\public\asyncapi --force-write

Replace <path-to-log0-website> with the absolute path to your local log0-website clone.

Step 3 - Commit the output

Commit the regenerated files in log0-website/public/asyncapi/ as a follow-up to the spec change PR.

How is this guide?

On this page