Skip to main content
Version: Developer

Session lifecycle events reference

Every transition a workspace session goes through is one structured log record tagged with kasm.metric_name. This page lists each event, the fields it carries, where it is emitted, and how to read it back. Available in Kasm Workspaces 1.20.0 and later.

For the counters that pair with these events, see Observability metrics. For the environment variable that adds these fields to the JSON log file, see Observability configuration.

Where the records go​

Kasm application logs are emitted on two channels.

ChannelCarries the structured fieldsNotes
OTLP to the collector, then ClickHouseYes, in the LogAttributes mapAlso the source for the Splunk HEC, OTLP/HTTP, and syslog forwarders
/opt/kasm/current/log/api.logNoOne JSON object per line with timestamp, level, logger, module, line, and message

The api.log file carries the six base fields by default. Its message names the session and the transition. To add the attributes below to the file, list them in KASM_LOG_FILE_EXTRA_FIELDS. The file writes them with dots replaced by underscores, so kasm.kasm.id appears as kasm_kasm_id. The trace ID is never in the file. A consumer that needs correlation reads the OTLP data.

One event, client detached, is not emitted by the API at all. The OTel Collector derives it from the proxy's nginx access log, so it exists in ClickHouse and in any collector forwarder but never in api.log.

Common fields​

PurposeField on the recordSource
TimestampTimestamp (ClickHouse) or timestamp (file)The log record's own time, UTC, millisecond precision or better
Event typekasm.metric_nameOne of the values in the events table
Session identifierkasm.kasm.idThe session ID
Correlation identifierTraceIdW3C trace ID. Records written inside an API request carry that request's trace. Worker records carry the job's trace, linked to the triggering request's span. Records forwarded by a legacy component through the API (kasm.log.ingest_path is legacy_bridge) carry no trace ID
Container identifierkasm.agent.container_idNull until compute exists, and always null for server-backed sessions
Regionkasm.zone.nameThe deployment zone the session was placed in
Elapsed durationkasm.kasm.transition_msPresent only on kasm.provision.started, kasm.provision.ready, and kasm.provision.destroy, where it is the milliseconds since the session was requested, and on kasm.session.client_detached, where it is the milliseconds since the client attached

Records written inside an API request also carry the request context fields kasm.kasm.user_id, kasm.kasm.user_name, client.address, and url.path. No record carries a visited URL, a page title, or input text.

Events​

Eventkasm.metric_nameEmitted fromChange in 1.20.0
Session requestedkasm.provision.requestAPI, session request acceptedExisting record, now tagged
Provisioning startedkasm.provision.startedWorker, reconcile reserves computeNew
Container createdkasm.provision.createWorker, agent create callExisting record, new fields
Session readykasm.provision.readyWorker, start_date setNew
Client attachedkasm.session.client_attachedAPI, proxy authorization of the session websocketNew
Client detachedkasm.session.client_detachedOTel Collector, from the proxy access logNew
Session resumedkasm.session.resumedAPI, resume command acceptedExisting record, now tagged
Session destroyedkasm.provision.destroyWorker, accounting close-outExisting record, new fields
Errorkasm.provision.errorWorker, reconcile cannot convergeNew

Session requested​

Written once the request has passed every check and the session row is committed. A refused request writes no lifecycle record. It increments kasm.provision.request.failed with a reason instead.

FieldValue
kasm.kasm.idThe new session
kasm.kasm.image_idImage launched
kasm.kasm.image_typeContainer, Server, or Server Pool
kasm.zone.nameZone the request resolved. Null when placement chooses the zone later
kasm.kasm.is_castLaunched through a cast link
kasm.kasm.is_anonymousThe owner is an anonymous account
kasm.kasm.keepalive_secondsIdle lifetime decided at request time
kasm.cast_link.id, kasm.cast_link.keyPresent when launched through a cast link

Provisioning started​

Written by the first reconcile action that reserves compute: a free slot on a host, with kasm.server.id set, or the claim on a standby container. kasm.kasm.transition_ms is the time the request waited for capacity.

Container created​

Written by the agent create call for one placement. The record carries the image fields kasm.kasm.image_id, kasm.kasm.image_name, kasm.kasm.image_friendly_name, and kasm.kasm.image_type, plus kasm.zone.name, kasm.server.id, kasm.kasm.is_anonymous, kasm.kasm.keepalive_seconds, the cast link fields, and the egress selection. Server-backed sessions have no create call and skip this event.

