Function Logs
View historical and live execution logs for your functions.
View historical and live execution logs for your functions.
Read runtime and deployment logs with a platform token belonging to the project
owner or a project access token. A read_only project token is sufficient.
Project end-user sessions, anon keys, and service keys do not grant log access.
Keep project tokens on your server and rotate them through project token management.
For SDK examples, see JavaScript, Python, and Ruby.
Runtime Logs
Use this endpoint for paginated historical runtime logs and text or structured-field search:
POST /projects/{id}/logs/search
Authorization: Bearer PLATFORM_TOKEN
Content-Type: application/jsonSet resource.type to function. Omit resource.ids to return logs across all
functions in the project, or include one or more function IDs to return logs for
selected functions. A durable function is read the same
way, by its own ID; its runtime logs cover every execution, including what each
resume logged again. The q field is optional; leave it blank or omit it to list
stored logs using structured filters only.
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function","ids":["FUNC_ID"]}}'Set resource.kind to durable or standard to read one kind of function,
however many the project has:
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function","kind":"durable"}}'Response:
{
"data": [
{
"id": "log/us-east-1/01HKG3W9M0A5V7R2J6Z0Q6Y4S9",
"timestamp": "2024-01-01T12:00:00Z",
"level": "info",
"body": {
"message": "User logged in",
"user_id": "usr_123"
},
"resource": {
"type": "function",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "login"
},
"region": "us-east-1"
}
],
"limit": 100,
"has_more": true,
"next_cursor": "eyJwayI6..."
}Internal platform runtime logs are filtered out; only application logs emitted by deployed functions are returned.
A Python or Ruby function's uncaught error, or one raised while it starts, is
one error row whose body is the runtime's report, with the error's
errorType, errorMessage, and stackTrace. Ruby logs a line before it, such
as Error raised from handler method, as its own row.
A Python exception you log with logger.exception(), or with exc_info=True,
keeps the whole log record as its row's body: your message, and the
exception's errorType, errorMessage, and stackTrace. Node.js's
console.error("...", error) already writes the stack into the message, so its
body is the message. In local mode, an uncaught error's body is its record
in every runtime.
Historical log rows identify the owning function or frontend with a required
resource object. Deployment log rows include a deployment object when
available.
Filtering
Supported request body fields:
| Field | Description |
|---|---|
resource.type | Required. Use function. |
resource.ids | Optional function IDs. Omit or pass an empty array to search all functions in the project. |
resource.kind | Optional. standard or durable reads only functions of that kind; omit it for both. With resource.ids, IDs of the other kind return no logs. A cursor is bound to the kind. |
q | Optional query. Unqualified terms search the body. Supports quoted text, implicit AND, AND/OR/NOT, parentheses, and the fields body, level, region, invocation.id, resource.id, and resource.name. The resource-name aliases function, frontend, and database are also supported. level accepts trace, debug, info, warn, error, and fatal, plus the aliases log, information, warning, err, and critical. |
limit | Max records to return. Default 100, max 1000. |
cursor | Opaque cursor from the previous response. |
start_time | Inclusive lower bound as an RFC3339 timestamp. |
end_time | Inclusive upper bound as an RFC3339 timestamp. |
# Find "checkout_failed" in warn and error logs in one region
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function","ids":["FUNC_ID"]},"q":"level:(warn OR error) region:us-east-1 body:checkout_failed","start_time":"2024-01-01T12:00:00Z","end_time":"2024-01-01T13:00:00Z"}'Pagination
When has_more is true, pass the response next_cursor back as the cursor
field on the next request with the same filters. Keep paging until has_more
is false.
A search reads for a bounded time per request, so a page can hold fewer than
limit events, or none, while has_more is true. This happens most with
selective queries over a wide time range. Continue from next_cursor rather
than treating a short or empty page as the end of the results.
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function"},"limit":100}'
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function"},"limit":100,"cursor":"eyJwayI6..."}'Activity
Count logs over time to draw a histogram. The request takes the same
resource and q fields as search:
curl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/activity" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function"},"q":"level:error","start_time":"2024-01-01T12:03:10Z","end_time":"2024-01-01T13:03:10Z","bucket_count":4}'Response:
{
"data": [
{
"start_time": "2024-01-01T12:00:00Z",
"end_time": "2024-01-01T12:30:00Z",
"counts": {
"levels": { "debug": 0, "error": 3, "fatal": 0, "info": 0, "trace": 0, "warn": 0 },
"regions": { "us-east-1": 3 },
"resource_ids": { "550e8400-e29b-41d4-a716-446655440000": 3 }
},
"total": 3
},
{
"start_time": "2024-01-01T12:30:00Z",
"end_time": "2024-01-01T13:00:00Z",
"counts": {
"levels": { "debug": 0, "error": 1, "fatal": 0, "info": 0, "trace": 0, "warn": 0 },
"regions": { "us-east-1": 1 },
"resource_ids": { "550e8400-e29b-41d4-a716-446655440000": 1 }
},
"total": 1
},
{
"start_time": "2024-01-01T13:00:00Z",
"end_time": "2024-01-01T13:30:00Z",
"counts": {
"levels": { "debug": 0, "error": 0, "fatal": 0, "info": 0, "trace": 0, "warn": 0 },
"regions": {},
"resource_ids": {}
},
"total": 0
}
],
"total": 4
}Buckets follow these rules:
- Counts cover the half-open window
[start_time, end_time). An event exactly atend_timeis not counted. bucket_countis a maximum,24by default and at most96. Volcano picks the smallest width that covers the window in that many buckets, so a response can hold fewer buckets than requested.- Widths come from a fixed set: 1s, 2s, 5s, 10s, 15s, 30s, 1m, 2m, 5m, 10m, 15m, 30m, 1h, 2h, 3h, 6h, 12h, 1d, then whole days.
- Bucket edges fall on UTC multiples of the width, so they stay put as the window moves. The first and last buckets can extend past the window; they count only events inside it.
bucket_count: 1returns one bucket that spans exactly the window.- The window is limited to the plan's log retention (HOBBY: 1 day, SUPERAGENT: 30
days). An older
start_timeis moved up to the retention limit. - Counts across several resources or deployments come from Volcano's activity index. Logs from before a region's index began are not counted; search still returns them.
Live Streaming
Use the stream endpoint to live-tail runtime or deployment logs:
POST /projects/{id}/logs/stream
Authorization: Bearer PLATFORM_TOKEN
Content-Type: application/json
Accept: text/event-streamThe request body supports resource, q, start_time, and limit. The q
field uses the same syntax as search requests. Do not send cursor or
end_time; use /logs/search for range backfills.
curl -N -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/stream" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"resource":{"type":"function","ids":["FUNC_ID"]},"q":"level:error"}'Each log event is sent as Server-Sent Events:
id: STREAM_CURSOR
event: log
data: {"id":"LOG_EVENT_ID","timestamp":"2024-01-01T12:00:00Z","level":"info","body":"User logged in","resource":{"type":"function","id":"FUNC_ID","name":"login"},"region":"us-east-1"}The SSE id field is an opaque stream cursor. To reconnect without replaying
recent events, send the most recent id as the Last-Event-ID header or
last_event_id query parameter. The log event's stable ID remains in
data.id.
The cursor is bound to the request body: resource, q, start_time, and
limit must match the original request when reconnecting. Resuming with a
changed body returns
400 — open a new stream (without a cursor) for the new selector instead.
This is a live tail, not a gap-free backfill. On connect (or reconnect) the
server delivers at most limit of the most recent matching events from the
cursor position and then follows new events. If a stream stays disconnected
long enough for more than limit events to accumulate, the older events in
that gap are not replayed — use /logs/search to backfill a specific time
range.
The stream sends : keepalive comments while idle. If a transient read fails
after the stream is open, the server emits an event: warning payload and
continues retrying. The warning's error field carries a safe message;
internal failures are reported generically and logged server-side.
Deployment Logs
Deployment build logs use the same project log search endpoint. Add a
resource.deployments.ids selector to read one or more deployments for the
selected function resources. New function and frontend builds show dependency
installation, project build and syntax-check output, concise progress, and
actionable errors. Platform setup, packaging, and publishing diagnostics stay
in internal operator logs. Existing historical logs are unchanged.
POST /projects/{id}/logs/search
Authorization: Bearer PLATFORM_TOKENcurl -X POST "https://api.volcano.dev/projects/PROJECT_ID/logs/search" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource":{"type":"function","ids":["FUNC_ID"],"deployments":{"ids":["DEPLOYMENT_ID"]}},"limit":100}'Use the same selector with /logs/stream to live-tail deployment logs.
Deployment log rows include deployment context:
{
"resource": {
"type": "function",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "login"
},
"deployment": {
"id": "8c6c7c2e-e3ff-42a5-ae4d-650ef9b45746"
}
}Installed Packages
After installing dependencies, a function build lists each dependency your manifest declares, with the version that was installed:
Installing dependencies...
added 42 packages in 3s
Installed packages:
@volcano.dev/sdk@1.7.2
pg@8.13.1
sharp (not installed)The list covers dependencies and optionalDependencies in package.json,
the packages named in requirements.txt and the files it includes with -r,
and the Gemfile gems outside the development and test groups. A
requirement given as a URL or path, editable (-e) or not, is listed when it
names its package, as in name @ git+https://... or #egg=name. The list
shows at most 50 entries.
If a package your function imports is missing from the list, declare it in the
manifest the build installs from. For Node.js, declare it under dependencies:
the list leaves out devDependencies, which your function should not rely on at
runtime. An entry marked (not installed) is declared, but the install did not
add it; (optional, not installed) is an optional dependency the install
skipped, such as one for another platform; (installed, no version) is a
package the install added whose package.json names no version, as a local
file: dependency may. A requirements file included from a URL is marked
(not listed): the install reads it, but the list does not. The URL is shown
without its credentials or query, or as <url> when it uses an environment
variable. A requirement marked
(not installed; environment marker) has a marker, such as
; sys_platform == "win32", that excludes the build's platform, so the install
skipped it on purpose.
Read Errors
An internal failure while loading log resources returns HTTP 500 with a
message identifying the operation, for example:
{ "error": "failed to list functions" }The message omits internal connection details. Retry the request; include its
X-Request-Id if you contact support.
Log levels
Every runtime log line has one of the levels trace, debug, info, warn,
error, or fatal:
- Console and logging calls.
console.error,console.warn, and the otherconsolemethods set the level, as do Python'sloggingcalls. In local mode, Python'sprintand Ruby'sputsset it too:info, orerroron standard error, whatever the line contains, including a JSON line such as structlog prints. - JSON lines with a level. A JSON object written straight to standard
output, as pino, bunyan, and structlog do, keeps the level in its top-level
levelfield, read with the table below. Names match in any case. The whole line must be that one well-formed object, nested at most 31 levels deep. - Other lines. A line written straight to standard output otherwise takes
the level of its first level word:
fatal;error,errored,fail,failed,failing, orfailureforerror;warn,warned, orwarningforwarn;debug; ortrace. Sowarning: request failediswarn. A line with none of these isinfo, orerrorwhen it was written to standard error in local mode.
A JSON line's level maps to a stored level, with the same names the level
search field accepts:
| Stored level | level number | level names |
|---|---|---|
trace | 10 | trace |
debug | 20 | debug |
info | 30 | info, information, log |
warn | 40 | warn, warning |
error | 50 | error, err |
fatal | 60 | fatal, critical |
import pino from 'pino';
const logger = pino();
logger.error({ orderId: 'ord_123' }, 'card declined'); // {"level":50,...} is stored at error
logger.warn('retrying payment'); // {"level":40,...} is stored at warnLogging Best Practices
Use structured logging so important fields can be indexed:
console.log(
JSON.stringify({
level: 'info',
action: 'user_login',
user_id: user.id,
timestamp: new Date().toISOString(),
}),
);Use consistent log levels:
console.log('INFO:', ...);
console.error('ERROR:', ...);
console.warn('WARN:', ...);