# 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](https://docs.volcano.dev/sdk/js/logs.md), [Python](https://docs.volcano.dev/sdk/python/logs.md),
and [Ruby](https://docs.volcano.dev/sdk/ruby/logs.md).

## Runtime Logs

Use this endpoint for paginated historical runtime logs and text or
structured-field search:

```http
POST /projects/{id}/logs/search
Authorization: Bearer PLATFORM_TOKEN
Content-Type: application/json
```

Set `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](https://docs.volcano.dev/platform/functions/durable-functions.md) 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.

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

```bash
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:**

```json
{
  "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.                                                                                                                                                                                                                                                                                                                                                                                                               |

```bash
# 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.

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

```bash
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:**

```json
{
  "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
  at `end_time` is not counted.
- `bucket_count` is a maximum, `24` by default and at most `96`. 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: 1` returns 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_time` is 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:

```http
POST /projects/{id}/logs/stream
Authorization: Bearer PLATFORM_TOKEN
Content-Type: application/json
Accept: text/event-stream
```

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

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

```text
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.

```http
POST /projects/{id}/logs/search
Authorization: Bearer PLATFORM_TOKEN
```

```bash
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"],"deployments":{"ids":["DEPLOYMENT_ID"]}},"limit":100}'
```

Use the same selector with `/logs/stream` to live-tail deployment logs.

Deployment log rows include deployment context:

```json
{
  "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:

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

```json
{ "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 other
  `console` methods set the level, as do Python's `logging` calls. In local
  mode, Python's `print` and Ruby's `puts` set it too: `info`, or `error` on
  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
  `level` field, 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`, or `failure` for `error`; `warn`, `warned`, or `warning`
  for `warn`; `debug`; or `trace`. So `warning: request failed` is `warn`. A
  line with none of these is `info`, or `error` when 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`          |

```javascript
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 warn
```

## Logging Best Practices

Use structured logging so important fields can be indexed:

```javascript
console.log(
  JSON.stringify({
    level: 'info',
    action: 'user_login',
    user_id: user.id,
    timestamp: new Date().toISOString(),
  }),
);
```

Use consistent log levels:

```javascript
console.log('INFO:', ...);
console.error('ERROR:', ...);
console.warn('WARN:', ...);
```

## See Also

- [Creating Functions](https://docs.volcano.dev/platform/functions/creating-functions.md)
- [Invoking Functions](https://docs.volcano.dev/platform/functions/invoking-functions.md)
