otel: add host.name resource attribute to logs/traces via gethostname (#12352)
**PR Summary**
This PR adds the OpenTelemetry `host.name` resource attribute to Codex
OTEL exports so every OTEL log (and trace, via the shared resource)
carries the machine hostname.
**What changed**
- Added `host.name` to the shared OTEL `Resource` in
`/Users/michael.mcgrew/code/codex/codex-rs/otel/src/otel_provider.rs`
- This applies to both:
- OTEL logs (`SdkLoggerProvider`)
- OTEL traces (`SdkTracerProvider`)
- Hostname is now resolved via `gethostname::gethostname()`
(best-effort)
- Value is trimmed
- Empty values are omitted (non-fatal)
- Added focused unit tests for:
- including `host.name` when present
- omitting `host.name` when missing/empty
**Why**
- `host.name` is host/process metadata and belongs on the OTEL
`resource`, not per-event attributes.
- Attaching it in the shared resource is the smallest change that
guarantees coverage across all exported OTEL logs/traces.
**Scope / Non-goals**
- No public API changes
- No changes to metrics behavior (this PR only updates log/trace
resource metadata)
**Dependency updates**
- Added `gethostname` as a workspace dependency and `codex-otel`
dependency
- `Cargo.lock` updated accordingly
- `MODULE.bazel.lock` unchanged after refresh/check
**Validation**
- `just fmt`
- `cargo test -p codex-otel`
- `just bazel-lock-update`
- `just bazel-lock-check`
This commit is contained in:
parent
8d49e0d0c4
commit
bccce0d75f
4 changed files with 77 additions and 7 deletions
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
|
|
@ -2095,6 +2095,7 @@ dependencies = [
|
|||
"codex-utils-absolute-path",
|
||||
"codex-utils-string",
|
||||
"eventsource-stream",
|
||||
"gethostname",
|
||||
"http 1.4.0",
|
||||
"opentelemetry",
|
||||
"opentelemetry-appender-tracing",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ env_logger = "0.11.9"
|
|||
eventsource-stream = "0.2.3"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
globset = "0.4"
|
||||
gethostname = "1.1.0"
|
||||
http = "1.3.1"
|
||||
icu_decimal = "2.1"
|
||||
icu_locale_core = "2.1"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ codex-utils-string = { workspace = true }
|
|||
codex-api = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
gethostname = { workspace = true }
|
||||
opentelemetry = { workspace = true, features = ["logs", "metrics", "trace"] }
|
||||
opentelemetry-appender-tracing = { workspace = true }
|
||||
opentelemetry-otlp = { workspace = true, features = [
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::config::OtelHttpProtocol;
|
|||
use crate::config::OtelSettings;
|
||||
use crate::metrics::MetricsClient;
|
||||
use crate::metrics::MetricsConfig;
|
||||
use gethostname::gethostname;
|
||||
use opentelemetry::Context;
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::context::ContextGuard;
|
||||
|
|
@ -40,6 +41,7 @@ use tracing_subscriber::Layer;
|
|||
use tracing_subscriber::registry::LookupSpan;
|
||||
|
||||
const ENV_ATTRIBUTE: &str = "env";
|
||||
const HOST_NAME_ATTRIBUTE: &str = "host.name";
|
||||
const TRACEPARENT_ENV_VAR: &str = "TRACEPARENT";
|
||||
const TRACESTATE_ENV_VAR: &str = "TRACESTATE";
|
||||
static TRACEPARENT_CONTEXT: OnceLock<Option<Context>> = OnceLock::new();
|
||||
|
|
@ -223,16 +225,37 @@ fn extract_traceparent_context(traceparent: String, tracestate: Option<String>)
|
|||
fn make_resource(settings: &OtelSettings) -> Resource {
|
||||
Resource::builder()
|
||||
.with_service_name(settings.service_name.clone())
|
||||
.with_attributes(vec![
|
||||
KeyValue::new(
|
||||
semconv::attribute::SERVICE_VERSION,
|
||||
settings.service_version.clone(),
|
||||
),
|
||||
KeyValue::new(ENV_ATTRIBUTE, settings.environment.clone()),
|
||||
])
|
||||
.with_attributes(resource_attributes(
|
||||
settings,
|
||||
detected_host_name().as_deref(),
|
||||
))
|
||||
.build()
|
||||
}
|
||||
|
||||
fn resource_attributes(settings: &OtelSettings, host_name: Option<&str>) -> Vec<KeyValue> {
|
||||
let mut attributes = vec![
|
||||
KeyValue::new(
|
||||
semconv::attribute::SERVICE_VERSION,
|
||||
settings.service_version.clone(),
|
||||
),
|
||||
KeyValue::new(ENV_ATTRIBUTE, settings.environment.clone()),
|
||||
];
|
||||
if let Some(host_name) = host_name.and_then(normalize_host_name) {
|
||||
attributes.push(KeyValue::new(HOST_NAME_ATTRIBUTE, host_name));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
fn detected_host_name() -> Option<String> {
|
||||
let host_name = gethostname();
|
||||
normalize_host_name(host_name.to_string_lossy().as_ref())
|
||||
}
|
||||
|
||||
fn normalize_host_name(host_name: &str) -> Option<String> {
|
||||
let host_name = host_name.trim();
|
||||
(!host_name.is_empty()).then(|| host_name.to_owned())
|
||||
}
|
||||
|
||||
fn build_logger(
|
||||
resource: &Resource,
|
||||
exporter: &OtelExporter,
|
||||
|
|
@ -377,6 +400,8 @@ mod tests {
|
|||
use opentelemetry::trace::SpanId;
|
||||
use opentelemetry::trace::TraceContextExt;
|
||||
use opentelemetry::trace::TraceId;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn parses_valid_traceparent() {
|
||||
|
|
@ -398,4 +423,46 @@ mod tests {
|
|||
fn invalid_traceparent_returns_none() {
|
||||
assert!(extract_traceparent_context("not-a-traceparent".to_string(), None).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resource_attributes_include_host_name_when_present() {
|
||||
let attrs = resource_attributes(&test_otel_settings(), Some("opentelemetry-test"));
|
||||
|
||||
let host_name = attrs
|
||||
.iter()
|
||||
.find(|kv| kv.key.as_str() == HOST_NAME_ATTRIBUTE)
|
||||
.map(|kv| kv.value.as_str().to_string());
|
||||
|
||||
assert_eq!(host_name, Some("opentelemetry-test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resource_attributes_omit_host_name_when_missing_or_empty() {
|
||||
let missing = resource_attributes(&test_otel_settings(), None);
|
||||
let empty = resource_attributes(&test_otel_settings(), Some(" "));
|
||||
|
||||
assert!(
|
||||
!missing
|
||||
.iter()
|
||||
.any(|kv| kv.key.as_str() == HOST_NAME_ATTRIBUTE)
|
||||
);
|
||||
assert!(
|
||||
!empty
|
||||
.iter()
|
||||
.any(|kv| kv.key.as_str() == HOST_NAME_ATTRIBUTE)
|
||||
);
|
||||
}
|
||||
|
||||
fn test_otel_settings() -> OtelSettings {
|
||||
OtelSettings {
|
||||
environment: "test".to_string(),
|
||||
service_name: "codex-test".to_string(),
|
||||
service_version: "0.0.0".to_string(),
|
||||
codex_home: PathBuf::from("."),
|
||||
exporter: OtelExporter::None,
|
||||
trace_exporter: OtelExporter::None,
|
||||
metrics_exporter: OtelExporter::None,
|
||||
runtime_metrics: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue