Skip to main content
Version: 3.1

Logging and Debugging

When a Reaktor does not behave as expected, you need visibility into what happened. Operaide provides two complementary tools: structured logging through getLogger for recording events in code, and Reaktor-Trace for visually inspecting each step of an execution. Every log entry is automatically correlated with the current Reaktor run — no extra wiring required.

Getting a Logger

Call getLogger at the top of your module, outside any function. The name you pass becomes the aktorId field in each log entry, making it easy to filter logs by component.

import { getLogger } from '@operaide/aktor';

const logger = getLogger('myAktor');

Log Levels

The logger exposes four methods, from least to most severe:

logger.debug('Processing input', { input });            // verbose, development detail
logger.info('Request completed', { result }); // normal operations
logger.warn('Retrying after timeout', { attempt }); // unexpected but recoverable
logger.error('Database call failed', error, { query }); // failures
LevelMethodWhen to use
debuglogger.debug(message, context?)Detailed diagnostic output, disabled in production by default
infologger.info(message, context?)Normal operational events (started, completed, counts)
warnlogger.warn(message, context?)Unexpected but recoverable situations
errorlogger.error(message, error?, context?)Failures that need attention

The error method accepts an optional Error object as its second argument, followed by an optional context object. A complete example:

const logger = getLogger('aktorProcessDocument');

const aktorProcessDocument = createAktorFunction(
'aktorProcessDocument',
async ({ documentId }: { documentId: string }) => {
logger.debug('Starting document processing', { documentId });

const content = await fetchDocument(documentId);
logger.info('Document fetched', { documentId, length: content.length });

if (content.length === 0) {
logger.warn('Empty document received', { documentId });
return { status: 'skipped' };
}

try {
const result = await analyze(content);
logger.info('Analysis complete', { documentId, resultSize: result.length });
return result;
} catch (error) {
logger.error('Analysis failed', error, { documentId });
throw error;
}
}
);

Automatic Context

Every log entry automatically includes:

FieldSourceDescription
aktorIdLogger nameThe name passed to getLogger()
runIdAsyncLocalStorageUnique ID for the current Reaktor execution
reaktorIdAsyncLocalStorageID of the Reaktor definition being executed

You do not need to pass these manually — they are injected by the platform. Log entries from different Aktors within the same Reaktor execution share the same runId, making it easy to correlate them.

Execution Context

Use getExecutionContext() when you need the runId or reaktorId in your own code — for example, to store them as foreign keys in a database.

import { getExecutionContext, createAktorFunction } from '@operaide/aktor';

const aktorStoreResult = createAktorFunction(
'aktorStoreResult',
async ({ client, data }: { client: any; data: string }) => {
const { runId, reaktorId } = getExecutionContext();

await client.execute(
'INSERT INTO results (run_id, reaktor_id, data) VALUES (?, ?, ?)',
[runId, reaktorId, data]
);

return { stored: true, runId };
}
);

runId and reaktorId are undefined when called outside of a Reaktor execution (e.g. during module initialization or tests).

Where Logs Appear

Logs are written to the Reaktor's execution log, accessible in Operaide Studio and on the Reaktor Instance view under Logs. Each entry shows the timestamp, level, message, and all context fields, including aktorId, reaktorId, and runId.

Platform-level events such as restarts and out-of-memory happen below the Reaktor level and never reach a run's log. They are captured separately. See Platform Logs.

Debug Mode

By default, only warn and error log entries are persisted to the database. To also persist debug and info entries, enable Debug Mode on the Reaktor Deployment.

You can toggle Debug Mode in two ways:

  • In the UI — open the Reaktor Deployment's Logs tab and flip the Debug switch in the top-right corner.
  • Via REST API — see Logs REST API below.

Debug Mode is a deployment-level setting. It takes effect on the next execution — runs that are already in progress are not affected. When Debug Mode is off, debug and info entries still appear in the server console but are not stored or shown in the Logs tab.

tip

Enable Debug Mode while developing or investigating a problem, then disable it once you are done. Keeping it on permanently increases storage usage and may expose sensitive data in the log viewer.

Viewing Logs

Open a Reaktor Deployment and click the Logs tab to browse stored log entries. The viewer provides:

  • Time range — preset ranges (last hour, 24 hours, 7 days, 30 days) or a custom date range
  • Filters — narrow by log level, logger name, run ID, or aktor ID. Filter options update dynamically based on the data.
  • Search — free-text search across message, logger name, aktor ID, and run ID
  • Pagination — server-side pagination with configurable page size (25, 50, or 100 entries)
  • Export — download the current page as a JSON file for offline analysis
  • Clear — delete stored log entries for this Reaktor Deployment. The dialog lets you restrict deletion to selected log levels, for example to remove debug noise but keep errors.

