1+ using  Aspire . Hosting . ApplicationModel ; 
2+ using  Aspire . Hosting . Lifecycle ; 
3+ using  Microsoft . Extensions . Configuration ; 
4+ using  Microsoft . Extensions . Hosting ; 
5+ 
6+ namespace  Aspire . Hosting ; 
7+ 
8+ /// <summary> 
9+ /// Extension methods to add the collector resource 
10+ /// </summary> 
11+ public  static class  CollectorExtensions 
12+ { 
13+     private  const  string  DashboardOtlpUrlVariableNameLegacy  =  "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL" ; 
14+     private  const  string  DashboardOtlpUrlVariableName  =  "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL" ; 
15+     private  const  string  DashboardOtlpApiKeyVariableName  =  "AppHost:OtlpApiKey" ; 
16+     private  const  string  DashboardOtlpUrlDefaultValue  =  "http://localhost:18889" ; 
17+ 
18+     /// <summary> 
19+     /// Adds an OpenTelemetry Collector into the Aspire AppHost 
20+     /// </summary> 
21+     /// <param name="builder"></param> 
22+     /// <param name="name"></param> 
23+     /// <param name="configureSettings"></param> 
24+     /// <returns></returns> 
25+     public  static IResourceBuilder < CollectorResource >  AddOpenTelemetryCollector ( this  IDistributedApplicationBuilder  builder , 
26+         string  name , 
27+         Action < OpenTelemetryCollectorSettings >  configureSettings  =  null ! ) 
28+     { 
29+         var  url  =  builder . Configuration [ DashboardOtlpUrlVariableName ]  ?? 
30+             builder . Configuration [ DashboardOtlpUrlVariableNameLegacy ]  ?? 
31+             DashboardOtlpUrlDefaultValue ; 
32+ 
33+         var  settings  =  new  OpenTelemetryCollectorSettings ( ) ; 
34+         configureSettings ? . Invoke ( settings ) ; 
35+ 
36+         var  isHttpsEnabled  =  ! settings . ForceNonSecureReceiver  &&  url . StartsWith ( "https" ,  StringComparison . OrdinalIgnoreCase ) ; 
37+ 
38+         var  dashboardOtlpEndpoint  =  ReplaceLocalhostWithContainerHost ( url ,  builder . Configuration ) ; 
39+ 
40+         var  resource  =  new  CollectorResource ( name ) ; 
41+         var  resourceBuilder  =  builder . AddResource ( resource ) 
42+             . WithImage ( settings . CollectorImage ,  settings . CollectorVersion ) 
43+             . WithEnvironment ( "ASPIRE_ENDPOINT" ,  dashboardOtlpEndpoint ) 
44+             . WithEnvironment ( "ASPIRE_API_KEY" ,  builder . Configuration [ DashboardOtlpApiKeyVariableName ] ) ; 
45+ 
46+         if  ( settings . EnableGrpcEndpoint ) 
47+             resourceBuilder . WithEndpoint ( targetPort :  4317 ,  name :  CollectorResource . GRPCEndpointName ,  scheme :  isHttpsEnabled  ?  "https"  :  "http" ) ; 
48+         if  ( settings . EnableHttpEndpoint ) 
49+             resourceBuilder . WithEndpoint ( targetPort :  4318 ,  name :  CollectorResource . HTTPEndpointName ,  scheme :  isHttpsEnabled  ?  "https"  :  "http" ) ; 
50+ 
51+ 
52+         if  ( ! settings . ForceNonSecureReceiver  &&  isHttpsEnabled  &&  builder . ExecutionContext . IsRunMode  &&  builder . Environment . IsDevelopment ( ) ) 
53+         { 
54+             DevCertHostingExtensions . RunWithHttpsDevCertificate ( resourceBuilder ,  "HTTPS_CERT_FILE" ,  "HTTPS_CERT_KEY_FILE" ,  ( certFilePath ,  certKeyPath )  => 
55+             { 
56+                 if  ( settings . EnableHttpEndpoint ) 
57+                 { 
58+                     resourceBuilder . WithArgs ( 
59+                         $@ "--config=yaml:receivers::otlp::protocols::http::tls::cert_file: ""{ certFilePath } """,
60+                         $@" -- config= yaml: receivers::otlp::protocols::http::tls::key_file:  ""{ certKeyPath} """); 
61+                 } 
62+                 if (settings.EnableGrpcEndpoint) 
63+                 { 
64+                     resourceBuilder.WithArgs( 
65+                         $@"--config=yaml:receivers::otlp::protocols::grpc::tls::cert_file: ""{certFilePath}""" , 
66+                         $@ "--config=yaml:receivers::otlp::protocols::grpc::tls::key_file: ""{ certKeyPath } """);
67+                 } 
68+             }); 
69+         } 
70+         return resourceBuilder; 
71+     } 
72+ 
73+     /// <summary> 
74+     /// Force all apps to forward to the collector instead of the dashboard directly 
75+     /// </summary> 
76+     /// <param name=" builder"> < / param > 
77+     /// <returns></returns> 
78+     public  static  IResourceBuilder< CollectorResource>  WithAppForwarding( this  IResourceBuilder < CollectorResource >  builder ) 
79+     { 
80+         builder . ApplicationBuilder . Services . TryAddLifecycleHook < EnvironmentVariableHook > ( ) ; 
81+         return  builder; 
82+     } 
83+ 
84+     private static  string  ReplaceLocalhostWithContainerHost ( string  value ,  IConfiguration  configuration ) 
85+     { 
86+         var  hostName =  configuration[ "AppHost:ContainerHostname" ]  ??  "host.docker.internal" ; 
87+ 
88+         return  value. Replace ( "localhost" ,  hostName ,  StringComparison . OrdinalIgnoreCase ) 
89+                     . Replace ( "127.0.0.1" ,  hostName ) 
90+                     . Replace ( "[::1]" ,  hostName ) ; 
91+     } 
92+ 
93+     /// <summary> 
94+     /// Adds a config file to the collector 
95+     /// </summary> 
96+     /// <param name="builder"></param> 
97+     /// <param name="configPath"></param> 
98+     /// <returns></returns> 
99+     public static  IResourceBuilder < CollectorResource >  WithConfig ( this  IResourceBuilder < CollectorResource >  builder ,  string  configPath ) 
100+     { 
101+         var  configFileInfo =  new  FileInfo ( configPath ) ; 
102+         return  builder . WithBindMount ( configPath ,  $ "/config/{ configFileInfo . Name } ") 
103+             . WithArgs ( $ "--config=/config/{ configFileInfo . Name } ") ; 
104+     } 
105+ } 
0 commit comments