feat: harden community production foundation through story 1.5
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user