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
eventIdUUIDPlatform-assigned unique ID for this log event
tenantIdUUIDTenant that owns this event. Partition key.
serviceNamestringApplication service that generated the log
environmentstringDeployment environment (production, staging, dev)
receivedAtdatetimeWhen the ingestion-gateway received the request
logTimestampdatetimeTimestamp from the original log payload (nullable)
levelstringRaw log level as sent by the client
messagestringLog message body (max 10,000 chars)
tracestringStack 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
eventIdUUIDCarried through from RawLogEvent
tenantIdUUIDPartition key
serviceNamestring
environmentstring
timestampdatetimelogTimestamp if present, else receivedAt
levelstringNormalized (uppercased, defaults to INFO)
messagestringTrimmed log message
messageTemplatestringDynamic values replaced: UUIDs → <uuid>, IPs → <ip>, numbers → <number>
fingerprintstringSHA-256 of service|messageTemplate|exceptionType|firstStackFrame
attributesobjectStructured attributes (reserved for future enrichment)
traceIdstringDistributed trace ID for APM correlation
schemaVersionstringAlways "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
tenantIdUUIDPartition key
fingerprintstringSHA-256 fingerprint - deduplication key in incident-service
serviceNamestring
environmentstring
severityenumHIGH (ERROR/FATAL), MEDIUM (WARN), LOW (INFO/DEBUG)
occurrenceCountintegerNumber of matching events in the current window
firstSeenAtdatetimeFirst event in the window
lastSeenAtdatetimeMost 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
tenantIdUUIDPartition key
incidentIdUUIDPostgreSQL ID of the incident
fingerprintstring
serviceNamestring
environmentstring
severityenumHIGH, MEDIUM, LOW
statusenumNEW, ASSIGNED, ACKNOWLEDGED, RESOLVED
occurrenceCountinteger
firstSeenAtdatetime
lastSeenAtdatetime
topMessagesstring[]
aiSummarystringAI-generated summary. Null on INCIDENT_CREATED (async).
notificationTypeenumINCIDENT_CREATED, INCIDENT_ASSIGNED, INCIDENT_RESOLVED
assignedToUserIdUUIDSet 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
originalEventobjectThe failed event payload (type varies by publisher)
errorMessagestringException message or cause
failedAtstringName of the service that caught the error
failedAtTsdatetimeWhen 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