OpenTelemetry
OpenTelemetry is a vendor-neutral standard for collecting telemetry data from running services. Posit Workbench can export structured log events and trace spans over OpenTelemetry Protocol (OTLP) HTTP to any compatible backend.
The OpenTelemetry export sits alongside the existing Prometheus metrics endpoint. Both can be enabled at once, since they cover different signals and answer different questions.
Prerequisites
Using OpenTelemetry to export data requires the Monitoring feature, which is included in the Basic, Enhanced, and Advanced license tiers. Without an active license at one of these tiers, the server accepts the configuration options below but emits no signals.
You also need an OTLP/HTTP-compatible receiver. The OpenTelemetry Collector is one option. Many observability platforms also accept OTLP/HTTP directly.
Configuration
Turn on OpenTelemetry export in rserver.conf. The primary option otel-enabled is the master switch that gates all signals. If you desire to collect only OpenTelemetry logs or only traces, you can disable the unused signal type to reduce buffering overhead with otel-logs-enabled and otel-traces-enabled.
/etc/rstudio/rserver.conf
otel-enabled=1| Config Option | Description | Possible Values | Default Value |
|---|---|---|---|
| otel-enabled | Master switch for OpenTelemetry export. When 0, no OTLP signals are emitted regardless of per-signal options or any otel.conf destination. |
0 or 1 |
0 |
| otel-logs-enabled | Enable export of OpenTelemetry log events. Requires otel-enabled=1. Defaults to 1, so it effectively follows otel-enabled. Set to 0 explicitly to disable logs while leaving traces on. |
0 or 1 |
1 |
| otel-traces-enabled | Enable trace export for session launches. Requires otel-enabled=1. Defaults to 1, so it effectively follows otel-enabled. Set to 0 explicitly to disable traces while leaving logs on. |
0 or 1 |
1 |
See the rserver.conf reference for the canonical descriptions of each option.
When enabled, endpoint(s) can be defined for export in otel.conf, in the same directory as rserver.conf (/etc/rstudio in a default install). Workbench installs a commented template there, owned by root with mode 0600. Uncomment or add one [OTLPEndpoint "name"] section per destination. The name is a label of your choosing and appears in the Workbench log:
/etc/rstudio/otel.conf
[OTLPEndpoint "local"]
Endpoint = http://localhost:4318
Logs = true
Traces = trueRestart Workbench after changing either file.
Exporting to multiple destinations
Define destinations in /etc/rstudio/otel.conf, one [OTLPEndpoint "name"] section per destination, to send signals to one or more OTLP/HTTP receivers. For example, to send to Posit Chronicle and a third-party observability platform, define one destination per receiver. Each destination gets its own headers, so credentials for one platform are never sent to another. If an endpoint is also configured directly in rserver.conf (see below), the otel.conf destinations are added alongside it, not in place of it.
Destination keys
| Key | Description | Possible Values | Default Value |
|---|---|---|---|
| Endpoint | Base OTLP/HTTP endpoint URL for this destination. /v1/logs and /v1/traces are appended automatically. |
A valid URL | empty |
| Logs | Send log events to this destination. | true or false |
true |
| Traces | Send traces to this destination. | true or false |
true |
| Metrics | Accepted for compatibility with Posit Connect and ignored. Workbench does not export OTLP metrics. | true or false |
ignored |
| Headers | Comma-separated key=value headers sent to this destination, for both logs and traces. May be encrypted; see Encrypting destination headers. |
For example DD-API-KEY=xyz |
empty |
| BatchIntervalMs | Interval in milliseconds between batch exports to this destination. Clamped to [500, 60000]. |
Integer in [500, 60000] |
5000 |
| LogsBatchIntervalMs | Batch interval for logs only. Overrides BatchIntervalMs if the key is present at all, including a value of 0 or lower (which is clamped to 500, not treated as “not set”). |
Integer, clamped to [500, 60000] |
falls back to BatchIntervalMs when omitted entirely |
| TracesBatchIntervalMs | Batch interval for traces only. Overrides BatchIntervalMs if the key is present at all, including a value of 0 or lower (which is clamped to 500, not treated as “not set”). |
Integer, clamped to [500, 60000] |
falls back to BatchIntervalMs when omitted entirely |
| TimeoutMs | How long a single export attempt to this destination may block before it is abandoned. Any positive value always applies, overriding OTEL_EXPORTER_OTLP_TIMEOUT and its per-signal variants; set to 0 to leave those environment variables (or the SDK’s own default) in effect instead. See Unresponsive destinations. |
Integer in milliseconds, 0 to defer to the environment |
3000 |
| Enabled | Whether this destination is active. Set to false to keep a destination in the file without exporting to it. |
true or false |
true |
Key names are matched case-insensitively, and - and _ are ignored, so BatchIntervalMs, batchintervalms, and batch-interval-ms are equivalent. Values may be quoted; inside quotes, \" and \\ are escapes and ;/# do not start a comment. Repeated sections and repeated keys are merged, with the last value winning.
Every destination’s Endpoint and Headers apply to both logs and traces. To send a signal to a different endpoint, or with different headers, than its sibling, define two destinations: one with Logs = true, Traces = false and one with Logs = false, Traces = true, each with its own Endpoint and Headers. See the two-destination example below. Batch intervals do not require this: a single destination can flush each signal on its own cadence with LogsBatchIntervalMs and TracesBatchIntervalMs.
A file with two destinations, one of them traces-only:
/etc/rstudio/otel.conf
[OTLPEndpoint "datadog"]
; It is strongly recommended to encrypt Headers values using: rstudio-server encrypt-password
Endpoint = https://otlp-http.datadoghq.com
Headers = "DD-API-KEY=xxxxxxxx"
[OTLPEndpoint "grafana"]
Endpoint = https://otlp-gateway.example.org
Logs = false
Traces = true
Headers = "Authorization=Basic xxxxxxxx"
BatchIntervalMs = 1000Notes:
otel-enabled,otel-logs-enabled, andotel-traces-enabledinrserver.confstill gate everything. A destination withTraces = trueexports nothing whileotel-traces-enabled=0.defaultis reserved for the destination configured inrserver.confand cannot be used as a destination name.- Because this file may hold API keys, restrict it:
chown root:root /etc/rstudio/otel.conf && chmod 0600 /etc/rstudio/otel.conf. Workbench logs a warning if the file is readable more broadly. - A syntax error, a non-boolean
Logs/Traces/Metrics/Enabled, a non-numeric interval or timeout, or a negativeTimeoutMsprevents Workbench from starting, with the reason in the startup log. Keys Workbench does not recognize are logged as a warning and ignored. A destination with no usable endpoint, or with both signals off, is skipped entirely with a warning. A destination whose name duplicates an earlier one is also skipped, while one that merely repeats an endpoint URL already claimed keeps its other signal and loses only the duplicated one.
Encrypting destination headers
Encrypting the Headers values in /etc/rstudio/otel.conf (and the otel-logs-headers and otel-traces-headers values in rserver.conf) protects your API keys if you back up your configuration, save it to a repository, or share it with Workbench Support.
Before configuring Workbench to export directly to a hosted observability platform, check that platform’s own documentation for the deployment it recommends. Most vendors recommend running a local collector agent on each host, which receives telemetry over an unauthenticated loopback endpoint and handles authentication, batching, and retry on the way to the hosted service. Where that is available it is the more robust arrangement, and it needs no Headers in this file. Direct authenticated export is offered as a convenience, useful for prototypes and small deployments where standing up a local collector is not worth the effort.
Use the following steps to encrypt a headers value:
- Run
sudo rstudio-server encrypt-passwordand enter the entire headers string, for exampleDD-API-KEY=xxxxxxxx. - Copy the resulting encrypted value printed in the terminal.
- Replace the plaintext value with the encrypted value. An example resulting line should look like
Headers = <encrypted-text>. - Restart Workbench, then confirm the Workbench logs contain no warning containing “An unencrypted value is being used for”.
Encrypt the whole value as a single string, including any commas separating multiple headers. Do not encrypt each header separately.
Workbench reads encrypted and plaintext values transparently, so pre-existing plaintext configurations continue to work.
Encrypted values are tied to the server’s secure-cookie key. If you rotate that key, follow the re-encryption step before restarting Workbench. If Workbench finds a value that appears to be encrypted but cannot be decrypted, it logs an error and exports to that destination without headers rather than sending a corrupted credential; export to other destinations is unaffected.
Configuring an endpoint directly in rserver.conf (deprecated)
Before otel.conf existed, the only way to point Workbench at an OTLP/HTTP receiver was a set of endpoint options in rserver.conf. Those options continue to work unchanged, but configuring endpoints in otel.conf (above) is now the recommended path. This section is kept for deployments already relying on it.
To send both logs and traces to a single OTLP/HTTP receiver this way:
# /etc/rstudio/rserver.conf
otel-enabled=1
otel-endpoint=http://collector.example.org:4318Workbench appends /v1/logs and /v1/traces to otel-endpoint automatically. Restart Workbench with rstudio-server restart after changing these options.
To send logs and traces to different receivers, set otel-logs-endpoint and otel-traces-endpoint explicitly; either overrides otel-endpoint for its own signal when both are set. Pass headers (for example, authentication tokens) with otel-logs-headers and otel-traces-headers, comma-separated key=value pairs. If otel-traces-headers is empty, Workbench reuses otel-logs-headers for traces.
| Config Option (deprecated) | Description | Possible Values | Default Value |
|---|---|---|---|
| otel-endpoint | Base OTLP/HTTP endpoint URL. /v1/logs and /v1/traces are appended automatically. Used for a signal only when that signal’s own endpoint option (otel-logs-endpoint / otel-traces-endpoint) is not set. |
A valid URL, for example http://collector:4318 |
empty |
| otel-logs-endpoint | OTLP/HTTP endpoint for log events. Overrides otel-endpoint for logs when set. Required for log export when neither otel-endpoint nor an otel.conf destination with Logs = true is configured. |
A valid URL | empty |
| otel-logs-headers | Comma-separated key=value headers for OTLP log export requests. |
For example Authorization=Bearer xyz,X-Tenant=acme |
empty |
| otel-logs-batch-interval-ms | Interval in milliseconds between log batch exports. Clamped to [500, 60000]. |
Integer ≥ 500 | 5000 |
| otel-traces-endpoint | OTLP/HTTP endpoint for traces. Overrides otel-endpoint for traces when set. |
A valid URL | empty |
| otel-traces-headers | Comma-separated key=value headers for OTLP trace export requests. Falls back to otel-logs-headers when empty. |
For example Authorization=Bearer xyz |
empty |
| otel-traces-batch-interval-ms | Interval in milliseconds between trace batch exports. Clamped to [500, 60000]. |
Integer in [500, 60000] |
5000 |
Workbench checks each enabled signal separately at startup. If a signal is enabled but neither an rserver.conf endpoint option nor any usable otel.conf destination applies to it, Workbench logs a warning naming that signal and disables it. A signal that is turned off never warns, whether or not a destination exists for it.
See the rserver.conf reference for the canonical descriptions of each option.
Unresponsive destinations
Each destination gets its own export queue and its own worker thread, so a destination that stops responding cannot delay or drop telemetry bound for the others. Records queued for the unresponsive destination are dropped once its queue fills, and are not retried.
A single export attempt may block for up to TimeoutMs (3 seconds by default). Because Workbench flushes pending telemetry as it shuts down, that timeout also bounds how long an unresponsive destination can delay a restart. Raising it gives a slow but working link more room to succeed; lowering it makes restarts more predictable at the cost of dropping more batches.
Any positive TimeoutMs (including the 3000 default) always applies and takes precedence over OTEL_EXPORTER_OTLP_TIMEOUT (or the per-signal OTEL_EXPORTER_OTLP_LOGS_TIMEOUT / OTEL_EXPORTER_OTLP_TRACES_TIMEOUT). Set TimeoutMs = 0 to leave those environment variables, or the OpenTelemetry SDK’s own default, in effect for that destination instead. Note that the SDK interprets a value with no unit suffix as seconds, so OTEL_EXPORTER_OTLP_TIMEOUT=9000 means 9000 seconds rather than 9000 milliseconds. Prefer an explicit TimeoutMs to avoid the ambiguity.
Signal catalog
Workbench exports two signal types:
- Log events for sign-in, session lifecycle, session state transitions, and browser-side IDE launch performance.
- Trace spans that wrap each session launch with one child span per launch phase.
This section lists every signal Workbench emits, including event names, attributes, types, and allowed values. Operators building dashboards, alerts, or queries against the OTLP stream should treat this section as the contract.
Conventions
Attribute names follow the OpenTelemetry semantic conventions where one applies. Workbench-specific attributes use the session.*, browser.*, and transition_type namespaces.
In every table:
- Required attributes are present on every emission of the event.
- Optional attributes are emitted only when a value is available (for example,
session.r_versionis emitted only for R sessions, and only after the version has been resolved). - Type uses OTLP attribute types:
string,int64, orbool.
Every log event also carries an event.name attribute equal to the event name in the heading. The event.name row is omitted from each table for brevity.
Log events
workbench.user.login
Emitted on each successful sign-in.
Severity: INFO
| Attribute | Type | Required | Description |
|---|---|---|---|
user.id |
string | yes | The user’s Globally Unique Identifier (GUID), resolved server-side. |
workbench.session
Emitted when a session starts, suspends, completes normally, fails, or is terminated.
Severity: INFO
| Attribute | Type | Required | Description |
|---|---|---|---|
session.id |
string | yes | Workbench session identifier. |
user.id |
string | yes | The user’s GUID. |
session.type |
string | yes | Normalized IDE name. Allowed values: RStudio, Positron, JupyterLab, VS Code. |
session.status |
string | yes | Lifecycle status. Allowed values: Started, Suspended, Terminated, Failed, Completed. |
session.r_version |
string | no | R version, when applicable. Emitted only when non-empty. |
session.project |
string | no | Initial project path. Emitted only when non-empty. |
session.cluster |
string | no | Job Launcher cluster name in load-balanced deployments. Emitted only when non-empty. |
session.exit_code |
int64 | no | Process exit code, on terminal events. |
session.exit_reason |
string | no | Human-readable exit classification. Allowed values: NormalExit, Killed, Crashed, MemoryLimitExceeded, TooManyOpenFiles, NotEnoughMemory, Unknown. |
session.startup_duration_ms |
int64 | no | Time from launch request to running, on Started events. |
workbench.session.transition
Emitted on every session state change. Use this event to reconstruct per-session timelines without enabling traces.
Severity: INFO
| Attribute | Type | Required | Description |
|---|---|---|---|
session.id |
string | yes | Workbench session identifier. |
session.status |
string | yes | The state the session has transitioned to. |
session.previous_status |
string | no | The state the session transitioned from. Emitted only when non-empty. |
transition_type |
string | yes | Classification of the transition. Allowed values: milestone, internal. |
user.id |
string | no | The user’s GUID. Emitted only when resolvable from the username. |
milestone transitions correspond to user-visible state changes (for example, running, suspended, finished); internal transitions cover intermediate states such as starting or resuming.
browser.session.launch
Emitted by the IDE-launch performance beacon in the user’s browser. Each session launch produces multiple events, one per phase. Heartbeat events fire periodically while the IDE is open.
Severity: DEBUG for heartbeat, INFO for all other phases.
| Attribute | Type | Required | Description |
|---|---|---|---|
session.id |
string | yes | Workbench session identifier. |
user.id |
string | no | The user’s GUID, resolved server-side from the authenticated username. Never trusted from the client payload. |
browser.ide |
string | yes | IDE type, emitted as a lowercase identifier rather than a display name. Allowed values: rstudio, jupyterlab, positron, vscode. |
browser.phase |
string | yes | Launch phase or heartbeat. Allowed values: fcp, load, app_init, ready, heartbeat, pagehide. |
browser.nav_id |
string | yes | Browser-generated ID for one IDE navigation. Use to correlate the phase events from a single launch. |
browser.last_phase_reached |
string | no | The most recent phase the browser observed before this event was sent. |
browser.click_to_phase_ms |
int64 | no | Milliseconds from the user’s click on the launch link to this phase. Present when click-time is known. |
browser.nav_start_to_phase_ms |
int64 | no | Milliseconds from navigationStart to this phase. Always derivable in modern browsers. |
browser.launch_t0_ms |
int64 | no | Browser-side T0 timestamp (ms since epoch). |
browser.timings.<key> |
int64 | string | no | Detailed PerformanceTiming-style breakdown attached to the first beacon of a launch only. The <key> suffix matches the underlying timing field. |
The launch milestones, in chronological order, are fcp (first contentful paint), load, app_init, and ready. heartbeat fires periodically while the IDE is active. pagehide fires when the user navigates away or closes the tab.
Trace spans
session.launch
Wraps a single session launch from request to running (or to terminal failure). The root span carries identity and routing attributes; one child span is open at a time, named after the current launch phase.
Span hierarchy:
session.launch (root)
├── session.launch.launching (child, first phase)
├── session.launch.starting (child, next phase)
├── session.launch.running (child, terminal on success)
└── session.launch.<other-state> (one child per intermediate state)
Child spans close as the session transitions out of the matching state. The root span closes when the session reaches running or fails.
Root span attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
session.id |
string | yes | Workbench session identifier. |
user.id |
string | yes | The user’s GUID. |
session.type |
string | yes | Normalized IDE name (see workbench.session for allowed values). |
session.cluster |
string | yes | Job Launcher cluster name. Empty string for non-load-balanced deployments. |
Child span names take the form session.launch.<state> where <state> is the activity state. Common values include launching, starting, running, suspending, and resuming. Child spans inherit the trace context from the root and carry no additional attributes; phase identity is encoded by the span name.
A failed launch closes the root span with status ERROR. A relaunch attempt arriving during an active launch closes the previous root span with status ERROR and message "overwritten", then opens a new one.