Runtime Logs
Runtime Logs help you observe and troubleshoot Reactor and Proxy code-transform executions. They capture messages emitted by your code and platform lifecycle events, such as when execution starts, completes, times out, or fails.
Enable capture on a supported Node.js runtime, then invoke the Reactor or Proxy. View retained logs in the Customer Portal or the CLI, or query them through the API. To receive log batches through Webhooks, create a Webhook subscription.
Choose How to View Logs
| View | Use it to |
|---|---|
| Portal History | Search retained logs by time range and filters, newest first. |
| Portal Live | Follow new logs in your browser. |
| CLI read | Read logs from a time range, oldest first, and exit. |
| CLI tail | Follow new logs in your terminal, optionally including recent history. |
| API | Query retained logs for one Reactor or Proxy from your own code, with the same filters as Portal History. |
| Webhooks | Receive log batches at your own endpoint. |
Enable Runtime Logs
Set runtime.logs.enabled to true on a Reactor or on each Proxy code transform whose records you want to capture. Configurable runtime-log capture is disabled by default.
{
"runtime": {
"image": "node24",
"logs": {
"enabled": true,
"level": "info"
}
}
}
Changing runtime.logs.enabled or runtime.logs.level updates the Reactor or affected Proxy code transform asynchronously. While the resource is updating, invocations use the previous active configuration. If the resource becomes outdated, invocations continue using the previous working version. The new log settings apply after the resource returns to active.
See the Reactor API and Pre-Configured Proxies API for complete configuration examples.
Choose a Log Level
runtime.logs.level is optional and defaults to info.
| Level | Use it for |
|---|---|
debug | Detailed diagnostic context during development or focused troubleshooting. This can produce a high volume of records. |
info | Normal execution milestones and meaningful operations. |
warn | Unexpected or degraded conditions from which execution can continue. |
error | Failures that prevent an operation from completing as intended. |
The configured level includes application records at that level and every more severe level. For example, warn captures warn and error records. This setting does not filter platform lifecycle records.
Log Application Activity
Use the logger passed on the Reactor or transform event:
module.exports = async function handler(event) {
const { logger } = event;
logger.debug("Received request");
logger.info("Processing request", { operation: "tokenize" });
logger.warn("Using fallback configuration");
logger.error("Processing failed", new Error("Processor unavailable"));
// ...
};
The logger supports logger.debug, logger.info, logger.warn, and logger.error. logger.log is an alias for logger.info.
Output written through console.* is not captured as a runtime-log record. Use the injected logger instead.
Application records use source: "application" and record_type: "log". They contain a level and message, and can include structured attributes and direct JavaScript Error details.
Understand Platform Lifecycle Records
Basis Theory emits platform lifecycle records independently of application logging calls. These records use source: "platform" and record_type: "lifecycle":
execution.startedindicates that runtime execution started.execution.completedindicates that runtime execution ended and includes anoutcome.
A completed execution has one of these outcomes:
| Outcome | Meaning |
|---|---|
success | Runtime execution completed successfully. |
timeout | Runtime execution exceeded its configured timeout. |
out_of_memory | Runtime execution exhausted its available memory. |
application_error | Application code failed without a platform error classification. |
platform_error | The runtime platform reported another recognized failure. |
An execution.completed record can include duration_ms, the elapsed execution time in milliseconds.
Runtime Log Record Types
| Source | Record type | Description | Type-specific fields |
|---|---|---|---|
application | log | A message or structured diagnostic context emitted through event.logger. | level, message, attributes, error |
platform | lifecycle | Execution lifecycle information emitted automatically by the runtime. | event, outcome, duration_ms |
Record Examples
These record excerpts illustrate application messages and platform lifecycle records. See the Customer Portal, CLI, and Webhooks sections for the fields and formats available with each method.
debug, info, and warn application records share the same shape; error records can also include the nested error object.
- Application info
- Application error
- Lifecycle started
- Lifecycle completed
{
...
"occurred_at": "2026-08-11T12:00:00.010Z",
"source": "application",
"record_type": "log",
"level": "info",
"message": "Processing request",
"attributes": {
"operation": "tokenize"
},
...
}
{
...
"occurred_at": "2026-08-11T12:00:00.010Z",
"source": "application",
"record_type": "log",
"level": "error",
"message": "Processing failed",
"error": {
"type": "Error",
"message": "Processor unavailable",
"stack_trace": "Error: Processor unavailable\n at handler (index.js:7:37)"
},
...
}
{
...
"occurred_at": "2026-08-11T12:00:00.000Z",
"source": "platform",
"record_type": "lifecycle",
"event": "execution.started",
...
}
{
...
"occurred_at": "2026-08-11T12:00:30.000Z",
"source": "platform",
"record_type": "lifecycle",
"event": "execution.completed",
"outcome": "timeout",
"duration_ms": 30000,
...
}
Sanitization, Truncation and Capture Limitations
Basis Theory bounds and sanitizes customer application messages, attributes, and direct Error details before publishing them. Sensitive values in supported content are replaced with [REDACTED]. Only the documented runtime-log fields are published. Internal execution metadata and identifiers are omitted.
Large string values in runtime-log records may be truncated after sanitization. Truncated values retain a complete UTF-8 prefix and end with the suffix …[truncated; original_bytes=N], where N is the full sanitized value's byte length before truncation. Oversized attribute keys may be omitted along with their values or subtrees. All runtime-log consumers receive the same sanitized, truncated values.
Runtime Logs are fail-open: a failure to capture or deliver records does not change the customer code result. Abrupt runtime termination can prevent final records, including an execution.completed lifecycle record, from being captured.
Avoid logging sensitive information, including secrets, authentication tokens, or payment card data, in messages or attributes. Sanitization reduces accidental exposure but does not replace controlling what your code logs.
Search Runtime-Log Messages
Word search is limited to the message field and matches all query words in the same message, ignoring case and word order. For example, payment failed matches Payment authorization failed.
In the CLI, use --message-search 'payment failed' to search for both words.
In the API, send the words in the message[search_words] query parameter.
To match a complete message, use exact-message matching in the Customer Portal.
View Logs in the Customer Portal
Select your Tenant in the Customer Portal and choose the Proxies or Reactors tab. Open the details from the Actions column, then select Logs beside Configuration. Your Portal user needs access to the selected Tenant and the log:read permission.
The Logs tab is available for Reactors using a supported Node.js runtime image and Proxies with at least one deployed code transform using a supported image. Enable capture on the Reactor or each Proxy transform whose logs you want to view.
Search History
History is selected by default. To search captured logs:
- Choose History time range (UTC): Last 15 minutes, Last hour, Last 24 hours (default), or Custom with Start (UTC) and End (UTC).
- Set the filters for your investigation.
- Click Search history.
Results are newest first. Use Load More to load additional results within your selected time range, or Restart when prompted to start a fresh search. Available history is limited by your Tenant log retention, shown as Log Retention on the Customer Portal's Quotas page.
Use Source to choose All sources, Application, or Platform. Search message uses word search; a blank word-search field matches any message.
Enable Match entire message (case-sensitive) to match the complete stored message. With this option enabled, a blank field matches empty messages.
Expand Advanced filters to narrow the result:
| Filter | Matches |
|---|---|
| Level | Application records at the selected level: debug, info, warn, or error. |
| Event | execution.started or execution.completed platform records. |
| Outcome | Completed executions with a lifecycle outcome: success, timeout, out_of_memory, application_error, or platform_error. |
| Trace ID (case-sensitive) | The exact BT trace ID. |
| Proxy transform | Request or Response. Both directions are included by default. Available for Proxies only. |
Use message and level filters for Application records, and event and outcome filters for Platform records. Incompatible controls are disabled or cleared. To investigate execution failures, select Platform and choose the relevant Outcome.
Use Show details to expand a record's full stored message and available metadata, including trace ID, source, record type, outcome, duration, transform direction, error, and attributes.
Follow Live Logs
Switching to Live resets its filters while preserving your History search. Choose your filters, then click Start following to see logs from that point forward.
Live periodically checks for new logs.
Click Stop following before changing disabled filters. Stopping preserves the visible records. Starting again clears the Live list and begins a new watch.
Choose Live order: Newest first (default) or Oldest first. Auto-scroll keeps the view at the latest arriving records. Use History to inspect older records that are no longer shown in Live.
If Live reaches a retrieval limit, it stops following and keeps the last complete results visible. Narrow your filters and start again, or use History to search a shorter time range.
Read and Tail Logs with the CLI
Install the CLI and authenticate with a Management API key that has the log:read permission. Select the API environment and region for your Tenant and enable capture on the Reactor or Proxy code transform.
- Reactor
- Proxy
Read the last hour, then exit:
bt reactors logs read '<REACTOR_ID>' --since 1h
Follow new logs, starting with the last five minutes:
bt reactors logs tail '<REACTOR_ID>' --since 5m
See the Reactor read and tail references for flags and more examples.
Read response-transform records from the last hour, then exit:
bt proxies logs read '<PROXY_ID>' --transform-type response_transform --since 1h
Follow new response-transform logs, starting with the last five minutes:
bt proxies logs tail '<PROXY_ID>' --transform-type response_transform --since 5m
See the Proxy read and tail references for flags and more examples. Omit --transform-type to include both directions.
CLI Output Formats
Each CLI output record contains occurred_at and the applicable fields from this list: level, event, trace_id, transform_type, message, outcome, duration_ms, error, and attributes.
pretty is the default human-readable format. json emits compact objects, and json-pretty emits indented objects. Both JSON formats emit standalone objects separated by blank lines.
The following examples show the same application record in each format.
- Pretty
- JSON
- JSON Pretty
2026-08-11T12:00:00.010Z ERROR trace_id=example-trace response_transform Payment authorization failed | {"operation":"authorize"}
{"occurred_at":"2026-08-11T12:00:00.010Z","level":"error","trace_id":"example-trace","transform_type":"response_transform","message":"Payment authorization failed","attributes":{"operation":"authorize"}}
{
"occurred_at": "2026-08-11T12:00:00.010Z",
"level": "error",
"trace_id": "example-trace",
"transform_type": "response_transform",
"message": "Payment authorization failed",
"attributes": {
"operation": "authorize"
}
}
Query Logs with the API
List Reactor Logs and List Proxy Logs return retained records for one Reactor or Proxy, newest first, with the same time range, source, level, trace ID, and message filters as Portal History. They require the log:read permission and use cursor pagination.
Troubleshoot Runtime Logs
If you have trouble finding the logs you expect, check the following:
- Tenant and resource. Confirm the selected Tenant, Reactor or Proxy ID, and Proxy transform direction. For CLI commands, check the API environment and region.
- Logging configuration. Use a supported Node.js runtime image and enable runtime logs on the Reactor or relevant Proxy transform. After configuration changes, wait for the resource to become
active. Useevent.loggerand choose aruntime.logs.levelthat includes the messages you want to capture. - Search parameters. Review the time range, trace ID, message filter, and selected application-log level. In the Portal, select Platform to filter lifecycle records by Event or Outcome.
- Permissions. For CLI commands, use a Management API key with the
log:readpermission. In the Customer Portal, check your Tenant access andlog:readpermission. - Tenant log retention. Check Log Retention on the Quotas page in the Customer Portal. Searches automatically limit the selected time range to that retention period.
Receive Runtime Logs through Webhooks
For Node.js runtime images, enable configurable runtime-log capture and subscribe to:
reactor.logfor Reactor runtime records.proxy.logfor Proxy request-transform and response-transform runtime records.
Register these values in the Webhook events list through the Create Webhook API. Follow Webhook Delivery for signature verification, retries, and idempotent processing.
Each delivery has three contract layers:
- The outer
eventfields, includingevent.trace_id, follow the common event structure, and the request headers anddelivered_atfollow Webhook Delivery. Related events can share this request-level correlation identifier. event.datafollows the shared runtime-log event data fields. This layer containsinvocation_id, resource identity, batch sequence bounds, andrecords.- Each item in
event.data.recordsfollows the shared runtime-log record fields.
Runtime Log Event Data Fields
Runtime-log event batches have these data fields:
| Name | Type | Presence | Applies to | Description |
|---|---|---|---|---|
| invocation_id | String | Required | reactor.log, proxy.log | Invocation identity shared by every batch from one invocation. |
| reactor | Object | Required | reactor.log | Customer-visible Reactor resource. |
| reactor.id | String | Required | reactor.log | Reactor identifier. |
| proxy | Object | Required | proxy.log | Customer-visible Proxy resource. |
| proxy.id | String | Required | proxy.log | Proxy identifier. |
| transform_type | String | Required | proxy.log | request_transform or response_transform. |
| sequence_start | Number | Required | reactor.log, proxy.log | Sequence of the first record in this batch. |
| sequence_end | Number | Required | reactor.log, proxy.log | Sequence of the last record in this batch. |
| records | Array | Required | reactor.log, proxy.log | Non-empty ordered array. Each item follows the runtime-log record fields. |
Runtime Log Record Fields
Each runtime-log record has this shape:
| Name | Type | Presence | Description |
|---|---|---|---|
| occurred_at | String | Required | ISO 8601 timestamp when the record occurred. |
| sequence | Number | Required | Invocation-local record order. |
| source | String | Required | application for logger output or platform for runtime lifecycle records. |
| record_type | String | Required | log for application records or lifecycle for platform records. |
| event | String | Lifecycle only | execution.started or execution.completed. |
| level | String | Application only | debug, info, warn, or error. |
| message | String | Application only | Sanitized application message. If no message argument is supplied, this is the direct Error's message when present, otherwise an empty string. |
| attributes | Object | Optional application field | Sanitized structured attributes supplied to the logger. |
| error | Object | Optional application field | Sanitized details from a direct JavaScript Error. |
| error.type | String | When error is present | Error type or name. |
| error.message | String | When error is present | Error message. |
| error.stack_trace | String | Optional when error is present | Sanitized stack trace. |
| outcome | String | Completed lifecycle only | success, timeout, out_of_memory, application_error, or platform_error. |
| duration_ms | Number | Optional completed lifecycle field | Elapsed execution time in milliseconds. |
occurred_at describes when an individual record occurred. Use sequence, not a timestamp, to order records from the same invocation.
The following tabs show complete deliveries for each event type.
- Reactor
- Proxy request transform
- Proxy response transform
{
"event": {
"id": "730ffeb4-d511-5d96-bc79-98ae023a8bc7",
"type": "reactor.log",
"timestamp": "2026-08-11T12:00:00.100Z",
"tenant_id": "869d5b1c-1ae8-4ce6-96c6-73a602b407ff",
"trace_id": "ecae00ef-164d-4c0e-aa58-3f306fc7600b",
"data": {
"invocation_id": "8230820d-6e6d-4a87-b801-991e2c4535d1",
"reactor": {
"id": "5b493235-6917-4307-906a-2cd6f1a90b13"
},
"sequence_start": 0,
"sequence_end": 2,
"records": [
{
"occurred_at": "2026-08-11T12:00:00.000Z",
"sequence": 0,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.started"
},
{
"occurred_at": "2026-08-11T12:00:00.010Z",
"sequence": 1,
"source": "application",
"record_type": "log",
"level": "info",
"message": "Validating payload",
"attributes": {
"operation": "validate_payload"
}
},
{
"occurred_at": "2026-08-11T12:00:00.018Z",
"sequence": 2,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.completed",
"outcome": "success",
"duration_ms": 18.4
}
]
}
},
"delivered_at": "2026-08-11T12:00:00.673Z"
}
{
"event": {
"id": "2fcaf393-b635-5ce8-b6a7-b1fc82c78970",
"type": "proxy.log",
"timestamp": "2026-08-11T12:01:00.100Z",
"tenant_id": "869d5b1c-1ae8-4ce6-96c6-73a602b407ff",
"trace_id": "0f777425-9080-4d82-b557-799f43aefcba",
"data": {
"invocation_id": "d00ab9c5-b8ec-46de-8db1-6952d4f19a4c",
"proxy": {
"id": "2db4ded5-98a1-4b3f-87d1-2788340d89ed"
},
"transform_type": "request_transform",
"sequence_start": 0,
"sequence_end": 2,
"records": [
{
"occurred_at": "2026-08-11T12:01:00.000Z",
"sequence": 0,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.started"
},
{
"occurred_at": "2026-08-11T12:01:00.010Z",
"sequence": 1,
"source": "application",
"record_type": "log",
"level": "info",
"message": "Validating payload",
"attributes": {
"operation": "validate_payload"
}
},
{
"occurred_at": "2026-08-11T12:01:00.018Z",
"sequence": 2,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.completed",
"outcome": "success",
"duration_ms": 18.4
}
]
}
},
"delivered_at": "2026-08-11T12:01:00.673Z"
}
{
"event": {
"id": "5a9fb4f1-bd87-5707-9a40-bc2f4a24c872",
"type": "proxy.log",
"timestamp": "2026-08-11T12:01:01.100Z",
"tenant_id": "869d5b1c-1ae8-4ce6-96c6-73a602b407ff",
"trace_id": "0f777425-9080-4d82-b557-799f43aefcba",
"data": {
"invocation_id": "755362a9-0d89-475a-a8aa-b4156e9c5307",
"proxy": {
"id": "2db4ded5-98a1-4b3f-87d1-2788340d89ed"
},
"transform_type": "response_transform",
"sequence_start": 0,
"sequence_end": 2,
"records": [
{
"occurred_at": "2026-08-11T12:01:01.000Z",
"sequence": 0,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.started"
},
{
"occurred_at": "2026-08-11T12:01:01.012Z",
"sequence": 1,
"source": "application",
"record_type": "log",
"level": "info",
"message": "Validating payload",
"attributes": {
"operation": "validate_payload"
}
},
{
"occurred_at": "2026-08-11T12:01:01.015Z",
"sequence": 2,
"source": "platform",
"record_type": "lifecycle",
"event": "execution.completed",
"outcome": "success",
"duration_ms": 15.2
}
]
}
},
"delivered_at": "2026-08-11T12:01:01.744Z"
}
Process Ordered Webhook Batches
One invocation can produce multiple bounded Webhook events. Every batch for the invocation shares invocation_id. Within each event, sequence_start and sequence_end identify the first and last included record, and every record has its own sequence.
Webhook delivery order is not record order. Deduplicate repeated deliveries by event.id, group batches by invocation_id, and order their records by sequence.
For example, these deliveries arrive as batch B, batch A, and a retry of batch A:
[
{
"event": {
"id": "8d8fd53b-a0a5-553c-8aeb-32dd7133885d",
"data": {
"invocation_id": "c7a9e2b4-1d63-4f85-a0c2-5e7b913d684f",
"sequence_start": 2,
"sequence_end": 3,
"records": [
{ "sequence": 2, "message": "record 2" },
{ "sequence": 3, "message": "record 3" }
]
}
}
},
{
"event": {
"id": "f032f5bc-c05c-5889-b833-9778c406592a",
"data": {
"invocation_id": "c7a9e2b4-1d63-4f85-a0c2-5e7b913d684f",
"sequence_start": 0,
"sequence_end": 1,
"records": [
{ "sequence": 0, "message": "record 0" },
{ "sequence": 1, "message": "record 1" }
]
}
}
},
{
"event": {
"id": "f032f5bc-c05c-5889-b833-9778c406592a",
"data": {
"invocation_id": "c7a9e2b4-1d63-4f85-a0c2-5e7b913d684f",
"sequence_start": 0,
"sequence_end": 1,
"records": [
{ "sequence": 0, "message": "record 0" },
{ "sequence": 1, "message": "record 1" }
]
}
}
}
]
After deduplicating by event ID and sorting by record sequence, the result is 0, 1, 2, 3.
Delivery is at least once. Do not depend on Webhook arrival time or any timestamp for ordering. Sequences are invocation-local ordering keys, but abrupt termination can prevent records from being captured. Do not assume that every invocation contains a complete or contiguous sequence.