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
| Level | Method | When to use |
|---|---|---|
debug | logger.debug(message, context?) | Detailed diagnostic output, disabled in production by default |
info | logger.info(message, context?) | Normal operational events (started, completed, counts) |
warn | logger.warn(message, context?) | Unexpected but recoverable situations |
error | logger.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:
| Field | Source | Description |
|---|---|---|
aktorId | Logger name | The name passed to getLogger() |
runId | AsyncLocalStorage | Unique ID for the current Reaktor execution |
reaktorId | AsyncLocalStorage | ID 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.
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:
| Parameter | Type | Description |
|---|---|---|
aktorId | string | Filter by aktor ID |
runId | string | Filter by run ID |
logLevel | string | Filter by level (debug, info, warn, error) |
loggerName | string | Filter by logger name |
searchText | string | Search across message, logger name, aktor ID, and run ID |
fromDate | string | Start of time range (ISO 8601) |
toDate | string | End of time range (ISO 8601) |
limit | number | Max entries to return (default 500, max 1000) |
offset | number | Number 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:
| Parameter | Type | Description |
|---|---|---|
tail | number | Number of trailing lines to return (default 200, max 5000) |
file | string | Name 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:
| Parameter | Type | Description |
|---|---|---|
name | string | The log file to read, from the files list (required) |
offset | number | First line to return, 0-based (default 0) |
limit | number | Number of lines to return (default 1000, max 10000) |
tail | number | Read 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 kind | Limit | What is kept |
|---|---|---|
| Regular strings | 100,000 | First and last 300 characters |
| Binary strings (base64, data URIs) | 1,000 | First and last 100 characters |
| Arrays | 1,000 items | First and last 5 items |
| Byte arrays (numeric-key objects) | 1,000 bytes | First 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:
- 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. - Known token prefixes — values starting with
sk-,operaide_,ghp_,xoxb-,eyJ(JWT), and similar well-known prefixes are treated as secrets. - 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.
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:
| Field | Purpose |
|---|---|
excludeAktors | Skip tracing for specific aktor names (array of strings) |
maxStringLength | Override the regular-string limit |
maxBinaryLength | Override the binary-string limit |
maxArrayLength | Override the array-length limit |
captureStreamEvents | Record 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.
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:
- Navigate to the Reaktor in the Operaide Studio
- Run the Reaktor or open a past execution
- 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
aktorIfElsewas 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)
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.