Skip to content

Commit 6f7ef56

Browse files
authored
feat(prometheus_remote_write source): add path configuration option (#23956)
1 parent 0c99f16 commit 6f7ef56

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added `path` configuration option to `prometheus_remote_write` source to allow accepting metrics on custom URL paths instead of only the root path. This enables configuration of endpoints like `/api/v1/write` to match standard Prometheus remote write conventions.
2+
3+
authors: elohmeier

src/sources/prometheus/remote_write.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ pub struct PrometheusRemoteWriteConfig {
3737
#[configurable(metadata(docs::examples = "0.0.0.0:9090"))]
3838
address: SocketAddr,
3939

40+
/// The URL path on which metric POST requests are accepted.
41+
#[serde(default = "default_path")]
42+
#[configurable(metadata(docs::examples = "/api/v1/write"))]
43+
#[configurable(metadata(docs::examples = "/remote-write"))]
44+
path: String,
45+
4046
#[configurable(derived)]
4147
tls: Option<TlsEnableableConfig>,
4248

@@ -66,6 +72,7 @@ impl PrometheusRemoteWriteConfig {
6672
pub fn from_address(address: SocketAddr) -> Self {
6773
Self {
6874
address,
75+
path: default_path(),
6976
tls: None,
7077
auth: None,
7178
acknowledgements: false.into(),
@@ -75,10 +82,15 @@ impl PrometheusRemoteWriteConfig {
7582
}
7683
}
7784

85+
fn default_path() -> String {
86+
"/".to_string()
87+
}
88+
7889
impl GenerateConfig for PrometheusRemoteWriteConfig {
7990
fn generate_config() -> toml::Value {
8091
toml::Value::try_from(Self {
8192
address: "127.0.0.1:9090".parse().unwrap(),
93+
path: default_path(),
8294
tls: None,
8395
auth: None,
8496
acknowledgements: SourceAcknowledgementsConfig::default(),
@@ -98,7 +110,7 @@ impl SourceConfig for PrometheusRemoteWriteConfig {
98110
};
99111
source.run(
100112
self.address,
101-
"",
113+
self.path.as_str(),
102114
HttpMethod::Post,
103115
StatusCode::OK,
104116
true,
@@ -203,6 +215,7 @@ mod test {
203215
.http_protocol_name();
204216
let source = PrometheusRemoteWriteConfig {
205217
address,
218+
path: default_path(),
206219
auth: None,
207220
tls: tls.clone(),
208221
acknowledgements: SourceAcknowledgementsConfig::default(),
@@ -317,6 +330,7 @@ mod test {
317330

318331
let source = PrometheusRemoteWriteConfig {
319332
address,
333+
path: default_path(),
320334
auth: None,
321335
tls: None,
322336
acknowledgements: SourceAcknowledgementsConfig::default(),
@@ -387,6 +401,7 @@ mod test {
387401

388402
let source = PrometheusRemoteWriteConfig {
389403
address,
404+
path: default_path(),
390405
auth: None,
391406
tls: None,
392407
acknowledgements: SourceAcknowledgementsConfig::default(),
@@ -456,6 +471,7 @@ mod test {
456471

457472
let source = PrometheusRemoteWriteConfig {
458473
address,
474+
path: default_path(),
459475
auth: None,
460476
tls: None,
461477
acknowledgements: SourceAcknowledgementsConfig::default(),
@@ -531,6 +547,93 @@ mod test {
531547
assert_eq!(valid_metric.name(), "test_metric_valid");
532548
assert_eq!(valid_metric.value(), &MetricValue::Gauge { value: 42.0 });
533549
}
550+
551+
#[tokio::test]
552+
async fn receives_metrics_on_custom_path() {
553+
let address = test_util::next_addr();
554+
let (tx, rx) = SourceSender::new_test_finalize(EventStatus::Delivered);
555+
556+
let source = PrometheusRemoteWriteConfig {
557+
address,
558+
path: "/api/v1/write".to_string(),
559+
auth: None,
560+
tls: None,
561+
acknowledgements: SourceAcknowledgementsConfig::default(),
562+
keepalive: KeepaliveConfig::default(),
563+
skip_nan_values: false,
564+
};
565+
let source = source
566+
.build(SourceContext::new_test(tx, None))
567+
.await
568+
.unwrap();
569+
tokio::spawn(source);
570+
wait_for_tcp(address).await;
571+
572+
let sink = RemoteWriteConfig {
573+
endpoint: format!("http://localhost:{}/api/v1/write", address.port()),
574+
..Default::default()
575+
};
576+
let (sink, _) = sink
577+
.build(SinkContext::default())
578+
.await
579+
.expect("Error building config.");
580+
581+
let events = make_events();
582+
let events_copy = events.clone();
583+
let mut output = test_util::spawn_collect_ready(
584+
async move {
585+
sink.run_events(events_copy).await.unwrap();
586+
},
587+
rx,
588+
1,
589+
)
590+
.await;
591+
592+
// The MetricBuffer used by the sink may reorder the metrics, so
593+
// put them back into order before comparing.
594+
output.sort_unstable_by_key(|event| event.as_metric().name().to_owned());
595+
596+
vector_lib::assert_event_data_eq!(events, output);
597+
}
598+
599+
#[tokio::test]
600+
async fn rejects_metrics_on_wrong_path() {
601+
let address = test_util::next_addr();
602+
let (tx, _rx) = SourceSender::new_test_finalize(EventStatus::Delivered);
603+
604+
let source = PrometheusRemoteWriteConfig {
605+
address,
606+
path: "/api/v1/write".to_string(),
607+
auth: None,
608+
tls: None,
609+
acknowledgements: SourceAcknowledgementsConfig::default(),
610+
keepalive: KeepaliveConfig::default(),
611+
skip_nan_values: false,
612+
};
613+
let source = source
614+
.build(SourceContext::new_test(tx, None))
615+
.await
616+
.unwrap();
617+
tokio::spawn(source);
618+
wait_for_tcp(address).await;
619+
620+
// Try to send to the root path, which should be rejected
621+
let client = reqwest::Client::new();
622+
let response = client
623+
.post(format!("http://localhost:{}/wrong/path", address.port()))
624+
.header("Content-Type", "application/x-protobuf")
625+
.body(vec![])
626+
.send()
627+
.await
628+
.unwrap();
629+
630+
// Should return an error status code since we're sending to the wrong path
631+
assert!(
632+
response.status().is_client_error(),
633+
"Expected 4xx error, got {}",
634+
response.status()
635+
);
636+
}
534637
}
535638

536639
#[cfg(all(test, feature = "prometheus-integration-tests"))]
@@ -565,6 +668,7 @@ mod integration_tests {
565668
// maybe there's a way to do a one-shot remote write from Prometheus? Not sure.
566669
let config = PrometheusRemoteWriteConfig {
567670
address: source_receive_address(),
671+
path: default_path(),
568672
auth: None,
569673
tls: None,
570674
acknowledgements: SourceAcknowledgementsConfig::default(),

website/cue/reference/components/sources/generated/prometheus_remote_write.cue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ generated: components: sources: prometheus_remote_write: configuration: {
112112
}
113113
}
114114
}
115+
path: {
116+
description: "The URL path on which metric POST requests are accepted."
117+
required: false
118+
type: string: {
119+
default: "/"
120+
examples: ["/api/v1/write", "/remote-write"]
121+
}
122+
}
115123
skip_nan_values: {
116124
description: """
117125
Whether to skip/discard received samples with NaN values.

0 commit comments

Comments
 (0)