Skip to main content
Version: 3.0

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.

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 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:

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"]
}

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 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

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.

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

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.