Skip to content

Commit

Permalink
feature(enterprise): configurable app name (#18554)
Browse files Browse the repository at this point in the history
* feature(enterprise): configurable service name

* cleanup & vector -> Vector

* nit

* nit

* cleanup

* switch order

* add sluggification for dd-evp-origin

* fmt

* feedback & fix vector-enterprise

* nits

* add expect entry

* correctly set app name for user agent as well

* fix error

* oncelock

* fmt again
  • Loading branch information
dsmith3197 authored Sep 22, 2023
1 parent ec9efb5 commit 2e7e7e7
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@ skywalking
slashin
slf
slideover
slugified
smallvec
smartphone
Smol
Expand Down
12 changes: 12 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,18 @@ fn build_enterprise(
config: &mut Config,
config_paths: Vec<ConfigPath>,
) -> Result<Option<EnterpriseReporter<BoxFuture<'static, ()>>>, ExitCode> {
use crate::ENTERPRISE_ENABLED;

ENTERPRISE_ENABLED
.set(
config
.enterprise
.as_ref()
.map(|e| e.enabled)
.unwrap_or_default(),
)
.expect("double initialization of enterprise enabled flag");

match EnterpriseMetadata::try_from(&*config) {
Ok(metadata) => {
let enterprise = EnterpriseReporter::new();
Expand Down
17 changes: 16 additions & 1 deletion src/config/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,27 @@ pub trait SinkConfig: DynClone + NamedComponent + core::fmt::Debug + Send + Sync

dyn_clone::clone_trait_object!(SinkConfig);

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct SinkContext {
pub healthcheck: SinkHealthcheckOptions,
pub globals: GlobalOptions,
pub proxy: ProxyConfig,
pub schema: schema::Options,
pub app_name: String,
pub app_name_slug: String,
}

impl Default for SinkContext {
fn default() -> Self {
Self {
healthcheck: Default::default(),
globals: Default::default(),
proxy: Default::default(),
schema: Default::default(),
app_name: crate::get_app_name().to_string(),
app_name_slug: crate::get_slugified_app_name(),
}
}
}

impl SinkContext {
Expand Down
5 changes: 3 additions & 2 deletions src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ where
let proxy_connector = build_proxy_connector(tls_settings.into(), proxy_config)?;
let client = client_builder.build(proxy_connector.clone());

let app_name = crate::get_app_name();
let version = crate::get_version();
let user_agent = HeaderValue::from_str(&format!("Vector/{}", version))
.expect("Invalid header value for version!");
let user_agent = HeaderValue::from_str(&format!("{}/{}", app_name, version))
.expect("Invalid header value for user-agent!");

Ok(HttpClient {
client,
Expand Down
32 changes: 32 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,38 @@ pub use source_sender::SourceSender;
pub use vector_common::{shutdown, Error, Result};
pub use vector_core::{event, metrics, schema, tcp, tls};

static APP_NAME_SLUG: std::sync::OnceLock<String> = std::sync::OnceLock::new();

/// Flag denoting whether or not enterprise features are enabled.
#[cfg(feature = "enterprise")]
pub static ENTERPRISE_ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();

/// The name used to identify this Vector application.
///
/// This can be set at compile-time through the VECTOR_APP_NAME env variable.
/// Defaults to "Vector".
pub fn get_app_name() -> &'static str {
#[cfg(not(feature = "enterprise"))]
let app_name = "Vector";
#[cfg(feature = "enterprise")]
let app_name = if *ENTERPRISE_ENABLED.get().unwrap_or(&false) {
"Vector Enterprise"
} else {
"Vector"
};

option_env!("VECTOR_APP_NAME").unwrap_or(app_name)
}

/// Returns a slugified version of the name used to identify this Vector application.
///
/// Defaults to "vector".
pub fn get_slugified_app_name() -> String {
APP_NAME_SLUG
.get_or_init(|| get_app_name().to_lowercase().replace(' ', "-"))
.clone()
}

/// The current version of Vector in simplified format.
/// `<version-number>-nightly`.
pub fn vector_version() -> impl std::fmt::Display {
Expand Down
9 changes: 7 additions & 2 deletions src/sinks/datadog/logs/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ impl DatadogLogsConfig {
self.get_uri().scheme_str().unwrap_or("http").to_string()
}

pub fn build_processor(&self, client: HttpClient) -> crate::Result<VectorSink> {
pub fn build_processor(
&self,
client: HttpClient,
dd_evp_origin: String,
) -> crate::Result<VectorSink> {
let default_api_key: Arc<str> = Arc::from(self.dd_common.default_api_key.inner());
let request_limits = self.request.tower.unwrap_with(&Default::default());

Expand All @@ -128,6 +132,7 @@ impl DatadogLogsConfig {
client,
self.get_uri(),
self.request.headers.clone(),
dd_evp_origin,
)?);

let encoding = self.encoding.clone();
Expand Down Expand Up @@ -164,7 +169,7 @@ impl SinkConfig for DatadogLogsConfig {
.dd_common
.build_healthcheck(client.clone(), self.region.as_ref())?;

let sink = self.build_processor(client)?;
let sink = self.build_processor(client, cx.app_name_slug)?;

Ok((sink, healthcheck))
}
Expand Down
21 changes: 17 additions & 4 deletions src/sinks/datadog/logs/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,31 @@ pub struct LogApiService {
client: HttpClient,
uri: Uri,
user_provided_headers: IndexMap<HeaderName, HeaderValue>,
dd_evp_headers: IndexMap<HeaderName, HeaderValue>,
}

impl LogApiService {
pub fn new(
client: HttpClient,
uri: Uri,
headers: IndexMap<String, String>,
dd_evp_origin: String,
) -> crate::Result<Self> {
let headers = validate_headers(&headers)?;
let user_provided_headers = validate_headers(&headers)?;

let dd_evp_headers = &[
("DD-EVP-ORIGIN".to_string(), dd_evp_origin),
("DD-EVP-ORIGIN-VERSION".to_string(), crate::get_version()),
]
.into_iter()
.collect();
let dd_evp_headers = validate_headers(dd_evp_headers)?;

Ok(Self {
client,
uri,
user_provided_headers: headers,
user_provided_headers,
dd_evp_headers,
})
}
}
Expand All @@ -128,8 +139,6 @@ impl Service<LogApiRequest> for LogApiService {
let mut client = self.client.clone();
let http_request = Request::post(&self.uri)
.header(CONTENT_TYPE, "application/json")
.header("DD-EVP-ORIGIN", "vector")
.header("DD-EVP-ORIGIN-VERSION", crate::get_version())
.header("DD-API-KEY", request.api_key.to_string());

let http_request = if let Some(ce) = request.compression.content_encoding() {
Expand All @@ -149,6 +158,10 @@ impl Service<LogApiRequest> for LogApiService {
// Replace rather than append to any existing header values
headers.insert(name, value.clone());
}
// Set DD EVP headers last so that they cannot be overridden.
for (name, value) in &self.dd_evp_headers {
headers.insert(name, value.clone());
}
}

let http_request = http_request
Expand Down
7 changes: 3 additions & 4 deletions src/sinks/datadog/logs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,14 +402,13 @@ async fn enterprise_headers_v1() {
}

async fn enterprise_headers_inner(api_status: ApiStatus) {
let (mut config, cx) = load_sink::<DatadogLogsConfig>(indoc! {r#"
let (mut config, mut cx) = load_sink::<DatadogLogsConfig>(indoc! {r#"
default_api_key = "atoken"
compression = "none"
[request]
headers.DD-EVP-ORIGIN = "vector-enterprise"
"#})
.unwrap();
cx.app_name = "Vector Enterprise".to_string();
cx.app_name_slug = "vector-enterprise".to_string();

let addr = next_addr();
// Swap out the endpoint so we can force send it to our local server
Expand Down
2 changes: 2 additions & 0 deletions src/topology/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@ impl<'a> Builder<'a> {
globals: self.config.global.clone(),
proxy: ProxyConfig::merge_with_env(&self.config.global.proxy, sink.proxy()),
schema: self.config.schema,
app_name: crate::get_app_name().to_string(),
app_name_slug: crate::get_slugified_app_name(),
};

let (sink, healthcheck) = match sink.inner.build(cx).await {
Expand Down

0 comments on commit 2e7e7e7

Please sign in to comment.