Skip to main content
All structured logging on the server goes through evloglogger.{info,warn,error,debug}({ fields }, msg) and wideEvent.set/error/timed from @activepieces/server-utils. The field keys (not the message string) are the real schema — they are what dashboards, alerts, and the OTLP drain query against. If the same thing is logged under different keys (runId here, flowRunId there, a bare id elsewhere), every query becomes a guessing game and correlation silently breaks. One rule above all: one concept = one field, everywhere.

Group fields by entity

Following evlog’s guidance, group related fields under the entity they belong to instead of flat, prefixed keys. An entity’s own id is just id inside its group.
The group name is the camelCase singular entity from the domain model. The id lives at entity.id, and the entity’s attributes sit beside it in the same group.

Rules

1

The id is `id`, inside its entity group

Never a top-level flowRunId, runId, or bare id. A flow run is flowRun: { id }, a flow is flow: { id }. This gives exactly one queryable path per entity.
2

Attributes live beside the id, merged into one group

{ jobId, jobType } becomes job: { id, type }; { pieceName, pieceVersion } becomes piece: { name, version }. Never a bare name / version / status / type at the top level — they’re meaningless without their entity.
3

Errors use `error`

Pass the error straight to evlog — logger.error(err) or logger.error({ err }, 'msg') — and it lands under the canonical error key. Descriptive error fields like migrationError are fine.
4

Units go in the suffix

Durations end in Ms (durationMs, timings.{op}Ms), bytes in Bytes, counts in Count or a plural.
5

Keep it shallow, and leave reserved fields flat

One level of grouping (two max). requestId, service, version, level, msg, timestamp, error, method, path are added automatically — never set them, and never fold requestId into a group.

Groups

This convention governs the keys in logging calls only, not entity or DTO field names. Data-model fields such as JobData.runId, DB query arguments, and service-call arguments stay as-is — the value is simply logged under flowRun: { id }.