mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
424ca59dfe
* fix: auto-heal corrupted python runtime cache on remote workers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "fix: auto-heal corrupted python runtime cache on remote workers"
This reverts commit 0ea013a554.
* feat: make WINDMILL_DIR configurable via environment variable
Allow users to configure the base directory for Windmill's tmp/cache files
via the WINDMILL_DIR env var (default: /tmp/windmill). This fixes Python
runtime cache corruption on RHEL systems where systemd-tmpfiles-clean
removes files from /tmp.
Converts TMP_DIR (renamed to WINDMILL_DIR) and all derived cache directory
constants from compile-time const &str (concatcp!) to runtime lazy_static
String values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref ERROR_DIR lazy_static for AsRef<Path> and Display traits
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref to branch name for CI compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref lazy_static constants in all executor files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: panic if WINDMILL_DIR has trailing slash
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: also reject trailing backslash in WINDMILL_DIR for Windows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref GO_BIN_CACHE_DIR in test utils
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace remaining hardcoded /tmp/windmill paths and validate empty WINDMILL_DIR
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: nsjail powershell mount dst, Windows path assumptions, pwsh deref consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore Windows /tmp path translation in go and bun executors
The Windows path translation replaces /tmp with the Windows temp dir
(e.g. C:\tmp) before normalizing slashes. Without this, the default
WINDMILL_DIR=/tmp/windmill produces paths without a drive letter on
Windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 6fd5a2ce908235a17975ad4dbdf0051cd89334f3
This commit updates the EE repository reference after PR #436 was merged in windmill-ee-private.
Previous ee-repo-ref: e8c03e16720833230ebd1878b4c63642ecc6c80f
New ee-repo-ref: 6fd5a2ce908235a17975ad4dbdf0051cd89334f3
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
272 lines
9.1 KiB
Rust
272 lines
9.1 KiB
Rust
/*
|
|
* Author: Ruben Fiszel
|
|
* Copyright: Windmill Labs, Inc 2022
|
|
* This file and its contents are licensed under the AGPLv3 License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-AGPL for a copy of the license.
|
|
*/
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, RwLock},
|
|
};
|
|
use tracing::{level_filters::LevelFilter, Event};
|
|
use tracing_appender::non_blocking::{NonBlockingBuilder, WorkerGuard};
|
|
use tracing_subscriber::layer::Context;
|
|
use tracing_subscriber::{
|
|
filter::Targets,
|
|
fmt::{format, Layer},
|
|
prelude::*,
|
|
EnvFilter,
|
|
};
|
|
|
|
use crate::utils::Mode;
|
|
|
|
fn json_layer<S>() -> Layer<S, format::JsonFields, format::Format<format::Json>> {
|
|
tracing_subscriber::fmt::layer()
|
|
.json()
|
|
.flatten_event(true)
|
|
.with_span_list(false)
|
|
.with_current_span(true)
|
|
}
|
|
|
|
fn compact_layer<S>() -> Layer<S, format::DefaultFields, format::Format<format::Compact>> {
|
|
tracing_subscriber::fmt::layer().compact()
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// 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";
|
|
|
|
/// 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 =
|
|
Targets::new().with_target("windmill:job_log", tracing::level_filters::LevelFilter::OFF);
|
|
|
|
if *QUIET_MODE {
|
|
targets
|
|
.with_target(VERBOSE_TARGET, tracing::level_filters::LevelFilter::OFF)
|
|
.with_default(default_env_filter)
|
|
} else {
|
|
targets.with_default(default_env_filter)
|
|
}
|
|
}
|
|
|
|
pub const LOGS_SERVICE: &str = "logs/services/";
|
|
|
|
lazy_static::lazy_static! {
|
|
pub static ref TMP_WINDMILL_LOGS_SERVICE: String = format!("{}/{}", *crate::worker::WINDMILL_DIR, LOGS_SERVICE);
|
|
}
|
|
|
|
pub fn initialize_tracing(
|
|
hostname: &str,
|
|
mode: &Mode,
|
|
environment: &str,
|
|
) -> (WorkerGuard, crate::otel_oss::OtelProvider) {
|
|
let style = std::env::var("RUST_LOG_STYLE").unwrap_or_else(|_| "auto".into());
|
|
|
|
let rust_log_env = std::env::var("RUST_LOG");
|
|
let rust_log_stdout_env = std::env::var("RUST_LOG_STDOUT");
|
|
|
|
if rust_log_env
|
|
.as_ref()
|
|
.is_ok_and(|x| x == "debug" || x == "info")
|
|
{
|
|
unsafe {
|
|
std::env::set_var(
|
|
"RUST_LOG",
|
|
&format!("windmill={}", rust_log_env.as_ref().unwrap()),
|
|
)
|
|
}
|
|
} else if rust_log_env.as_ref().is_ok_and(|x| x == "sqlxdebug") {
|
|
unsafe {
|
|
std::env::set_var("RUST_LOG", "windmill=debug,sqlx=debug");
|
|
}
|
|
};
|
|
|
|
let default_env_filter = if rust_log_env.is_ok_and(|x| x == "debug" || x == "sqlxdebug") {
|
|
LevelFilter::DEBUG
|
|
} else {
|
|
LevelFilter::INFO
|
|
};
|
|
|
|
let meter_provider = crate::otel_oss::init_meter_provider(mode, hostname, environment);
|
|
|
|
#[cfg(all(feature = "otel", feature = "enterprise"))]
|
|
let opentelemetry = crate::otel_oss::init_otlp_tracer(mode, hostname, environment)
|
|
.map(|x| tracing_opentelemetry::layer().with_tracer(x));
|
|
|
|
#[cfg(not(all(feature = "otel", feature = "enterprise")))]
|
|
let opentelemetry: Option<EnvFilter> = None;
|
|
|
|
let logs_bridge = crate::otel_oss::init_logs_bridge(&mode, hostname, environment);
|
|
|
|
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
|
|
|
let log_dir = format!("{}/{}/", *TMP_WINDMILL_LOGS_SERVICE, hostname);
|
|
std::fs::create_dir_all(&log_dir).unwrap();
|
|
let file_appender = RollingFileAppender::builder()
|
|
.rotation(Rotation::MINUTELY)
|
|
.filename_prefix(format!("{}.log", hostname))
|
|
.max_log_files(20)
|
|
.build(log_dir)
|
|
.expect("Can build tracing file appender");
|
|
|
|
let (log_file_writer, _guard) = NonBlockingBuilder::default()
|
|
.lossy(false)
|
|
.finish(file_appender);
|
|
|
|
// let job_logs_filter = tracing_subscriber::filter::Targets::new()
|
|
// .with_target("windmill:job_log", tracing::Level::TRACE);
|
|
|
|
// Create the base filter for file writer (always uses RUST_LOG)
|
|
let file_env_filter = EnvFilter::builder()
|
|
.with_default_directive(tracing::level_filters::LevelFilter::ERROR.into())
|
|
.from_env_lossy();
|
|
|
|
// Create the filter for stdout (uses RUST_LOG_STDOUT if available, otherwise RUST_LOG)
|
|
let stdout_env_filter = if rust_log_stdout_env.is_ok() {
|
|
// Temporarily set RUST_LOG to RUST_LOG_STDOUT value to parse it
|
|
let original_rust_log = std::env::var("RUST_LOG").ok();
|
|
unsafe {
|
|
std::env::set_var("RUST_LOG", rust_log_stdout_env.unwrap());
|
|
}
|
|
let filter = EnvFilter::builder()
|
|
.with_default_directive(tracing::level_filters::LevelFilter::ERROR.into())
|
|
.from_env_lossy();
|
|
unsafe {
|
|
// Restore original RUST_LOG
|
|
match original_rust_log {
|
|
Some(val) => std::env::set_var("RUST_LOG", val),
|
|
None => std::env::remove_var("RUST_LOG"),
|
|
}
|
|
}
|
|
filter
|
|
} else {
|
|
file_env_filter.clone()
|
|
};
|
|
|
|
// Create a common filter for OTEL logs bridge and tracing layer to respect RUST_LOG
|
|
let otel_logs_filter = file_env_filter.clone();
|
|
|
|
// Apply filter to the opentelemetry tracing layer to prevent debug events from being attached to spans
|
|
#[cfg(all(feature = "otel", feature = "enterprise"))]
|
|
let opentelemetry_filtered = {
|
|
let otel_tracing_filter = file_env_filter.clone();
|
|
opentelemetry.map(|layer| layer.with_filter(otel_tracing_filter))
|
|
};
|
|
|
|
#[cfg(not(all(feature = "otel", feature = "enterprise")))]
|
|
let opentelemetry_filtered = opentelemetry;
|
|
|
|
let base_layer = tracing_subscriber::registry()
|
|
.with(logs_bridge.with_filter(otel_logs_filter))
|
|
.with(opentelemetry_filtered);
|
|
|
|
match *JSON_FMT {
|
|
true => {
|
|
// Stdout layer with its own filter
|
|
let stdout_layer = json_layer()
|
|
.with_writer(std::io::stdout)
|
|
.flatten_event(true)
|
|
.with_filter(stdout_env_filter)
|
|
.with_filter(create_targets_filter(default_env_filter));
|
|
|
|
// File layer with its own filter
|
|
let file_layer = json_layer()
|
|
.with_writer(log_file_writer)
|
|
.flatten_event(true)
|
|
.with_filter(file_env_filter)
|
|
.with_filter(create_targets_filter(default_env_filter));
|
|
|
|
base_layer
|
|
.with(stdout_layer)
|
|
.with(file_layer)
|
|
.with(CountingLayer::new())
|
|
.init()
|
|
}
|
|
false => {
|
|
// Stdout layer with its own filter
|
|
let stdout_layer = compact_layer()
|
|
.with_writer(std::io::stdout)
|
|
.with_ansi(style.to_lowercase() != "never")
|
|
.with_file(true)
|
|
.with_line_number(true)
|
|
.with_target(false)
|
|
.with_filter(stdout_env_filter)
|
|
.with_filter(create_targets_filter(default_env_filter));
|
|
|
|
// File layer with its own filter
|
|
let file_layer = compact_layer()
|
|
.with_writer(log_file_writer)
|
|
.with_ansi(false) // No ANSI codes in log files
|
|
.with_file(true)
|
|
.with_line_number(true)
|
|
.with_target(false)
|
|
.with_filter(file_env_filter)
|
|
.with_filter(create_targets_filter(default_env_filter));
|
|
|
|
base_layer
|
|
.with(stdout_layer)
|
|
.with(file_layer)
|
|
.with(CountingLayer::new())
|
|
.init()
|
|
}
|
|
}
|
|
(_guard, meter_provider)
|
|
}
|
|
|
|
lazy_static::lazy_static! {
|
|
pub static ref LOG_COUNTING_BY_MIN: Arc<RwLock<HashMap<String, LogCounter>>> = Arc::new(RwLock::new(HashMap::new()));
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LogCounter {
|
|
pub non_error_count: usize,
|
|
pub error_count: usize,
|
|
}
|
|
|
|
impl LogCounter {
|
|
fn new() -> Self {
|
|
LogCounter { non_error_count: 0, error_count: 0 }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct CountingLayer {}
|
|
|
|
impl CountingLayer {
|
|
pub fn new() -> Self {
|
|
CountingLayer {}
|
|
}
|
|
}
|
|
|
|
pub const LOG_TIMESTAMP_FMT: &str = "%Y-%m-%d-%H-%M";
|
|
|
|
impl<S> tracing_subscriber::Layer<S> for CountingLayer
|
|
where
|
|
S: tracing::Subscriber,
|
|
{
|
|
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
|
|
let level = *event.metadata().level();
|
|
|
|
let date_str = chrono::Utc::now().format(LOG_TIMESTAMP_FMT).to_string();
|
|
let counters = LOG_COUNTING_BY_MIN.write();
|
|
if let Ok(mut counters) = counters {
|
|
let counter = counters.entry(date_str).or_insert(LogCounter::new());
|
|
if level == tracing::Level::ERROR {
|
|
counter.error_count += 1;
|
|
} else {
|
|
counter.non_error_count += 1;
|
|
}
|
|
} else {
|
|
println!("Error getting lock for log counting");
|
|
}
|
|
}
|
|
}
|