A Refresh button with a blue indicator appears when new log entries arrive after the current view was loaded.

For an app instance, the Tracing entry in the instance sidebar shows the merged logs of all its Reaktor deployments. The viewer adds a Reaktor column and filter on top of the same controls, plus a Debug dropdown that toggles debug mode per Reaktor or for all Reaktors of the instance at once. Clear Logs deletes the logs of all Reaktors of the instance, with the same per-level selection as the per-deployment dialog.

Logs REST API

You can retrieve logs programmatically using the REST API. This is useful for integrating with external monitoring tools or for automated analysis.

Fetch Logs

GET /api/v1/deployments/{deploymentId}/logs

Authentication: X-API-KEY header with a valid API key.

Query parameters:

ParameterTypeDescription
aktorIdstringFilter by aktor ID
runIdstringFilter by run ID
logLevelstringFilter by level (debug, info, warn, error)
loggerNamestringFilter by logger name
searchTextstringSearch across message, logger name, aktor ID, and run ID
fromDatestringStart of time range (ISO 8601)
toDatestringEnd of time range (ISO 8601)
limitnumberMax entries to return (default 500, max 1000)
offsetnumberNumber of entries to skip for pagination (default 0)

Response:

{
"metadata": {
"exportedAt": "2025-03-18T10:00:00.000Z",
"reaktorId": "my-deployment",
"filters": { "aktorId": null, "runId": null, "logLevel": null, "loggerName": null, "timeRange": { "from": null, "to": null } },
"count": 50,
"totalCount": 1200,
"hasMore": true,
"limit": 50,
"offset": 0
},
"logs": [
{
"loggerName": "aktorProcessDocument",
"level": "info",
"message": "Document fetched",
"createdAt": "2025-03-18T09:59:12.345Z",
"ordinal": 42,
"millisec": 123.4,
"runId": "abc123",
"reaktorId": "my-deployment",
"aktorType": "aktorProcessDocument",
"aktorId": "reaktor.processDocument",
"data": { "documentId": "doc-1", "length": 4096 }
}
]
}

Correlating Logs with API Calls

When you execute a Reaktor via the REST API, the response includes an X-Run-Id header. Use this run ID to fetch the logs for that specific execution:

