@@ -462,26 +462,38 @@ impl<E: ConfigExtension> ConfigBuilder<E> {
462462 . clone_from ( & self . config . trace_propagation_style ) ;
463463 }
464464
465+ // Trim trailing slashes: some consumers validate `dd_url`/`url` against a strict
466+ // prefix pattern (e.g. dogstatsd's `DdUrl`/`DdDdUrl`) that rejects a trailing slash
467+ // with a `UrlPrefixError`.
468+ self . config . dd_url = trim_url ( & self . config . dd_url ) ;
469+ self . config . url = trim_url ( & self . config . url ) ;
470+
465471 // If Logs URL is not set, set it to the default
466472 if self . config . logs_config_logs_dd_url . trim ( ) . is_empty ( ) {
467473 self . config . logs_config_logs_dd_url = build_fqdn_logs ( self . config . site . clone ( ) ) ;
468474 } else {
469475 self . config . logs_config_logs_dd_url =
470- logs_intake_url ( self . config . logs_config_logs_dd_url . as_str ( ) ) ;
476+ logs_intake_url ( & trim_url ( & self . config . logs_config_logs_dd_url ) ) ;
471477 }
472478
473479 // If APM URL is not set, set it to the default
474- if self . config . apm_dd_url . is_empty ( ) {
480+ if self . config . apm_dd_url . trim ( ) . is_empty ( ) {
475481 self . config . apm_dd_url = trace_intake_url ( self . config . site . clone ( ) . as_str ( ) ) ;
476482 } else {
477483 // If APM URL is set, add the site to the URL
478- self . config . apm_dd_url = trace_intake_url_prefixed ( self . config . apm_dd_url . as_str ( ) ) ;
484+ self . config . apm_dd_url = trace_intake_url_prefixed ( & trim_url ( & self . config . apm_dd_url ) ) ;
479485 }
480486
481487 self . config . clone ( )
482488 }
483489}
484490
491+ #[ inline]
492+ #[ must_use]
493+ fn trim_url ( url : & str ) -> String {
494+ url. trim ( ) . trim_end_matches ( '/' ) . to_owned ( )
495+ }
496+
485497#[ inline]
486498#[ must_use]
487499fn build_fqdn_logs ( site : String ) -> String {
@@ -721,6 +733,24 @@ pub mod tests {
721733 } ) ;
722734 }
723735
736+ #[ test]
737+ fn test_logs_intake_url_trims_trailing_slash ( ) {
738+ figment:: Jail :: expect_with ( |jail| {
739+ jail. clear_env ( ) ;
740+ jail. set_env (
741+ "DD_LOGS_CONFIG_LOGS_DD_URL" ,
742+ "https://custom-intake.logs.datadoghq.com/" ,
743+ ) ;
744+
745+ let config = get_config ( Path :: new ( "" ) ) ;
746+ assert_eq ! (
747+ config. logs_config_logs_dd_url,
748+ "https://custom-intake.logs.datadoghq.com" . to_string( )
749+ ) ;
750+ Ok ( ( ) )
751+ } ) ;
752+ }
753+
724754 #[ test]
725755 fn test_support_pci_traces_intake_url ( ) {
726756 figment:: Jail :: expect_with ( |jail| {
@@ -748,6 +778,21 @@ pub mod tests {
748778 } ) ;
749779 }
750780
781+ #[ test]
782+ fn test_dd_dd_url_trims_trailing_slash ( ) {
783+ figment:: Jail :: expect_with ( |jail| {
784+ jail. clear_env ( ) ;
785+ jail. set_env ( "DD_DD_URL" , "https://test.agent.datadoghq.com/" ) ;
786+
787+ let config = get_config ( Path :: new ( "" ) ) ;
788+ assert_eq ! (
789+ config. dd_url,
790+ "https://test.agent.datadoghq.com" . to_string( )
791+ ) ;
792+ Ok ( ( ) )
793+ } ) ;
794+ }
795+
751796 #[ test]
752797 fn test_support_dd_url ( ) {
753798 figment:: Jail :: expect_with ( |jail| {
@@ -760,6 +805,33 @@ pub mod tests {
760805 } ) ;
761806 }
762807
808+ #[ test]
809+ fn test_dd_url_trims_trailing_slash ( ) {
810+ figment:: Jail :: expect_with ( |jail| {
811+ jail. clear_env ( ) ;
812+ jail. set_env ( "DD_URL" , "https://test.datadoghq.com/" ) ;
813+
814+ let config = get_config ( Path :: new ( "" ) ) ;
815+ assert_eq ! ( config. url, "https://test.datadoghq.com" . to_string( ) ) ;
816+ Ok ( ( ) )
817+ } ) ;
818+ }
819+
820+ #[ test]
821+ fn test_apm_dd_url_trims_trailing_slash ( ) {
822+ figment:: Jail :: expect_with ( |jail| {
823+ jail. clear_env ( ) ;
824+ jail. set_env ( "DD_APM_DD_URL" , "https://test.agent.datadoghq.com/" ) ;
825+
826+ let config = get_config ( Path :: new ( "" ) ) ;
827+ assert_eq ! (
828+ config. apm_dd_url,
829+ "https://test.agent.datadoghq.com/api/v0.2/traces" . to_string( )
830+ ) ;
831+ Ok ( ( ) )
832+ } ) ;
833+ }
834+
763835 #[ test]
764836 fn test_dd_dd_url_default ( ) {
765837 figment:: Jail :: expect_with ( |jail| {
0 commit comments