mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
feat: enrich OTEL spans with job_kind, trigger_kind, trigger, created_by, and script_hash
Add five new attributes to the `job` and `job_postprocessing` tracing spans so that OTEL-consuming backends (Sentry, Honeycomb, Datadog, etc.) can filter and group telemetry by how a job was triggered and what type it is. New span attributes: - `job_kind` — Script, Flow, AppScript, AIAgent, Preview, etc. - `created_by` — the user or system identity that queued the job - `trigger_kind` — schedule, webhook, kafka, http, sqs, etc. - `trigger` — the schedule/trigger path (when applicable) - `script_hash` — hex hash of the script version that ran Also adds `JobKind::as_str()` for a consistent lowercase string representation, following the same pattern as `ScriptLang::as_str()`. Existing attributes (job_id, workspace_id, script_path, language, tag, flow_step_id, parent_job, root_job) are unchanged. Note: the EE `full_job` span in `otel_ee.rs` and the log records emitted by `job_logger_ee.rs` would also benefit from these attributes. This PR covers only the public-repo spans; a follow-up EE change would propagate the same fields to logs and the full_job span.
This commit is contained in:
@@ -395,6 +395,14 @@ async fn test_root_job_span_created_on_success() {
|
||||
attrs.contains(&"script_path"),
|
||||
"missing script_path attribute"
|
||||
);
|
||||
assert!(
|
||||
attrs.contains(&"job_kind"),
|
||||
"missing job_kind attribute"
|
||||
);
|
||||
assert!(
|
||||
attrs.contains(&"created_by"),
|
||||
"missing created_by attribute"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -105,6 +105,30 @@ pub enum JobStatus {
|
||||
}
|
||||
|
||||
impl JobKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
JobKind::Script => "script",
|
||||
JobKind::Script_Hub => "script_hub",
|
||||
JobKind::Preview => "preview",
|
||||
JobKind::Dependencies => "dependencies",
|
||||
JobKind::Flow => "flow",
|
||||
JobKind::FlowPreview => "flowpreview",
|
||||
JobKind::SingleStepFlow => "singlestepflow",
|
||||
JobKind::Identity => "identity",
|
||||
JobKind::FlowDependencies => "flowdependencies",
|
||||
JobKind::AppDependencies => "appdependencies",
|
||||
JobKind::Noop => "noop",
|
||||
JobKind::DeploymentCallback => "deploymentcallback",
|
||||
JobKind::FlowScript => "flowscript",
|
||||
JobKind::FlowNode => "flownode",
|
||||
JobKind::AppScript => "appscript",
|
||||
JobKind::AIAgent => "aiagent",
|
||||
JobKind::UnassignedScript => "unassigned_script",
|
||||
JobKind::UnassignedFlow => "unassigned_flow",
|
||||
JobKind::UnassignedSinglestepFlow => "unassigned_singlestepflow",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_flow(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
||||
@@ -86,6 +86,11 @@ async fn process_jc(
|
||||
script_path = field::Empty,
|
||||
flow_step_id = field::Empty,
|
||||
parent_job = field::Empty,
|
||||
job_kind = %jc.job.kind.as_str(),
|
||||
created_by = %jc.job.created_by,
|
||||
trigger_kind = field::Empty,
|
||||
trigger = field::Empty,
|
||||
script_hash = field::Empty,
|
||||
otel.name = field::Empty,
|
||||
success = %success,
|
||||
labels = field::Empty,
|
||||
@@ -100,6 +105,11 @@ async fn process_jc(
|
||||
script_path = field::Empty,
|
||||
flow_step_id = field::Empty,
|
||||
parent_job = field::Empty,
|
||||
job_kind = %jc.job.kind.as_str(),
|
||||
created_by = %jc.job.created_by,
|
||||
trigger_kind = field::Empty,
|
||||
trigger = field::Empty,
|
||||
script_hash = field::Empty,
|
||||
otel.name = field::Empty,
|
||||
success = %success,
|
||||
error.message = field::Empty,
|
||||
@@ -141,6 +151,15 @@ async fn process_jc(
|
||||
if let Some(root_job) = jc.job.flow_innermost_root_job.as_ref() {
|
||||
span.record("root_job", root_job.to_string().as_str());
|
||||
}
|
||||
if let Some(trigger_kind) = jc.job.trigger_kind.as_ref() {
|
||||
span.record("trigger_kind", trigger_kind.to_string().as_str());
|
||||
}
|
||||
if let Some(trigger) = jc.job.trigger.as_ref() {
|
||||
span.record("trigger", trigger.as_str());
|
||||
}
|
||||
if let Some(script_hash) = jc.job.runnable_id.as_ref() {
|
||||
span.record("script_hash", script_hash.to_string().as_str());
|
||||
}
|
||||
if !success {
|
||||
if let Ok(result_error) = serde_json::from_str::<ErrorMessage>(jc.result.get()) {
|
||||
span.record("error.message", result_error.message.as_str());
|
||||
|
||||
@@ -1316,6 +1316,11 @@ pub fn create_span_with_name(
|
||||
script_path = field::Empty,
|
||||
flow_step_id = field::Empty,
|
||||
parent_job = field::Empty,
|
||||
job_kind = %arc_job.kind.as_str(),
|
||||
created_by = %arc_job.created_by,
|
||||
trigger_kind = field::Empty,
|
||||
trigger = field::Empty,
|
||||
script_hash = field::Empty,
|
||||
otel.name = field::Empty
|
||||
);
|
||||
|
||||
@@ -1342,6 +1347,15 @@ pub fn create_span_with_name(
|
||||
if let Some(hostname) = hostname {
|
||||
span.record("hostname", hostname);
|
||||
}
|
||||
if let Some(trigger_kind) = arc_job.trigger_kind.as_ref() {
|
||||
span.record("trigger_kind", trigger_kind.to_string().as_str());
|
||||
}
|
||||
if let Some(trigger) = arc_job.trigger.as_ref() {
|
||||
span.record("trigger", trigger.as_str());
|
||||
}
|
||||
if let Some(script_hash) = arc_job.runnable_id.as_ref() {
|
||||
span.record("script_hash", script_hash.to_string().as_str());
|
||||
}
|
||||
|
||||
windmill_common::otel_oss::set_span_parent(&span, &rj);
|
||||
span
|
||||
|
||||
Reference in New Issue
Block a user