feat: harden community production foundation through story 1.5

This commit is contained in:
2026-08-14 00:21:59 +03:00
parent c30461cc92
commit f6fc2e5c9b
161 changed files with 16758 additions and 2515 deletions
+87 -42
View File
@@ -1,4 +1,4 @@
use std::{collections::HashMap, env, fmt, time::Duration};
use std::{collections::HashMap, fmt, time::Duration};
use axum::http::{HeaderName, HeaderValue};
use opentelemetry::{
@@ -118,8 +118,36 @@ impl fmt::Debug for OtlpTraceConfig {
}
impl OtlpTraceConfig {
pub fn from_env() -> Result<Self, OtlpTraceConfigError> {
OtlpEnvSettings::from_env()?.into_config()
#[allow(clippy::too_many_arguments)]
pub fn from_values(
generic_endpoint: Option<String>,
traces_endpoint: Option<String>,
generic_protocol: Option<String>,
traces_protocol: Option<String>,
generic_timeout: Option<String>,
traces_timeout: Option<String>,
generic_headers: Option<String>,
traces_headers: Option<String>,
max_queue_size: usize,
max_export_batch_size: usize,
scheduled_delay: String,
batch_export_timeout: String,
) -> Result<Self, OtlpTraceConfigError> {
OtlpEnvSettings {
traces_endpoint,
generic_endpoint,
traces_protocol,
generic_protocol,
traces_timeout,
generic_timeout,
traces_headers,
generic_headers,
max_queue_size: Some(max_queue_size.to_string()),
max_export_batch_size: Some(max_export_batch_size.to_string()),
scheduled_delay: Some(scheduled_delay),
batch_export_timeout: Some(batch_export_timeout),
}
.into_config()
}
fn from_settings(settings: OtlpEnvSettings) -> Result<Self, OtlpTraceConfigError> {
@@ -232,6 +260,17 @@ impl OtlpTraceConfig {
}
}
impl Default for OtlpTraceConfig {
fn default() -> Self {
Self {
endpoint: None,
export_timeout: DEFAULT_EXPORT_TIMEOUT,
batch: OtlpBatchConfig::default(),
headers: HashMap::new(),
}
}
}
#[derive(Default)]
struct OtlpEnvSettings {
traces_endpoint: Option<String>,
@@ -249,23 +288,6 @@ struct OtlpEnvSettings {
}
impl OtlpEnvSettings {
fn from_env() -> Result<Self, OtlpTraceConfigError> {
Ok(Self {
traces_endpoint: optional_env("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")?,
generic_endpoint: optional_env("OTEL_EXPORTER_OTLP_ENDPOINT")?,
traces_protocol: optional_env("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")?,
generic_protocol: optional_env("OTEL_EXPORTER_OTLP_PROTOCOL")?,
traces_timeout: optional_env("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT")?,
generic_timeout: optional_env("OTEL_EXPORTER_OTLP_TIMEOUT")?,
traces_headers: optional_env("OTEL_EXPORTER_OTLP_TRACES_HEADERS")?,
generic_headers: optional_env("OTEL_EXPORTER_OTLP_HEADERS")?,
max_queue_size: optional_env("OTEL_BSP_MAX_QUEUE_SIZE")?,
max_export_batch_size: optional_env("OTEL_BSP_MAX_EXPORT_BATCH_SIZE")?,
scheduled_delay: optional_env("OTEL_BSP_SCHEDULE_DELAY")?,
batch_export_timeout: optional_env("OTEL_BSP_EXPORT_TIMEOUT")?,
})
}
fn into_config(self) -> Result<OtlpTraceConfig, OtlpTraceConfigError> {
OtlpTraceConfig::from_settings(self)
}
@@ -313,7 +335,28 @@ pub fn build_tracer_provider(
let processor = BatchSpanProcessor::builder(ObservedSpanExporter(exporter))
.with_batch_config(config.batch.sdk_config())
.build();
let resource = Resource::builder_empty()
let resource = trace_resource(identity);
let provider = SdkTracerProvider::builder()
.with_span_processor(processor)
.with_resource(resource)
.build();
let tracer = provider.tracer("crank");
Ok(Some((provider, tracer)))
}
pub(crate) fn build_local_tracer_provider(
identity: &ServiceIdentity,
) -> (SdkTracerProvider, SdkTracer) {
let provider = SdkTracerProvider::builder()
.with_resource(trace_resource(identity))
.build();
let tracer = provider.tracer("crank");
(provider, tracer)
}
fn trace_resource(identity: &ServiceIdentity) -> Resource {
Resource::builder_empty()
.with_attributes([
KeyValue::new("service.name", identity.service().to_owned()),
KeyValue::new("service.version", identity.version().to_owned()),
@@ -322,14 +365,7 @@ pub fn build_tracer_provider(
identity.environment().to_owned(),
),
])
.build();
let provider = SdkTracerProvider::builder()
.with_span_processor(processor)
.with_resource(resource)
.build();
let tracer = provider.tracer("crank");
Ok(Some((provider, tracer)))
.build()
}
#[derive(Debug)]
@@ -405,7 +441,7 @@ fn is_allowed_span_attribute(attribute: &KeyValue) -> bool {
};
let value = value.as_str();
match attribute.key.as_str() {
"request_id" => crate::RequestId::is_valid(value),
"request_id" => is_valid_request_id(value),
"stage" => is_allowed_span_name(value),
"outcome" => matches!(
value,
@@ -453,6 +489,14 @@ fn is_allowed_span_attribute(attribute: &KeyValue) -> bool {
}
}
fn is_valid_request_id(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 128
&& value
.bytes()
.all(|byte| byte.is_ascii_graphic() && byte != b',' && byte != b';')
}
#[derive(Clone, Copy)]
enum EndpointKind {
Trace,
@@ -514,17 +558,6 @@ fn parse_headers(value: &str) -> Result<HashMap<String, String>, OtlpTraceConfig
})
}
fn optional_env(field: &'static str) -> Result<Option<String>, OtlpTraceConfigError> {
match env::var(field) {
Ok(value) if value.is_empty() => Ok(None),
Ok(value) => Ok(Some(value)),
Err(env::VarError::NotPresent) => Ok(None),
Err(env::VarError::NotUnicode(_)) => {
Err(OtlpTraceConfigError::InvalidEnvironmentEncoding { field })
}
}
}
fn usize_env(
field: &'static str,
value: Option<String>,
@@ -586,7 +619,10 @@ mod tests {
use tracing::{Instrument, info_span};
use tracing_subscriber::layer::SubscriberExt;
use super::{OtlpBatchConfig, OtlpEnvSettings, OtlpTraceConfig, build_tracer_provider};
use super::{
OtlpBatchConfig, OtlpEnvSettings, OtlpTraceConfig, build_local_tracer_provider,
build_tracer_provider,
};
use crate::ServiceIdentity;
#[test]
@@ -708,6 +744,15 @@ mod tests {
assert!(build_tracer_provider(&identity, &config).unwrap().is_none());
}
#[test]
fn local_provider_creates_valid_context_without_an_exporter() {
let identity = ServiceIdentity::try_new("admin-api", "0.3.1", "test").unwrap();
let (provider, tracer) = build_local_tracer_provider(&identity);
let span = tracer.start("http.request");
assert!(span.span_context().is_valid());
provider.shutdown().unwrap();
}
#[test]
fn real_http_protobuf_export_contains_resource_and_trace() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();