feat: Add OTel auto-instrumentation for Python and TypeScript scripts (EE)

Add OpenTelemetry auto-instrumentation as an enterprise feature that
automatically instruments Python and TypeScript scripts to collect traces.

Backend changes:
- Add database migration for job_otel_traces table to store trace spans
- Add otel_auto_instrumentation_oss.rs with OSS stub implementation
- Inject OTel environment variables in Python, Bun, and Deno executors
- Add API endpoint GET /api/w/{workspace}/jobs/get_otel_traces/{id}
- Add OTEL_AUTO_INSTRUMENTATION_SETTING constant

Frontend changes:
- Add OTel auto-instrumentation settings in instance settings (OTEL/Prom tab)
- Add JobOtelTraces.svelte component for viewing traces
- Add "Traces" tab to job details page

When enabled, scripts using OpenTelemetry libraries will automatically
send traces to a built-in collector. Traces are stored in the database
and can be viewed in the job details.

Closes #7512

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
claude[bot]
2026-01-07 04:23:42 +00:00
co-authored by Claude Opus 4.5
parent b26d2fe641
commit e38b316aad
13 changed files with 619 additions and 2 deletions
@@ -41,6 +41,15 @@ use windmill_common::variables::get_secret_value_as_admin;
use std::env::var;
use windmill_queue::{append_logs, CanceledBy, PrecomputedAgentInfo};
#[cfg(feature = "enterprise")]
use crate::otel_auto_instrumentation_ee::{
get_otel_auto_instrumentation_config, get_otel_python_env_vars,
};
#[cfg(not(feature = "enterprise"))]
use crate::otel_auto_instrumentation_oss::{
get_otel_auto_instrumentation_config, get_otel_python_env_vars,
};
use process_wrap::tokio::TokioChildWrapper;
lazy_static::lazy_static! {
@@ -812,6 +821,18 @@ mount {{
job.id
);
// Get OTel auto-instrumentation env vars (EE feature)
let otel_envs: Vec<(String, String)> = if let Connection::Sql(db) = conn {
let otel_config = get_otel_auto_instrumentation_config(db).await;
if otel_config.enabled && otel_config.python_enabled {
get_otel_python_env_vars(&job.id, &job.workspace_id, &script_path, &otel_config)
} else {
vec![]
}
} else {
vec![]
};
let child = if !*DISABLE_NSJAIL {
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
nsjail_cmd
@@ -820,6 +841,7 @@ mount {{
// inject PYTHONPATH here - for some reason I had to do it in nsjail conf
.envs(reserved_variables)
.envs(PROXY_ENVS.clone())
.envs(otel_envs.clone())
.env("PATH", PATH_ENV.as_str())
.env("TZ", TZ_ENV.as_str())
.env("BASE_INTERNAL_URL", base_internal_url)
@@ -845,6 +867,7 @@ mount {{
.env_clear()
.envs(envs)
.envs(reserved_variables)
.envs(otel_envs)
.env("PATH", PATH_ENV.as_str())
.env("TZ", TZ_ENV.as_str())
.env("BASE_INTERNAL_URL", base_internal_url)