npm
Source
Description
@wolfstar/plugin-opentelemetry traces a @wolfstar/http-framework bot with
OpenTelemetry. It has two parts. You can use them together or apart:
- A preload,
@wolfstar/plugin-opentelemetry/preload, loaded with--import. It starts the OpenTelemetry SDK with the automatic instrumentations before the bot imports anything. It also handles the problems a Discord bot runs into when it does that. See What the preload handles. - A plugin,
@wolfstar/plugin-opentelemetry/plugin, that opens one span per interaction. Thepg,ioredis, orundicispans of a command then have the command as their parent, instead of floating in the trace of the HTTP request.
@opentelemetry/api is a peer dependency because the process must hold a single copy of it. The SDK, the
instrumentations, and the OTLP exporters are dependencies of this package.
The preload
Add the preload to the command that starts the bot:What the preload handles
- ES modules. The bot is ESM, so the SDK must register the
import-in-the-middleloader hook before anything is imported. A preload is the only place that runs early enough. - Worker threads. A worker thread inherits
--importand would start its own SDK, costing seconds and hundreds of megabytes each. The preload only starts the SDK on the main thread. SIGTERM. The preload shuts the SDK down onSIGTERMto export the last spans. ASIGTERMlistener stops the process from exiting on that signal, so the preload raises the signal again once no other listener is left. The broker consumer of aBOT_MODE=workerprocess does the same, and the two do not keep each other alive.- Tokens in URLs. Webhook and interaction callback URLs hold a token that lets anybody post there. The preload
replaces that token with
REDACTEDinundicispans before they start. - The gateway WebSocket.
@discordjs/wsopens the gateway with anhttprequest carryingUpgrade: websocket. Everything the bot does with the dispatches it receives afterwards runs in that request’s context, producing one trace that never closes. The preload does not trace WebSocket handshakes. - Sentry’s transport. Requests from
@sentry/nodecarry the key of the DSN in the query string. The preload does not trace them. - Two destinations. An OTLP exporter adds the
OTEL_EXPORTER_OTLP_*headers to the ones it receives, so a second exporter would send the first destination’s token to Sentry. The preload hides those variables while it creates the Sentry exporter.
The plugin
The plugin opens the interaction spans. It cannot start the SDK, so load the preload as well.Activating with modules
On framework 6.1 and later, list the ./module entry in modules in stars.config and write its options under the
opentelemetry key. It needs the optional @wolfstar/kit peer dependency, at ^0.2.0 || ^0.3.0.
[specifier, options] tuple of modules still works. Its options are merged over the opentelemetry key, so they win.
Without the stars CLI
Pass the ./plugin definePlugin factory to the plugins client option:
@opentelemetry/api hands out no-op spans and the plugin costs nothing.
Options
Set
interactions: false to turn them all off. Set interactions: true to use the defaults.
Spans
Each span is named<kind> <name>, for example command ping or component confirm. It carries these attributes:
When a run fails, the plugin records the exception and sets the span status to
ERROR.
The framework’s events do not wrap the run of a command. The plugin makes the span active with
context.attach from the *Run event, and
restores the previous context on *Finish. This needs a context manager that implements attach, such as the SDK’s
AsyncLocalStorageContextManager (@opentelemetry/api 1.10 and later). With a context manager that does not, the plugin still creates the spans,
but the work a command starts is not attached to them.flushTraces()
flushTraces() exports the spans waiting in the batch. Call it from a shutdown path before the process exits. It never
rejects, and does nothing when no SDK is running.
Sentry
@sentry/node 11 does not register a tracer provider by default, because enableOpenTelemetrySetup defaults to false.
This lets @wolfstar/plugin-opentelemetry own the SDK. Add Sentry’s openTelemetryIntegration() to Sentry.init to put
the errors Sentry captures on the active trace. Each error is then linked to the span of the command that raised it.