# Execute a Reaktor
RUN_ID=$(curl -s -D- -X POST /api/v1/aktor/myReaktor \
-H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{"input": "value"}' | grep -i x-run-id | cut -d' ' -f2)

# Fetch logs for that run
curl /api/v1/deployments/myReaktor/logs?runId=$RUN_ID \
-H "X-API-KEY: $KEY"

Filter Options

To populate filter dropdowns or discover available filter values:

GET /api/v1/deployments/{deploymentId}/logs/filters

Accepts the same filter parameters as the logs endpoint (except limit and offset). Returns the distinct values that exist in the filtered result set:

{
"aktorIds": ["reaktor.ai", "reaktor.processDocument"],
"runIds": [{ "runId": "abc123", "startedAt": "2025-03-18T09:58:00.000Z" }],
"loggerNames": ["aktorProcessDocument", "aktorAICall"],
"logLevels": ["info", "warn", "error"]
}

App Instance Logs

An app instance exposes the merged logs of all its Reaktor deployments:

GET /api/v1/app-instances/{appInstanceId}/logs
GET /api/v1/app-instances/{appInstanceId}/logs/filters

Authentication: X-API-KEY header with a valid API key.

Both endpoints accept the same query parameters as their deployment-level counterparts. An additional optional reaktorId parameter narrows the result to a single deployment of the instance. Log entries are merged across all Reaktors and sorted by time; the response metadata carries appInstanceId instead of reaktorId.

The filters response additionally lists the deployments that produced the filtered logs:

{
"reaktors": [{ "reaktorId": "dep-1", "label": "Process Document" }],
"aktorIds": ["reaktor.ai", "reaktor.processDocument"],
"runIds": [{ "runId": "abc123", "startedAt": "2025-03-18T09:58:00.000Z" }],
"loggerNames": ["aktorProcessDocument", "aktorAICall"],
"logLevels": ["info", "warn", "error"]
}

Set Debug Mode

PUT /api/v1/deployments/{deploymentId}/debug-mode

Authentication: X-API-KEY header with a valid API key.

Request body:

{ "enabled": true }

Response:

{ "deploymentId": "my-deployment", "debugMode": true }

Set enabled to false to disable debug mode again.

To find the deployment ids of an app instance, list its reaktors:

GET /api/v2/orgs/{orgId}/apps/{instanceName}/reaktors

Each entry carries deploymentId and the current debugMode, so you can toggle debug mode for selected reaktors of an app one by one.

Clear Logs

DELETE /api/v1/deployments/{deploymentId}/logs

Authentication: X-API-KEY header with a valid API key.

Deletes all stored log entries for the given deployment. This is irreversible.

Response:

{ "deploymentId": "my-deployment", "deletedCount": 42 }

Platform Logs

Platform logs are the server process's own console output: startup, restarts, out-of-memory events, and platform errors. They are separate from Reaktor execution logs and belong to no single run.

Use them when a Reaktor trace ends abruptly or the platform restarted. Those events happen below the Reaktor level, so they never appear in a run's logs.

  • Scope: the whole server process output, across all organizations.
  • Permission: the Log Reader role, or System Administrator.
  • Access: REST only. On disk the files are root-only, so the API is the single read path.
  • Size and secrets: the on-disk log is capped and oldest entries roll off first. Credentials are redacted before any line is written, the same redaction used for the console.

List and Tail

GET /api/v1/server-logs

Authentication: X-API-KEY header with a valid API key.

Permission: Log Reader or System Administrator.

Query parameters:

ParameterTypeDescription
tailnumberNumber of trailing lines to return (default 200, max 5000)
filestringName of a specific log file to tail (default: the newest one)

Response:

{
"dir": "/data/logs/server",
"files": [
{ "name": "@400000006a26f4c8210e1ebc.s", "sizeBytes": 102394, "modifiedAt": "2026-06-08T16:58:24.540Z" },
{ "name": "current", "sizeBytes": 13942, "modifiedAt": "2026-06-08T17:13:32.426Z" }
],
"tail": {
"file": "current",
"lines": [
"Container output is being persisted to /data/logs/server (capped ~50MB, root-only)",
"process restart detected after out-of-memory"
]
}
}

Each rotated file carries an opaque storage name. Read modifiedAt for its time and name to download it. current is the active file.

Read a Line Range

GET /api/v1/server-logs/lines?name={name}

Authentication: X-API-KEY header with a valid API key.

Permission: Log Reader or System Administrator.

Reads a file by line. Pass offset to page from the start, or tail to read from the end of a file that is still being written. The response always carries totalLines.

Query parameters:

ParameterTypeDescription
namestringThe log file to read, from the files list (required)
offsetnumberFirst line to return, 0-based (default 0)
limitnumberNumber of lines to return (default 1000, max 10000)
tailnumberRead the last N lines instead of paging from offset (max 5000)

Response:

{
"name": "current",
"totalLines": 8421,
"offset": 8221,
"limit": 200,
"lines": ["...", "..."]
}

To follow a file that is still being written, read it with tail, remember totalLines, then poll again with offset set to that value. Each poll returns only the lines appended since.

Download a Log File

GET /api/v1/server-logs/file?name={name}

Authentication: X-API-KEY header with a valid API key.

Permission: Log Reader or System Administrator.

Streams one log file verbatim as a plain-text download. Pass a name from the files list. An invalid or unknown name returns 400.

Controlling Trace Size

When Debug Mode is on, every Aktor input and output is stored in the trace. Values containing base64 data (images, PDFs, audio) or very long strings can make trace documents huge — and MongoDB refuses documents over 16 MB. Operaide automatically truncates such values before storing them.

Defaults

Value kindLimitWhat is kept
Regular strings100,000First and last 300 characters
Binary strings (base64, data URIs)1,000First and last 100 characters
Arrays1,000 itemsFirst and last 5 items
Byte arrays (numeric-key objects)1,000 bytesFirst and last 100 bytes

Binary content is detected automatically by looking at character composition, so a long base64 string is truncated more aggressively than a long regular string. Truncated values are replaced with a marker that preserves the original length and kind so you can still see that a value was present.

Secret Redaction

In addition to length-based truncation, Operaide also redacts anything that looks like a credential before it is stored in the trace or log collection. Three strategies run on every value that is about to be persisted:

  1. Field name — if the surrounding object key is apiKey, password, token, authorization, credentials, privateKey, accessToken, refreshToken, clientSecret, jwt, or any case-insensitive variant, the string value is replaced with a marker.
  2. Known token prefixes — values starting with sk-, operaide_, ghp_, xoxb-, eyJ (JWT), and similar well-known prefixes are treated as secrets.
  3. Per-token entropy — strings that contain high-entropy substrings (≥ 20 characters with Shannon entropy ≥ 4.5 bits per character) have those substrings replaced with [REDACTED:N] placeholders. This catches tokens embedded in URLs, connection strings, and free-form log messages.

This applies to both the Aktor state trace (Aktor inputs and outputs) and any data you pass to getLogger().info(…, data). Secrets never reach the log collection, so you can safely hand out log-read permissions to developers without leaking credentials.

tip

Secret redaction is a defence in depth, not a substitute for good hygiene. Do not rely on it to hide sensitive business data — the entropy check only catches credential-shaped strings, not personal names, addresses, or document content.

Customising Limits

Create a Reaktor Run Tracing connection with name default. Settings → Connections → New → Reaktor Run Tracing. The config accepts:

FieldPurpose
excludeAktorsSkip tracing for specific aktor names (array of strings)
maxStringLengthOverride the regular-string limit
maxBinaryLengthOverride the binary-string limit
maxArrayLengthOverride the array-length limit
captureStreamEventsRecord per-chunk streaming deltas. Off by default

If no connection is configured, the defaults above apply automatically. You can also create this connection via the Connection REST API — see Connection REST API below.

A streamed Aktor (an AI call rendered token by token) emits one trace event per token. That can be thousands of events per run, which buries the step-level trace. captureStreamEvents is off by default, so the trace records each Aktor's final value but not its intermediate tokens. Turn it on only to inspect streaming behaviour, then turn it back off.

Connection REST API

Connections store credentials and configuration for external services (LLM providers, OCR, databases, trace settings, …). They can be managed programmatically so coding agents can set up integrations without opening the UI.

note

Responses from the Connection REST API pass through the same secret redaction described above. Fields like apiKey or password come back as markers rather than plaintext. This is intentional: the REST API is designed to be scriptable, and scripts tend to leak into logs and transcripts. If you need the raw credential, manage it through the UI.

List Connection Types

GET /api/v1/connection-types

Returns all registered connection types with their JSON schemas, so you can discover what config a given type expects.

GET /api/v1/connection-types/{type}

Returns a single type.

List Connections

GET /api/v1/connections

Lists all connections in the authenticated organisation.

Response:

{
"connections": [
{
"type": "aktor-run-tracing",
"name": "default",
"description": null,
"config": { "maxBinaryLength": 500 },
"createdAt": "2026-04-09T10:00:00.000Z",
"updatedAt": "2026-04-09T10:00:00.000Z"
}
]
}

Get a Connection

GET /api/v1/connections/{type}/{name}

Create or Update a Connection

POST /api/v1/connections

Request body:

{
"type": "aktor-run-tracing",
"name": "default",
"config": {
"maxBinaryLength": 500,
"maxStringLength": 5000,
"excludeAktors": ["aktorPdfProcessor"]
},
"description": "Aggressive truncation for debugging"
}

The config is validated against the type's schema. If a connection with the same type + name already exists it is updated, otherwise a new one is created. Returns 201 Created on creation, 200 OK on update.

Delete a Connection

DELETE /api/v1/connections/{type}/{name}

Permissions: listing, reading, and type discovery require permToReadConnections (role App Developer or Credential Manager). Creating, updating, and deleting require permToWriteConnections / permToDeleteConnections (role Credential Manager).

Reaktor-Trace

While logging gives you a textual record of what your code did, Reaktor-Trace shows you the full execution as a visual step-by-step tree — useful when you want to understand the flow without adding log statements. Reaktor-Trace is the visual execution monitor built into the Operaide UI. When you run a Reaktor, the platform records every Aktor execution — inputs, outputs, duration, and errors — and displays them as a step-by-step tree.

To access Reaktor-Trace:

  1. Navigate to the Reaktor in the Operaide Studio
  2. Run the Reaktor or open a past execution
  3. Click on the execution to see the trace view

Each node in the trace shows:

  • Aktor name and ID — which step ran
  • Input values — what parameters were passed in
  • Output value — what the Aktor returned
  • Duration — how long the step took
  • Errors — if the step failed, the error message and stack trace

Reaktor-Trace is especially useful for debugging:

  • Tool call loops — see which tools the LLM called and what it received back
  • Conditional branches — verify which branch of an aktorIfElse was taken
  • Loop iterations — inspect each iteration of an aktorDoWhile
  • Data flow — trace how values transform as they pass through the Aktor graph

For the same run shown on the diagram instead of as a tree, with step controls and a live mode, see Reaktor Trace Walk.

Privacy Considerations

Logs are stored by the platform and may be visible to administrators. Think carefully about what you include in log context objects.

Avoid logging:

  • Personal data — names, email addresses, phone numbers, national IDs
  • Document content — full text extracted from user-uploaded files
  • AI model inputs and outputs — these often contain sensitive business or personal information
  • Authentication tokens, passwords, or API keys

Safe to log:

  • Internal IDs (documentId, reaktorId, runId)
  • Counts, durations, and status codes
  • Non-sensitive metadata (file type, page count, processing stage)
warning

Logging personal or sensitive data may violate GDPR and your organisation's data handling policies. When in doubt, log an ID and look up the details on demand — do not store the data itself in the log.