diff --git a/changelog.d/10980_loki_endpoint_validation.fix.md b/changelog.d/10980_loki_endpoint_validation.fix.md new file mode 100644 index 0000000000000..da8a4b75b7a20 --- /dev/null +++ b/changelog.d/10980_loki_endpoint_validation.fix.md @@ -0,0 +1 @@ +The `loki` sink now validates the `endpoint` option at startup and returns a clear error when it is not an absolute URL (for example, a host name with a missing or mistyped scheme). Previously such values were accepted during configuration parsing but later failed with an opaque `invalid format` error. diff --git a/src/sinks/loki/config.rs b/src/sinks/loki/config.rs index 16f25dc3d554b..67e4318818200 100644 --- a/src/sinks/loki/config.rs +++ b/src/sinks/loki/config.rs @@ -225,6 +225,8 @@ impl SinkConfig for LokiConfig { } } + validate_endpoint(&self.endpoint)?; + let client = self.build_client(cx)?; let config = LokiConfig { @@ -277,11 +279,30 @@ pub fn valid_label_name(label: &Template) -> bool { } } +/// Validates that the configured `endpoint` is a usable absolute URL. +/// +/// `endpoint` is deserialized as a permissive `UriSerde`, so values without a +/// scheme (for example a bare host name with a typo) are accepted at parse time +/// but later fail with an opaque "invalid format" error when the request URI is +/// built. Checking here lets us surface a clear, actionable message instead. +fn validate_endpoint(endpoint: &UriSerde) -> crate::Result<()> { + let uri = &endpoint.uri; + if uri.scheme().is_none() || uri.authority().is_none() { + return Err(format!( + "Invalid `endpoint`: {endpoint:?} is not an absolute URL. \ + Expected a value with a scheme and host, such as \"http://localhost:3100\"." + ) + .into()); + } + Ok(()) +} + #[cfg(test)] mod tests { use std::convert::TryInto; - use super::valid_label_name; + use super::{valid_label_name, validate_endpoint}; + use crate::sinks::util::UriSerde; #[test] fn valid_label_names() { @@ -299,4 +320,22 @@ mod tests { assert!(valid_label_name(&"{{field}}".try_into().unwrap())); } + + #[test] + fn validates_endpoint() { + let parse = |s: &str| s.parse::().unwrap(); + + // Valid absolute URLs. + assert!(validate_endpoint(&parse("http://localhost:3100")).is_ok()); + assert!(validate_endpoint(&parse("https://loki.example.com/")).is_ok()); + + // Scheme-less values (such as a host name with a typo) are rejected with + // a clear error instead of failing later with "invalid format". + let error = validate_endpoint(&parse("typo-host-no-scheme")) + .unwrap_err() + .to_string(); + assert!(error.contains("not an absolute URL"), "{error}"); + + assert!(validate_endpoint(&parse("/loki/api/v1/push")).is_err()); + } }