Session ready​

Written when the placement's connection details are recorded and start_date is set. The record carries kasm.kasm.id, kasm.agent.container_id, kasm.server.id, kasm.zone.name, and kasm.kasm.transition_ms measured from request to ready.

Client attached​

The proxy asks the API to authorize every request under /desktop/<session_id>/. Only the request that opens the session websocket counts as a client attaching: websockify for KasmVNC sessions and websocket-tunnel for Connection Proxy (Guac) sessions. Assets and polls are not counted. The record carries kasm.kasm.id, kasm.kasm.user_id, kasm.kasm.user_name, kasm.agent.container_id, kasm.server.id, kasm.kasm.is_owner, and kasm.kasm.is_shared. The paired counter is kasm.session.connect.

Client detached​

nginx writes one access log row when a request finishes. For the session websocket, the request finishes when the client disconnects. The row for GET /desktop/<session_id>/vnc/websockify with status 101 is therefore the detach event, and its request_time is how long the client was attached. The OTel Collector tags that row at ingest with the following fields.

FieldValue
kasm.metric_namekasm.session.client_detached
kasm.kasm.idFrom the request path
kasm.kasm.transition_msrequest_time multiplied by 1000, rounded
cookie_usernameThe connected user. A proxy field, not kasm.kasm.user_name
TraceIdThe websocket request's trace from the nginx OTel module
ServiceNamekasm-proxy

The proxy also logs its loopback hop to the agent for the same websocket under a different path. That row is not tagged, so each detach is counted once. Query strings are removed from the proxy record at ingest.

Client detach events from ClickHouse
SELECT Timestamp,
LogAttributes['kasm.kasm.id'] AS session_id,
LogAttributes['kasm.kasm.transition_ms'] AS attached_ms,
LogAttributes['cookie_username'] AS user
FROM otel_logs
WHERE ServiceName = 'kasm-proxy'
AND LogAttributes['kasm.metric_name'] = 'kasm.session.client_detached'
ORDER BY Timestamp DESC;

Session resumed​

Written when a resume command is accepted for a paused or stopped session. The record carries kasm.kasm.id, kasm.agent.container_id, and kasm.session.action set to resume. Pause and stop are not lifecycle events. They are reported through the kasm.session.lifecycle counter and a plain accepted log line.

Session destroyed​

Written when the accounting row is closed, immediately before the session row is deleted. The record carries the existing user, image, zone, server, egress, cast, queued, and staged fields, plus kasm.agent.container_id, kasm.kasm.transition_ms measured from request to destroy, kasm.kasm.is_anonymous, kasm.kasm.keepalive_seconds, and kasm.kasm.image_type.

Error​

Written when reconcile decides a session cannot converge, and for any unexpected exception in a reconcile pass.

FieldValue
kasm.kasm.idThe session
kasm.agent.container_id, kasm.server.idPresent when compute existed
kasm.provision.failed.reasonThe reconcile reason code, when the error has one
exception.typeThe exception class name
exception.messageThe exception message

Reading the events back​

The following query returns one session's timeline from ClickHouse. Replace <session_id> with the session ID.

One session's lifecycle timeline
SELECT Timestamp, ServiceName,
LogAttributes['kasm.metric_name'] AS event,
LogAttributes['kasm.kasm.transition_ms'] AS transition_ms,
LogAttributes['kasm.zone.name'] AS zone,
LogAttributes['kasm.agent.container_id'] AS container_id,
TraceId
FROM otel_logs
WHERE LogAttributes['kasm.kasm.id'] = '<session_id>'
AND (LogAttributes['kasm.metric_name'] LIKE 'kasm.provision.%'
OR LogAttributes['kasm.metric_name'] LIKE 'kasm.session.%')
ORDER BY Timestamp;

In api.log, the same transitions appear as message lines: Accepted session request, reserved server, kasm (...) is ready, Client attached, Accepted resume, destroyed after, and cannot converge. With the following setting, each line also carries those five attributes as kasm_metric_name, kasm_kasm_id, and so on.

Log file attributes for lifecycle events
KASM_LOG_FILE_EXTRA_FIELDS=kasm.metric_name,kasm.kasm.id,kasm.zone.name,kasm.agent.container_id,kasm.kasm.transition_ms
note

The client detached event never appears in api.log, whatever the value of KASM_LOG_FILE_EXTRA_FIELDS. Read it from ClickHouse or a collector forwarder.