use apollo_configuration::Validate;
use apollo_configuration::configuration;
#[configuration]
pub(crate) struct ResourceConfig {
pub(crate) attributes: Vec<ResourceAttribute>,
pub(crate) detectors: ResourceDetectors,
pub(crate) schema_url: Option<String>,
}
#[configuration]
pub(crate) struct ResourceAttribute {
#[config(required)]
pub(crate) name: String,
#[config(required)]
pub(crate) value: AttributeValue,
}
#[allow(dead_code)]
#[derive(Debug, Clone, serde::Deserialize, schemars::JsonSchema)]
#[serde(untagged)]
pub(crate) enum AttributeValue {
String(String),
Int(i64),
Float(f64),
Bool(bool),
StringArray(Vec<String>),
IntArray(Vec<i64>),
FloatArray(Vec<f64>),
BoolArray(Vec<bool>),
}
impl Validate for AttributeValue {}
#[configuration]
pub(crate) struct ResourceDetectors {
pub(crate) attributes: EnvironmentResourceDetector,
}
#[configuration]
pub(crate) struct EnvironmentResourceDetector {
pub(crate) included: Vec<String>,
pub(crate) excluded: Vec<String>,
}
use opentelemetry::{Key, KeyValue, StringValue, Value};
use opentelemetry_sdk::Resource;
impl From<&ResourceConfig> for Resource {
fn from(config: &ResourceConfig) -> Self {
let attributes: Vec<KeyValue> = config
.attributes
.iter()
.map(|attr| KeyValue::new(attr.name.clone(), Value::from(&attr.value)))
.collect();
if let Some(ref schema_url) = config.schema_url {
Resource::builder()
.with_schema_url(attributes, schema_url.clone())
.build()
} else {
Resource::builder().with_attributes(attributes).build()
}
}
}
impl From<&AttributeValue> for Value {
fn from(value: &AttributeValue) -> Self {
match value {
AttributeValue::String(s) => Value::String(s.clone().into()),
AttributeValue::Int(i) => Value::I64(*i),
AttributeValue::Float(f) => Value::F64(*f),
AttributeValue::Bool(b) => Value::Bool(*b),
AttributeValue::StringArray(arr) => {
let strings: Vec<StringValue> = arr.iter().map(|s| s.clone().into()).collect();
Value::Array(strings.into())
}
AttributeValue::IntArray(arr) => Value::Array(arr.clone().into()),
AttributeValue::FloatArray(arr) => Value::Array(arr.clone().into()),
AttributeValue::BoolArray(arr) => Value::Array(arr.clone().into()),
}
}
}
impl From<&ResourceAttribute> for KeyValue {
fn from(attr: &ResourceAttribute) -> Self {
KeyValue::new(Key::new(attr.name.clone()), Value::from(&attr.value))
}
}
#[cfg(test)]
mod tests {
use apollo_configuration::parse_yaml;
use super::*;
use crate::config::OpenTelemetryConfig;
#[test]
fn from_config() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
resource:
attributes:
- name: service.name
value: test-service
- name: service.version
value: 1.0.0
- name: deployment.environment
value: production
"},
&Default::default(),
)
.unwrap();
let resource: Resource = (&config.resource).into();
assert!(!resource.is_empty());
}
#[test]
fn with_schema_url() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
resource:
schema_url: https://opentelemetry.io/schemas/1.21.0
attributes:
- name: service.name
value: test-service
"},
&Default::default(),
)
.unwrap();
let resource: Resource = (&config.resource).into();
assert!(!resource.is_empty());
}
#[test]
fn with_various_attribute_types() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
resource:
attributes:
- name: string_attr
value: hello
- name: int_attr
value: 42
- name: float_attr
value: 3.14
- name: bool_attr
value: true
"},
&Default::default(),
)
.unwrap();
let resource: Resource = (&config.resource).into();
assert!(resource.len() >= 4);
}
#[test]
fn parse_resource() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
resource:
attributes:
- name: service.name
value: my-service
- name: service.version
value: 1.0.0
"},
&Default::default(),
)
.unwrap();
assert_eq!(config.resource.attributes.len(), 2);
}
}