-
Couldn't load subscription status.
- Fork 1.9k
feat(axiom): add support for regional edge endpoints in AxiomConfig #24037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| The `axiom` sink now supports regional edges for data locality. A new optional `region` configuration field allows you to specify the regional edge domain (e.g., `eu-central-1.aws.edge.axiom.co`). When configured, data is sent to `https://{region}/v1/ingest/{dataset}`. The `url` field now intelligently handles paths: URLs with custom paths are used as-is, while URLs without paths maintain backwards compatibility by appending `/v1/datasets/{dataset}/ingest`. | ||
|
|
||
| authors: toppercodes |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -29,9 +29,12 @@ static CLOUD_URL: &str = "https://api.axiom.co"; | |||||
| pub struct AxiomConfig { | ||||||
| /// URI of the Axiom endpoint to send data to. | ||||||
| /// | ||||||
| /// Only required if not using Axiom Cloud. | ||||||
| /// If a path is provided, the URL is used as-is. | ||||||
| /// If no path (or only `/`) is provided, `/v1/datasets/{dataset}/ingest` is appended for backwards compatibility. | ||||||
| /// This takes precedence over `region` if both are set. | ||||||
| #[configurable(validation(format = "uri"))] | ||||||
| #[configurable(metadata(docs::examples = "https://axiom.my-domain.com"))] | ||||||
| #[configurable(metadata(docs::examples = "https://api.eu.axiom.co"))] | ||||||
| #[configurable(metadata(docs::examples = "http://localhost:3400/ingest"))] | ||||||
| #[configurable(metadata(docs::examples = "${AXIOM_URL}"))] | ||||||
| url: Option<String>, | ||||||
|
|
||||||
|
|
@@ -52,6 +55,16 @@ pub struct AxiomConfig { | |||||
| #[configurable(metadata(docs::examples = "vector_rocks"))] | ||||||
| dataset: String, | ||||||
|
|
||||||
| /// The Axiom regional edge domain to use for ingestion. | ||||||
| /// | ||||||
| /// Specify the domain name only (no scheme, no path). | ||||||
| /// When set, data will be sent to `https://{region}/v1/ingest/{dataset}`. | ||||||
| /// If `url` is also set, `url` takes precedence. | ||||||
| #[configurable(metadata(docs::examples = "${AXIOM_REGION}"))] | ||||||
| #[configurable(metadata(docs::examples = "mumbai.axiom.co"))] | ||||||
| #[configurable(metadata(docs::examples = "eu-central-1.aws.edge.axiom.co"))] | ||||||
| region: Option<String>, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since We can only validate and throw an error if both are set. |
||||||
|
|
||||||
| #[configurable(derived)] | ||||||
| #[serde(default)] | ||||||
| request: RequestConfig, | ||||||
|
|
@@ -150,17 +163,35 @@ impl SinkConfig for AxiomConfig { | |||||
|
|
||||||
| impl AxiomConfig { | ||||||
| fn build_endpoint(&self) -> String { | ||||||
| let url = if let Some(url) = self.url.as_ref() { | ||||||
| url.clone() | ||||||
| } else { | ||||||
| CLOUD_URL.to_string() | ||||||
| }; | ||||||
| // Priority: url > region > default cloud endpoint | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||||||
|
|
||||||
| // If url is set, check if it has a path | ||||||
| if let Some(url) = &self.url { | ||||||
| let url = url.trim_end_matches('/'); | ||||||
|
|
||||||
| // Parse URL to check if path is provided | ||||||
| // If path is empty or just "/", append the legacy format for backwards compatibility | ||||||
| // Otherwise, use the URL as-is | ||||||
| if let Ok(parsed) = url::Url::parse(url) { | ||||||
| let path = parsed.path(); | ||||||
| if path.is_empty() || path == "/" { | ||||||
| // Backwards compatibility: append legacy path format | ||||||
| return format!("{}/v1/datasets/{}/ingest", url, self.dataset); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
Same for all format! calls. |
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // URL has a custom path, use as-is | ||||||
| return url.to_string(); | ||||||
| } | ||||||
|
|
||||||
| // NOTE trim any trailing slashes to avoid redundant rewriting or 301 redirects from intermediate proxies | ||||||
| // NOTE Most axiom users will not need to configure a url, this is for the other 1% | ||||||
| let url = url.trim_end_matches('/'); | ||||||
| // If region is set, build the regional edge endpoint | ||||||
| if let Some(region) = &self.region { | ||||||
| let region = region.trim_end_matches('/'); | ||||||
| return format!("https://{}/v1/ingest/{}", region, self.dataset); | ||||||
| } | ||||||
|
|
||||||
| format!("{}/v1/datasets/{}/ingest", url, self.dataset) | ||||||
| // Default: use cloud endpoint with legacy path format | ||||||
| format!("{}/v1/datasets/{}/ingest", CLOUD_URL, self.dataset) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -169,17 +200,119 @@ mod test { | |||||
| #[test] | ||||||
| fn generate_config() { | ||||||
| crate::test_util::test_generate_config::<super::AxiomConfig>(); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_region_domain_only() { | ||||||
| // region: mumbai.axiomdomain.co → https://mumbai.axiomdomain.co/v1/ingest/test-3 | ||||||
| let config = super::AxiomConfig { | ||||||
| region: Some("mumbai.axiomdomain.co".to_string()), | ||||||
| dataset: "test-3".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "https://mumbai.axiomdomain.co/v1/ingest/test-3"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_default_no_config() { | ||||||
| // No url, no region → https://api.axiom.co/v1/datasets/foo/ingest | ||||||
| let config = super::AxiomConfig { | ||||||
| dataset: "foo".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "https://api.axiom.co/v1/datasets/foo/ingest"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_url_with_custom_path() { | ||||||
| // url: http://localhost:3400/ingest → http://localhost:3400/ingest (as-is) | ||||||
| let config = super::AxiomConfig { | ||||||
| url: Some("http://localhost:3400/ingest".to_string()), | ||||||
| dataset: "meh".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "http://localhost:3400/ingest"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_url_without_path_backwards_compat() { | ||||||
| // url: https://api.eu.axiom.co/ → https://api.eu.axiom.co/v1/datasets/qoo/ingest | ||||||
| let config = super::AxiomConfig { | ||||||
| url: Some("https://api.eu.axiom.co".to_string()), | ||||||
| dataset: "qoo".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "https://api.eu.axiom.co/v1/datasets/qoo/ingest"); | ||||||
|
|
||||||
| // Also test with trailing slash | ||||||
| let config = super::AxiomConfig { | ||||||
| url: Some("https://api.eu.axiom.co/".to_string()), | ||||||
| dataset: "qoo".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "https://api.eu.axiom.co/v1/datasets/qoo/ingest"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_url_takes_precedence_over_region() { | ||||||
| // When both url and region are set, url takes precedence | ||||||
| let config = super::AxiomConfig { | ||||||
| url: Some("http://localhost:3400/ingest".to_string()), | ||||||
| region: Some("mumbai.axiomdomain.co".to_string()), | ||||||
| dataset: "test".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!(endpoint, "http://localhost:3400/ingest"); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_production_regional_edges() { | ||||||
| // Production AWS edge | ||||||
| let config = super::AxiomConfig { | ||||||
| region: Some("eu-central-1.aws.edge.axiom.co".to_string()), | ||||||
| dataset: "my-dataset".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!( | ||||||
| endpoint, | ||||||
| "https://eu-central-1.aws.edge.axiom.co/v1/ingest/my-dataset" | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_staging_environment_edges() { | ||||||
| // Staging environment edge | ||||||
| let config = super::AxiomConfig { | ||||||
| region: Some("us-east-1.edge.staging.axiomdomain.co".to_string()), | ||||||
| dataset: "test-dataset".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!( | ||||||
| endpoint, | ||||||
| "https://us-east-1.edge.staging.axiomdomain.co/v1/ingest/test-dataset" | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_dev_environment_edges() { | ||||||
| // Dev environment edge | ||||||
| let config = super::AxiomConfig { | ||||||
| url: Some("https://axiom.my-domain.com///".to_string()), | ||||||
| org_id: None, | ||||||
| dataset: "vector_rocks".to_string(), | ||||||
| region: Some("eu-west-1.edge.dev.axiomdomain.co".to_string()), | ||||||
| dataset: "dev-dataset".to_string(), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| let endpoint = config.build_endpoint(); | ||||||
| assert_eq!( | ||||||
| endpoint, | ||||||
| "https://axiom.my-domain.com/v1/datasets/vector_rocks/ingest" | ||||||
| "https://eu-west-1.edge.dev.axiomdomain.co/v1/ingest/dev-dataset" | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will also need to run
make generate-component-docs.