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.
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 all stored log entries for this Reaktor Deployment
A Refresh button with a blue indicator appears when new log entries arrive after the current view was loaded.
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"]
}
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.
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 }
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 |
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.
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
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.