mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-12 23:52:45 +00:00
fix(obs): pass RustFS build version to telemetry (#8425)
fix(obs): align telemetry version with build identity Co-authored-by: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
@@ -18,10 +18,8 @@ use const_str::concat;
|
||||
/// Default value: RustFS
|
||||
/// Environment variable: RUSTFS_APP_NAME
|
||||
pub const APP_NAME: &str = "RustFS";
|
||||
/// Application version
|
||||
/// Default value: 1.0.0
|
||||
/// Environment variable: RUSTFS_VERSION
|
||||
pub const VERSION: &str = "1.0.0";
|
||||
/// Cargo package version for this workspace member.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Default configuration logger level
|
||||
/// Default value: error
|
||||
@@ -39,12 +37,6 @@ pub const SAMPLE_RATIO: f64 = 1.0;
|
||||
/// Default value: 30
|
||||
pub const METER_INTERVAL: u64 = 30;
|
||||
|
||||
/// Default configuration service version
|
||||
/// Default value: 1.0.0
|
||||
/// Environment variable: RUSTFS_OBS_SERVICE_VERSION
|
||||
/// Uses the same value as VERSION constant
|
||||
pub const SERVICE_VERSION: &str = "1.0.0";
|
||||
|
||||
/// Default configuration environment
|
||||
/// Default value: production
|
||||
pub const ENVIRONMENT: &str = "production";
|
||||
@@ -410,7 +402,6 @@ mod tests {
|
||||
APP_NAME,
|
||||
VERSION,
|
||||
DEFAULT_LOG_LEVEL,
|
||||
SERVICE_VERSION,
|
||||
ENVIRONMENT,
|
||||
RUSTFS_TLS_KEY,
|
||||
RUSTFS_TLS_CERT,
|
||||
@@ -446,8 +437,7 @@ mod tests {
|
||||
fn test_configuration_consistency() {
|
||||
// Test configuration consistency
|
||||
|
||||
// Version consistency
|
||||
assert_eq!(VERSION, SERVICE_VERSION, "Application version should match service version");
|
||||
assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
|
||||
|
||||
// Port conflict check
|
||||
let ports = [DEFAULT_PORT, DEFAULT_CONSOLE_PORT];
|
||||
|
||||
@@ -36,7 +36,7 @@ use rustfs_obs::init_obs;
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Build config from environment variables, then initialise all backends.
|
||||
let _guard = init_obs(None).await.expect("failed to initialise observability");
|
||||
let _guard = init_obs(None, None).await.expect("failed to initialise observability");
|
||||
|
||||
tracing::info!("RustFS started");
|
||||
|
||||
@@ -56,7 +56,7 @@ async fn main() {
|
||||
```rust
|
||||
use rustfs_obs::init_obs;
|
||||
|
||||
let _guard = init_obs(Some("http://otel-collector:4318".to_string()))
|
||||
let _guard = init_obs(Some("http://otel-collector:4318".to_string()), None)
|
||||
.await
|
||||
.expect("observability init failed");
|
||||
```
|
||||
@@ -136,7 +136,7 @@ With OTLP → Collector → Prometheus, the time from a failure to an alert also
|
||||
| Variable | Default | Description |
|
||||
|------------------------------|-------------------|---------------------------------------------------------|
|
||||
| `RUSTFS_OBS_SERVICE_NAME` | `rustfs` | OTel `service.name` |
|
||||
| `RUSTFS_OBS_SERVICE_VERSION` | _(crate version)_ | OTel `service.version` |
|
||||
| `RUSTFS_OBS_SERVICE_VERSION` | RustFS build version | OTel `service.version`; the explicit environment value overrides the version passed by the RustFS binary |
|
||||
| `RUSTFS_OBS_ENVIRONMENT` | `development` | Deployment environment (`production`, `development`, …) |
|
||||
|
||||
### Local logging
|
||||
|
||||
@@ -20,7 +20,7 @@ use tracing::{Level, info, instrument};
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let obs_conf = Some("http://localhost:4318".to_string());
|
||||
let _guard = init_obs(obs_conf).await;
|
||||
let _guard = init_obs(obs_conf, None).await;
|
||||
let span = tracing::span!(Level::INFO, "main");
|
||||
let _enter = span.enter();
|
||||
info!("Program starts");
|
||||
|
||||
@@ -46,7 +46,7 @@ use rustfs_config::observability::{
|
||||
use rustfs_config::{
|
||||
APP_NAME, DEFAULT_LOG_KEEP_FILES, DEFAULT_LOG_LEVEL, DEFAULT_LOG_ROTATION_TIME, DEFAULT_OBS_LOG_FILENAME,
|
||||
DEFAULT_OBS_LOGS_EXPORT_ENABLED, DEFAULT_OBS_METRICS_EXPORT_ENABLED, DEFAULT_OBS_PROFILING_EXPORT_ENABLED,
|
||||
DEFAULT_OBS_TRACES_EXPORT_ENABLED, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, SERVICE_VERSION, USE_STDOUT,
|
||||
DEFAULT_OBS_TRACES_EXPORT_ENABLED, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, USE_STDOUT, VERSION,
|
||||
};
|
||||
use rustfs_utils::{
|
||||
get_env_bool, get_env_bool_with_aliases, get_env_f64, get_env_opt_bool, get_env_opt_str, get_env_opt_u64, get_env_str,
|
||||
@@ -162,7 +162,7 @@ pub struct OtelConfig {
|
||||
pub meter_interval: Option<u64>,
|
||||
/// OTel `service.name` attribute (default: `APP_NAME`).
|
||||
pub service_name: Option<String>,
|
||||
/// OTel `service.version` attribute (default: `SERVICE_VERSION`).
|
||||
/// OTel `service.version` attribute (default: the supplied application version or Cargo package version).
|
||||
pub service_version: Option<String>,
|
||||
/// Deployment environment tag, e.g. `production` or `development`.
|
||||
pub environment: Option<String>,
|
||||
@@ -234,6 +234,10 @@ impl OtelConfig {
|
||||
/// `None` or an empty string the value is read from the
|
||||
/// `RUSTFS_OBS_ENDPOINT` environment variable instead.
|
||||
///
|
||||
/// The default service version is the Cargo package version. Applications
|
||||
/// that know their display/build version should use
|
||||
/// [`OtelConfig::extract_otel_config_from_env_with_version`].
|
||||
///
|
||||
/// When no endpoint is configured at all, `use_stdout` is forced to `true`
|
||||
/// so that logs are still visible during development.
|
||||
///
|
||||
@@ -250,6 +254,15 @@ impl OtelConfig {
|
||||
/// );
|
||||
/// ```
|
||||
pub fn extract_otel_config_from_env(endpoint: Option<String>) -> OtelConfig {
|
||||
Self::extract_otel_config_from_env_with_version(endpoint, None)
|
||||
}
|
||||
|
||||
/// Build an [`OtelConfig`] using `version` as the default OTel service version.
|
||||
///
|
||||
/// An explicit `RUSTFS_OBS_SERVICE_VERSION` environment variable takes
|
||||
/// precedence over `version`. When `version` is `None`, the current Cargo
|
||||
/// package version is used.
|
||||
pub fn extract_otel_config_from_env_with_version(endpoint: Option<String>, version: Option<String>) -> OtelConfig {
|
||||
let endpoint = match endpoint {
|
||||
Some(ep) if !ep.is_empty() => ep,
|
||||
_ => env::var(ENV_OBS_ENDPOINT).unwrap_or_default(),
|
||||
@@ -312,7 +325,7 @@ impl OtelConfig {
|
||||
sample_ratio: Some(get_env_f64(ENV_OBS_SAMPLE_RATIO, SAMPLE_RATIO)),
|
||||
meter_interval: Some(get_env_u64(ENV_OBS_METER_INTERVAL, METER_INTERVAL)),
|
||||
service_name: Some(get_env_str(ENV_OBS_SERVICE_NAME, APP_NAME)),
|
||||
service_version: Some(get_env_str(ENV_OBS_SERVICE_VERSION, SERVICE_VERSION)),
|
||||
service_version: Some(get_env_str(ENV_OBS_SERVICE_VERSION, version.as_deref().unwrap_or(VERSION))),
|
||||
environment: Some(get_env_str(ENV_OBS_ENVIRONMENT, ENVIRONMENT)),
|
||||
// Local logging
|
||||
logger_level: Some(get_env_str(ENV_OBS_LOGGER_LEVEL, DEFAULT_LOG_LEVEL)),
|
||||
@@ -459,6 +472,29 @@ mod tests {
|
||||
OtelConfig::extract_otel_config_from_env(None).profiling_export_enabled
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_version_uses_application_version_when_environment_is_unset() {
|
||||
with_profiling_env_lock(|| {
|
||||
temp_env::with_var_unset(ENV_OBS_SERVICE_VERSION, || {
|
||||
let config = OtelConfig::extract_otel_config_from_env_with_version(None, Some("2.3.4".to_string()));
|
||||
assert_eq!(config.service_version.as_deref(), Some("2.3.4"));
|
||||
|
||||
let config = OtelConfig::extract_otel_config_from_env_with_version(None, None);
|
||||
assert_eq!(config.service_version.as_deref(), Some(VERSION));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_version_environment_override_takes_precedence_over_application_version() {
|
||||
with_profiling_env_lock(|| {
|
||||
temp_env::with_var(ENV_OBS_SERVICE_VERSION, Some("custom-build-42"), || {
|
||||
let config = OtelConfig::extract_otel_config_from_env_with_version(None, Some("2.3.4".to_string()));
|
||||
assert_eq!(config.service_version.as_deref(), Some("custom-build-42"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profiling_export_defaults_to_disabled_when_unset() {
|
||||
with_profiling_env_lock(|| {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{AppConfig, GlobalError, OtelConfig, OtelGuard, telemetry::init_telemetry};
|
||||
use crate::{GlobalError, OtelConfig, OtelGuard, telemetry::init_telemetry};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing::{info, warn};
|
||||
@@ -68,10 +68,12 @@ pub(crate) fn set_observability_metric_enabled(enabled: bool) {
|
||||
/// Initialize the observability module
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `config`: Configuration information
|
||||
/// - `endpoint`: Optional root OTLP endpoint; falls back to `RUSTFS_OBS_ENDPOINT`.
|
||||
/// - `version`: Optional application build version used as the default OTel
|
||||
/// `service.version`; `RUSTFS_OBS_SERVICE_VERSION` takes precedence.
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple containing the logger and the telemetry guard
|
||||
/// An [`OtelGuard`] that must remain alive for the lifetime of the application.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
@@ -79,16 +81,15 @@ pub(crate) fn set_observability_metric_enabled(enabled: bool) {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// # match init_obs(None).await {
|
||||
/// # match init_obs(None, None).await {
|
||||
/// # Ok(guard) => {}
|
||||
/// # Err(e) => { eprintln!("Failed to initialize observability: {}", e); }
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn init_obs(endpoint: Option<String>) -> Result<OtelGuard, GlobalError> {
|
||||
// Load the configuration file
|
||||
let config = AppConfig::new_with_endpoint(endpoint);
|
||||
init_obs_with_config(&config.observability).await
|
||||
pub async fn init_obs(endpoint: Option<String>, version: Option<String>) -> Result<OtelGuard, GlobalError> {
|
||||
let config = OtelConfig::extract_otel_config_from_env_with_version(endpoint, version);
|
||||
init_obs_with_config(&config).await
|
||||
}
|
||||
|
||||
/// Initialize the observability module with an explicit [`OtelConfig`].
|
||||
@@ -142,7 +143,7 @@ pub async fn init_obs_with_config(config: &OtelConfig) -> Result<OtelGuard, Glob
|
||||
/// # use rustfs_obs::{ init_obs, set_global_guard};
|
||||
///
|
||||
/// # async fn init() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// # let guard = match init_obs(None).await{
|
||||
/// # let guard = match init_obs(None, None).await{
|
||||
/// # Ok(g) => g,
|
||||
/// # Err(e) => { return Err(Box::new(e)); }
|
||||
/// # };
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! # let _guard = match init_obs(None).await {
|
||||
//! # let _guard = match init_obs(None, None).await {
|
||||
//! # Ok(g) => g,
|
||||
//! # Err(e) => {
|
||||
//! # panic!("Failed to initialize observability: {:?}", e);
|
||||
|
||||
@@ -31,7 +31,7 @@ use opentelemetry_semantic_conventions::{
|
||||
SERVICE_VERSION as OTEL_SERVICE_VERSION,
|
||||
},
|
||||
};
|
||||
use rustfs_config::{APP_NAME, ENVIRONMENT, SERVICE_VERSION};
|
||||
use rustfs_config::{APP_NAME, ENVIRONMENT, VERSION};
|
||||
use rustfs_utils::get_local_ip_with_default;
|
||||
use std::borrow::Cow;
|
||||
|
||||
@@ -40,7 +40,7 @@ use std::borrow::Cow;
|
||||
/// The resource carries the following attributes:
|
||||
/// - `service.name` — from `config.service_name`, defaulting to [`APP_NAME`].
|
||||
/// - `service.version` — from `config.service_version`, defaulting to
|
||||
/// [`SERVICE_VERSION`].
|
||||
/// [`VERSION`].
|
||||
/// - `deployment.environment` — from `config.environment`, defaulting to
|
||||
/// [`ENVIRONMENT`].
|
||||
/// - `rustfs.node` / `service.instance.id` — the stable RustFS local node name
|
||||
@@ -61,7 +61,7 @@ pub(super) fn build_resource(config: &OtelConfig) -> Resource {
|
||||
[
|
||||
KeyValue::new(
|
||||
OTEL_SERVICE_VERSION,
|
||||
Cow::Borrowed(config.service_version.as_deref().unwrap_or(SERVICE_VERSION)).to_string(),
|
||||
Cow::Borrowed(config.service_version.as_deref().unwrap_or(VERSION)).to_string(),
|
||||
),
|
||||
KeyValue::new(
|
||||
DEPLOYMENT_ENVIRONMENT_NAME,
|
||||
@@ -81,6 +81,22 @@ mod tests {
|
||||
use super::*;
|
||||
use opentelemetry::Key;
|
||||
|
||||
#[test]
|
||||
fn build_resource_uses_configured_service_version() {
|
||||
let config = OtelConfig {
|
||||
service_version: Some("@a1b2c3d".to_string()),
|
||||
..OtelConfig::default()
|
||||
};
|
||||
let resource = build_resource(&config);
|
||||
|
||||
assert_eq!(
|
||||
resource
|
||||
.get(&Key::from_static_str(OTEL_SERVICE_VERSION))
|
||||
.map(|value| value.to_string()),
|
||||
Some("@a1b2c3d".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_resource_uses_stable_local_node_identity() {
|
||||
let _guard = crate::node_identity::local_node_identity_test_guard().await;
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn set_buffer_profile_enabled(enabled: bool) {
|
||||
}
|
||||
|
||||
pub(crate) async fn init_observability_guard(obs_endpoint: String) -> Result<OtelGuard, ObservabilityError> {
|
||||
rustfs_obs::init_obs(Some(obs_endpoint)).await
|
||||
rustfs_obs::init_obs(Some(obs_endpoint), Some(crate::version::DISPLAY_VERSION.to_owned())).await
|
||||
}
|
||||
|
||||
pub(crate) fn set_observability_guard(guard: OtelGuard) -> Result<(), ObservabilityError> {
|
||||
|
||||
Reference in New Issue
Block a user