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.
// ✅ Grouped — clear, and flattens to flowRun.id / flow.id
logger.info({
  flowRun: { id: run.id, status: run.status },
  flow: { id: run.flowId },
  project: { id: run.projectId },
}, 'Flow run started')

// ❌ Flat prefixes, alias, and a bare id
logger.info({ runId: run.id, flowRunId: run.id, id: run.id }, 'Flow run started')
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

GroupFieldsEntity
flowRunid, status, environmentA flow execution
flowid, versionA flow definition
flowVersionidA flow version
projectidA project
platformidA platform / workspace
useridA user
jobid, typeA queue job
piecename, versionAn integration piece
connectionidAn app connection
sandboxidAn execution sandbox
workeridA worker process
webhookid, requestId, mode, flowFound, responseStatusA webhook request
conversationidA chat conversation
waitpointidA pause / resume point
stepnameA flow step
triggernameA flow trigger
migrationnameA database migration
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 }.