fix(obs): honor profiling export contracts (#4433)

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-07-08 16:22:47 +08:00
committed by GitHub
parent 5aa25a3047
commit d7b55c9762
4 changed files with 130 additions and 35 deletions
+24 -21
View File
@@ -92,30 +92,33 @@ pub(crate) fn init_telemetry(config: &OtelConfig) -> Result<OtelGuard, Telemetry
|| config.metric_endpoint.as_deref().map(|s| !s.is_empty()).unwrap_or(false)
|| config.log_endpoint.as_deref().map(|s| !s.is_empty()).unwrap_or(false);
if has_obs {
return otel::init_observability_http(config, logger_level, is_production);
}
// ── Rule 2 & 3: Local logging (file or stdout) ────────────────────────────
// `init_local_logging` internally decides between file and stdout mode
// based on whether a log directory is configured.
//
// We check the environment variable here (rather than relying solely on the
// config struct) to honour dynamic overrides set after config construction.
let user_set_log_dir = get_env_opt_str(ENV_OBS_LOG_DIRECTORY);
let effective_config = if user_set_log_dir.as_deref().filter(|d| !d.is_empty()).is_some() {
// Environment variable is set: ensure the config reflects it so that
// `init_local_logging` picks up the value even if the struct was built
// before the env var was set.
std::borrow::Cow::Owned(OtelConfig {
log_directory: user_set_log_dir,
..config.clone()
})
let mut guard = if has_obs {
otel::init_observability_http(config, logger_level, is_production)?
} else {
std::borrow::Cow::Borrowed(config)
// ── Rule 2 & 3: Local logging (file or stdout) ────────────────────────
// `init_local_logging` internally decides between file and stdout mode
// based on whether a log directory is configured.
//
// We check the environment variable here (rather than relying solely on the
// config struct) to honour dynamic overrides set after config construction.
let user_set_log_dir = get_env_opt_str(ENV_OBS_LOG_DIRECTORY);
let effective_config = if user_set_log_dir.as_deref().filter(|d| !d.is_empty()).is_some() {
// Environment variable is set: ensure the config reflects it so that
// `init_local_logging` picks up the value even if the struct was built
// before the env var was set.
std::borrow::Cow::Owned(OtelConfig {
log_directory: user_set_log_dir,
..config.clone()
})
} else {
std::borrow::Cow::Borrowed(config)
};
local::init_local_logging(&effective_config, logger_level, is_production)?
};
local::init_local_logging(&effective_config, logger_level, is_production)
guard.profiling_agent = otel::init_profiler(config);
Ok(guard)
}
#[cfg(test)]
+48 -12
View File
@@ -65,7 +65,7 @@ use rustfs_config::{
};
use std::collections::HashMap;
use std::{fs, io::IsTerminal, time::Duration};
use tracing::info;
use tracing::{info, warn};
use tracing_error::ErrorLayer;
use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
use tracing_subscriber::{Layer, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt};
@@ -172,8 +172,6 @@ pub(super) fn init_observability_http(
// ── Meter provider (HTTP) ─────────────────────────────────────────────────
let meter_provider = build_meter_provider(&metric_ep, config, res.clone(), &service_name, use_stdout)?;
let profiling_agent = init_profiler(config);
// ── Logger Logic ──────────────────────────────────────────────────────────
// Logging is the only signal that may intentionally route to either OTLP
// or local files depending on configuration completeness.
@@ -316,7 +314,7 @@ pub(super) fn init_observability_http(
tracer_provider,
meter_provider,
logger_provider,
profiling_agent,
profiling_agent: None,
tracing_guard,
stdout_guard,
cleanup_handle,
@@ -517,7 +515,7 @@ fn build_logger_provider(
feature = "pyroscope",
any(target_os = "macos", all(target_os = "linux", target_env = "gnu", target_arch = "x86_64"))
))]
fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
pub(super) fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
use pyroscope::backend::{BackendConfig, PprofConfig, pprof_backend};
use pyroscope::pyroscope::PyroscopeAgentBuilder;
use rustfs_config::VERSION;
@@ -529,10 +527,19 @@ fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
return None;
}
let endpoint = config.profiling_endpoint.as_ref()?.as_str();
if endpoint.is_empty() {
let Some(endpoint) = config
.profiling_endpoint
.as_deref()
.map(str::trim)
.filter(|endpoint| !endpoint.is_empty())
else {
warn!(
backend = "pyroscope",
result = "profiling_endpoint_missing",
"Profiling export is enabled but no profiling endpoint was configured"
);
return None;
}
};
// Configure Pyroscope Agent
let backend = pprof_backend(PprofConfig::default(), BackendConfig::default());
@@ -540,15 +547,33 @@ fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
let version = config.service_version.as_deref().unwrap_or(VERSION);
let sample_rate = 100; // 100 Hz
let agent = PyroscopeAgentBuilder::new(endpoint, service_name, sample_rate, "pyroscope-rs", "1.0.1", backend)
let agent = match PyroscopeAgentBuilder::new(endpoint, service_name, sample_rate, "pyroscope-rs", "1.0.1", backend)
.tags(vec![("version", version), ("profile_type", "cpu")])
.build()
.ok()?;
{
Ok(agent) => agent,
Err(err) => {
warn!(
backend = "pyroscope",
endpoint,
result = "profiling_agent_build_failed",
error = %err,
"Profiling export agent initialization failed"
);
return None;
}
};
match agent.start() {
Ok(agent) => Some(agent),
Err(err) => {
eprintln!("Pyroscope agent start error: {err:?}");
warn!(
backend = "pyroscope",
endpoint,
result = "profiling_agent_start_failed",
error = ?err,
"Profiling export agent failed to start"
);
None
}
}
@@ -558,7 +583,18 @@ fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
feature = "pyroscope",
any(target_os = "macos", all(target_os = "linux", target_env = "gnu", target_arch = "x86_64"))
)))]
fn init_profiler(_config: &OtelConfig) -> Option<ProfilingAgent> {
pub(super) fn init_profiler(config: &OtelConfig) -> Option<ProfilingAgent> {
if config
.profiling_export_enabled
.unwrap_or(rustfs_config::DEFAULT_OBS_PROFILING_EXPORT_ENABLED)
{
warn!(
backend = "pyroscope",
result = "profiling_feature_not_compiled",
required_feature = "pyroscope",
"Profiling export is enabled but this binary was built without Pyroscope support"
);
}
None
}