fix(nativets): forward OTEL-prefixed console logs to tracing events (#8937)

* fix(nativets): forward OTEL-prefixed console logs to tracing events

Nativets jobs run in-process and bypass the handle_child.rs stdout loop
where `OTEL: ` lines are turned into `tracing::event!` calls when
`OTEL_JOB_LOGS=true`. Apply the same prefix handling in the nativets
log receiver so `console.log("OTEL: ...")` reaches the OTEL exporter
like it does for other runtimes.

Moves `OTEL_JOB_LOGS` and `OTEL_PREFIX` into windmill-common so both
crates share the same definition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nativets): emit job_log tracing target so logs reach OTEL bridge

For non-native runtimes, `lines_to_stream` → `process_streaming_log_lines`
(EE) emits every stdout line as `tracing::info!(target: "windmill:job_log", ...)`,
which is picked up by the EE `LogContextBridge` and exported to OTEL
(the bridge's filter is `EnvFilter` only, not the targets filter that
drops `windmill:job_log` from stdout/file sinks).

Nativets delivers logs in-process via a channel, so it never goes
through that path and console.log output only reached the Windmill UI.
Emit the same `windmill:job_log` event per line from the nativets log
receiver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-04-24 19:42:05 +00:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 749aff024f
commit e732004180
3 changed files with 26 additions and 3 deletions
@@ -37,12 +37,17 @@ fn compact_layer<S>() -> Layer<S, format::DefaultFields, format::Format<format::
lazy_static::lazy_static! {
pub static ref JSON_FMT: bool = std::env::var("JSON_FMT").map(|x| x == "true").unwrap_or(false);
pub static ref QUIET_MODE: bool = std::env::var("QUIET").map(|x| x == "true" || x == "1").unwrap_or(false);
pub static ref OTEL_JOB_LOGS: bool = std::env::var("OTEL_JOB_LOGS").ok().is_some_and(|x| x == "1" || x == "true");
}
/// Target name for verbose logs that should be filtered in quiet mode.
/// Use `tracing::info!(target: windmill_common::tracing_init::VERBOSE_TARGET, ...)` for logs that should be suppressed in quiet mode.
pub const VERBOSE_TARGET: &str = "windmill_verbose";
/// Prefix used by user scripts to emit a log line as an OTEL tracing event
/// when `OTEL_JOB_LOGS=true`. Stripped before forwarding to the tracing layer.
pub const OTEL_PREFIX: &str = "OTEL: ";
/// Creates a Targets filter that optionally filters out verbose logs when quiet mode is enabled.
fn create_targets_filter(default_env_filter: LevelFilter) -> Targets {
let targets =
@@ -579,6 +579,7 @@ pub async fn eval_fetch_timeout(
));
}
let w_id_for_tracing = w_id.to_string();
let result_f = tokio::task::spawn_blocking(move || {
let CreatedRuntime { mut js_runtime, mut log_receiver, mut memory_limit_rx } =
create_nativets_runtime(ann, spread)?;
@@ -604,11 +605,30 @@ pub async fn eval_fetch_timeout(
tracing::error!("failed to send extra logs: {e}");
}
}
let w_id_for_tracing = w_id_for_tracing;
let handle = tokio::spawn(async move {
let mut result_stream = String::new();
let mut is_stream = false;
while let Some(log) = log_receiver.recv().await {
use windmill_common::result_stream::extract_stream_from_logs;
use windmill_common::tracing_init::{OTEL_JOB_LOGS, OTEL_PREFIX};
// Mirror `process_streaming_log_lines` (EE) + the OTEL_JOB_LOGS
// hook from handle_child.rs, neither of which runs for nativets
// since nativets delivers logs in-process via the log channel.
for line in log.lines() {
tracing::info!(
target: "windmill:job_log",
job_id = ?job_id,
workspace_id = ?w_id_for_tracing,
"{line}"
);
if *OTEL_JOB_LOGS {
if let Some(otel_suffix) = line.strip_prefix(OTEL_PREFIX) {
tracing::event!(tracing::Level::INFO, otel_suffix);
}
}
}
if let Some(stream) = extract_stream_from_logs(&log.trim_end_matches("\n")) {
if !is_stream {
+1 -3
View File
@@ -57,11 +57,10 @@ use crate::job_logger_oss::process_streaming_log_lines;
use crate::worker_utils::{ping_job_status, update_worker_ping_from_job};
use crate::{MAX_RESULT_SIZE, MAX_WAIT_FOR_SIGINT, MAX_WAIT_FOR_SIGTERM};
use windmill_common::tracing_init::{QUIET_MODE, VERBOSE_TARGET};
use windmill_common::tracing_init::{OTEL_JOB_LOGS, OTEL_PREFIX, QUIET_MODE, VERBOSE_TARGET};
lazy_static::lazy_static! {
pub static ref SLOW_LOGS: bool = std::env::var("SLOW_LOGS").ok().is_some_and(|x| x == "1" || x == "true");
pub static ref OTEL_JOB_LOGS: bool = std::env::var("OTEL_JOB_LOGS").ok().is_some_and(|x| x == "1" || x == "true");
}
// - kill windows process along with all child processes
@@ -357,7 +356,6 @@ pub async fn handle_child(
}
}
pub const OTEL_PREFIX: &str = "OTEL: ";
pub const WAC_STEP_PREFIX: &str = "WM_WAC_STEP: ";
pub async fn write_lines(