Getting started
Wiring the plugin into a Tauri v2 app: depend on one release tag on both sides, grant the capability, register the plugin, set up the page, and continue the page's trace in your commands. Only the Rust half is required; an app that does not instrument its frontend skips the JS package, the capability and the last two steps.
1. Depend on one tag, on both sides
The crate and the JS package are consumed by git ref, like the organisation's other Tauri plugins. Pin a tag, so both always come from the same release.
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-otel = { git = "https://github.com/vaam-apps/tauri-otel", tag = "v0.1.2" }
tracing = "0.1"// package.json
"dependencies": {
"tauri-plugin-otel-api": "github:vaam-apps/tauri-otel#v0.1.2",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-trace-web": "^2"
}@opentelemetry/api is a peer dependency of the JS package. @opentelemetry/sdk-trace-web is what provides the WebTracerProvider and BatchSpanProcessor used in step 4.
Always move both references to a new tag together. The TypeScript producer and the Rust consumer share one wire format, pinned by fixtures/wire-span.json.
pnpm 10 must be allowed to build the JS package
The package is git-hosted and builds dist-js/ in its prepare script, so dist-js/ is never committed. pnpm 10 refuses to run a git-hosted package's build scripts until it is allowlisted, and fails the install with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED:
# pnpm-workspace.yaml
onlyBuiltDependencies:
- tauri-plugin-otel-apinpm needs nothing extra.
2. Grant the capability
Allow the webview to call the plugin in one of the app's capabilities:
{ "identifier": "main", "windows": ["main"], "permissions": ["otel:default"] }otel:default allows the plugin's two commands, log and export_spans. Without it the page's telemetry is refused, the guest package prints one console.warn, and the Rust half still exports. An app that does not instrument its frontend should leave it out rather than narrow it. See Permissions.
3. Register the plugin, first
fn main() {
tauri::Builder::default()
.plugin(
tauri_plugin_otel::Builder::new("vaam-vendor", "production")
.endpoint("https://otel.vaam.store")
.build_id(env!("BUILD_ID")) // e.g. "1.4.2+812", stamped by CI
.filter("info,vaam_app_core=debug")
.build(),
)
// ...other plugins after it
.run(tauri::generate_context!())
.expect("error while running tauri application");
}- Register it before the other plugins. It installs the global
tracingsubscriber in its own setup, and anything logged before that goes nowhere. - Both arguments to
Builder::neware required, and neither is defaulted. The first isservice.name(the app, not the build); the second isdeployment.environment.name(the tier this build ships to). The version is not asked for: it is read fromtauri.conf.jsonthrough Tauri'sPackageInfo. - Not calling
endpointis the off switch. Nothing is exported, and nothing else changes. - It is the app's only
tracingsubscriber. Do not also calltracing_subscriber::fmt().init()or register another logging plugin; use.console(true)for stderr output, which is already on in debug builds.
Every builder method is described in Configuration.
4. Set up the page
import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { TauriSpanExporter, captureErrors, log, tracedInvoke } from 'tauri-plugin-otel-api'
const provider = new WebTracerProvider({
spanProcessors: [new BatchSpanProcessor(new TauriSpanExporter())],
})
provider.register()
captureErrors()
log.info('listing editor opened', { listing: id }) // withheld unless an error follows
await tracedInvoke('save_listing', { draft }) // one trace, page to RustThe provider's own resource is ignored: every span is re-stamped with the app's resource on the Rust side, so do not configure service.name in the page. Outside Tauri, in a plain browser during development, every function is a quiet no-op, so the same frontend code runs there unchanged.
The JavaScript API page lists every export.
5. Continue the trace in a command
A command that should continue the page's trace takes a TraceParent argument. See Continuing the webview's trace.
Check that it works
Every launch emits exactly one WARN, which debug builds print to stderr:
my-app 1.4.2 (development) exporting to http://localhost:4318; records at WARN and aboveWhen nothing is exported it reads not exporting (<reason>) instead. Troubleshooting shows how to point a debug build at a local collector and check one traced call end to end.