@@ -41,6 +41,7 @@ public class Plugin
4141 private static readonly ILoggerFactory Factory = LoggerFactory . Create ( builder => builder . AddProvider ( new ConsoleLoggerProvider ( ) ) ) ;
4242 private static readonly ILogger Logger = Factory . CreateLogger < Plugin > ( ) ;
4343 private static readonly string ApplicationSignalsExporterEndpointConfig = "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT" ;
44+ private static readonly string MetricExporterConfig = "OTEL_METRICS_EXPORTER" ;
4445 private static readonly string MetricExportIntervalConfig = "OTEL_METRIC_EXPORT_INTERVAL" ;
4546 private static readonly int DefaultMetricExportInterval = 60000 ;
4647 private static readonly string DefaultProtocolEnvVarName = "OTEL_EXPORTER_OTLP_PROTOCOL" ;
@@ -59,6 +60,11 @@ public class Plugin
5960
6061 private static readonly string FormatOtelSampledTracesBinaryPrefix = "T1S" ;
6162 private static readonly string FormatOtelUnSampledTracesBinaryPrefix = "T1U" ;
63+ private static readonly string RuntimeMetricMeterName = "OpenTelemetry.Instrumentation.Runtime" ;
64+
65+ // As per https://opentelemetry.io/docs/specs/semconv/resource/#service
66+ // If service name is not specified, SDK defaults the service name starting with unknown_service
67+ private static readonly string OtelUnknownServicePrefix = "unknown_service" ;
6268
6369 private static readonly int LambdaSpanExportBatchSize = 10 ;
6470
@@ -119,27 +125,9 @@ public void TracerProviderInitialized(TracerProvider tracerProvider)
119125 // Disable Application Metrics for Lambda environment
120126 if ( ! AwsSpanProcessingUtil . IsLambdaEnvironment ( ) )
121127 {
122- string ? intervalConfigString = System . Environment . GetEnvironmentVariable ( MetricExportIntervalConfig ) ;
123- int exportInterval = DefaultMetricExportInterval ;
124- try
125- {
126- int parsedExportInterval = Convert . ToInt32 ( intervalConfigString ) ;
127- exportInterval = parsedExportInterval != 0 ? parsedExportInterval : DefaultMetricExportInterval ;
128- }
129- catch ( Exception )
130- {
131- Logger . Log ( LogLevel . Trace , "Could not convert OTEL_METRIC_EXPORT_INTERVAL to integer. Using default value 60000." ) ;
132- }
133-
134- if ( exportInterval . CompareTo ( DefaultMetricExportInterval ) > 0 )
135- {
136- exportInterval = DefaultMetricExportInterval ;
137- Logger . Log ( LogLevel . Information , "AWS Application Signals metrics export interval capped to {0}" , exportInterval ) ;
138- }
139-
140128 // https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md#enable-metric-exporter
141129 // for setting the temporatityPref.
142- var metricReader = new PeriodicExportingMetricReader ( this . ApplicationSignalsExporterProvider ( ) , exportInterval )
130+ var metricReader = new PeriodicExportingMetricReader ( this . CreateApplicationSignalsMetricExporter ( ) , GetMetricExportInterval ( ) )
143131 {
144132 TemporalityPreference = MetricReaderTemporalityPreference . Delta ,
145133 } ;
@@ -229,6 +217,40 @@ public TracerProviderBuilder AfterConfigureTracerProvider(TracerProviderBuilder
229217 return builder ;
230218 }
231219
220+ /// <summary>
221+ /// // To configure metrics SDK after Auto Instrumentation configured SDK
222+ /// </summary>
223+ /// <param name="builder">The metric provider builder</param>
224+ /// <returns>The configured metric provider builder</returns>
225+ public MeterProviderBuilder AfterConfigureMeterProvider ( MeterProviderBuilder builder )
226+ {
227+ if ( ! this . IsApplicationSignalsRuntimeEnabled ( ) )
228+ {
229+ return builder ;
230+ }
231+
232+ var exporters = System . Environment . GetEnvironmentVariable ( MetricExporterConfig ) ;
233+ if ( ! string . IsNullOrEmpty ( exporters ) && exporters . Contains ( "none" ) )
234+ {
235+ Logger . Log ( LogLevel . Information , "Install runtime metric filter in metrics collection." ) ;
236+ builder . AddView ( instrument => instrument . Meter . Name == RuntimeMetricMeterName
237+ ? null
238+ : MetricStreamConfiguration . Drop ) ;
239+ }
240+
241+ var runtimeScopeName = new HashSet < string > ( ) { RuntimeMetricMeterName } ;
242+ var metricReader = new PeriodicExportingMetricReader (
243+ this . CreateScopeBasedOtlpMetricExporter ( runtimeScopeName ) , GetMetricExportInterval ( ) )
244+ {
245+ TemporalityPreference = MetricReaderTemporalityPreference . Delta ,
246+ } ;
247+
248+ builder . AddReader ( metricReader ) ;
249+ Logger . Log ( LogLevel . Information , "AWS Application Signals runtime metrics enabled." ) ;
250+
251+ return builder ;
252+ }
253+
232254 /// <summary>
233255 /// To configure Resource with resource detectors and <see cref="DistroAttributes"/>
234256 /// Check <see cref="ResourceBuilderCustomizer"/> for more information.
@@ -357,6 +379,58 @@ public void ConfigureTracesOptions(AspNetTraceInstrumentationOptions options)
357379 }
358380#endif
359381
382+ private static int GetMetricExportInterval ( )
383+ {
384+ var intervalConfigString = System . Environment . GetEnvironmentVariable ( MetricExportIntervalConfig ) ;
385+ var exportInterval = DefaultMetricExportInterval ;
386+ try
387+ {
388+ var parsedExportInterval = Convert . ToInt32 ( intervalConfigString ) ;
389+ exportInterval = parsedExportInterval != 0 ? parsedExportInterval : DefaultMetricExportInterval ;
390+ }
391+ catch ( Exception )
392+ {
393+ Logger . Log ( LogLevel . Warning , "Could not convert OTEL_METRIC_EXPORT_INTERVAL to integer. Using default value 60000." ) ;
394+ }
395+
396+ if ( exportInterval . CompareTo ( DefaultMetricExportInterval ) > 0 )
397+ {
398+ exportInterval = DefaultMetricExportInterval ;
399+ Logger . Log ( LogLevel . Information , "AWS Application Signals metrics export interval capped to {0}" , exportInterval ) ;
400+ }
401+
402+ return exportInterval ;
403+ }
404+
405+ private static void ConfigureOtlpExporterOptions ( OtlpExporterOptions options )
406+ {
407+ var applicationSignalsEndpoint = System . Environment . GetEnvironmentVariable ( ApplicationSignalsExporterEndpointConfig ) ;
408+ var protocolString = System . Environment . GetEnvironmentVariable ( DefaultProtocolEnvVarName ) ?? "http/protobuf" ;
409+ OtlpExportProtocol protocol ;
410+
411+ switch ( protocolString )
412+ {
413+ case "http/protobuf" :
414+ applicationSignalsEndpoint = applicationSignalsEndpoint ?? "http://localhost:4316/v1/metrics" ;
415+ protocol = OtlpExportProtocol . HttpProtobuf ;
416+ break ;
417+ case "grpc" :
418+ applicationSignalsEndpoint = applicationSignalsEndpoint ?? "http://localhost:4315" ;
419+ protocol = OtlpExportProtocol . Grpc ;
420+ break ;
421+ default :
422+ throw new NotSupportedException ( "Unsupported AWS Application Signals export protocol: " + protocolString ) ;
423+ }
424+
425+ options . Endpoint = new Uri ( applicationSignalsEndpoint ) ;
426+ options . Protocol = protocol ;
427+
428+ Logger . Log (
429+ LogLevel . Debug , "AWS Application Signals export protocol: %{0}" , options . Protocol ) ;
430+ Logger . Log (
431+ LogLevel . Debug , "AWS Application Signals export endpoint: %{0}" , options . Endpoint ) ;
432+ }
433+
360434 // This new function runs the sampler a second time after the needed attributes (such as UrlPath and HttpTarget)
361435 // are finally available from the http instrumentation libraries. The sampler hooked into the Opentelemetry SDK
362436 // runs right before any activity is started so for the purposes of our X-Ray sampler, that isn't work and breaks
@@ -401,9 +475,23 @@ private bool IsApplicationSignalsEnabled()
401475 return System . Environment . GetEnvironmentVariable ( ApplicationSignalsEnabledConfig ) == "true" ;
402476 }
403477
478+ private bool IsApplicationSignalsRuntimeEnabled ( )
479+ {
480+ return false ;
481+ }
482+
404483 private ResourceBuilder ResourceBuilderCustomizer ( ResourceBuilder builder )
405484 {
406485 builder . AddAttributes ( DistroAttributes ) ;
486+ var resource = builder . Build ( ) ;
487+ var serviceName = ( string ? ) resource . Attributes . FirstOrDefault ( attr => attr . Key == ResourceSemanticConventions . AttributeServiceName ) . Value ;
488+ if ( serviceName == null || serviceName . StartsWith ( OtelUnknownServicePrefix ) )
489+ {
490+ Logger . Log ( LogLevel . Warning , "No valid service name provided." ) ;
491+ serviceName = AwsSpanProcessingUtil . UnknownService ;
492+ }
493+
494+ builder . AddAttributes ( new Dictionary < string , object > { { AwsAttributeKeys . AttributeAWSLocalService , serviceName } } ) ;
407495
408496 // ResourceDetectors are enabled by default. Adding config to be able to disable during local testing
409497 var resourceDetectorsEnabled = System . Environment . GetEnvironmentVariable ( ResourceDetectorEnableConfig ) ?? "true" ;
@@ -428,39 +516,21 @@ private ResourceBuilder ResourceBuilderCustomizer(ResourceBuilder builder)
428516 return builder ;
429517 }
430518
431- private OtlpMetricExporter ApplicationSignalsExporterProvider ( )
519+ private OtlpMetricExporter CreateApplicationSignalsMetricExporter ( )
432520 {
433521 var options = new OtlpExporterOptions ( ) ;
434-
435- string ? applicationSignalsEndpoint = System . Environment . GetEnvironmentVariable ( ApplicationSignalsExporterEndpointConfig ) ;
436- string ? protocolString = System . Environment . GetEnvironmentVariable ( DefaultProtocolEnvVarName ) ?? "http/protobuf" ;
437- OtlpExportProtocol protocol ;
438- if ( protocolString == "http/protobuf" )
439- {
440- applicationSignalsEndpoint = applicationSignalsEndpoint ?? "http://localhost:4316/v1/metrics" ;
441- protocol = OtlpExportProtocol . HttpProtobuf ;
442- }
443- else if ( protocolString == "grpc" )
444- {
445- applicationSignalsEndpoint = applicationSignalsEndpoint ?? "http://localhost:4315" ;
446- protocol = OtlpExportProtocol . Grpc ;
447- }
448- else
449- {
450- throw new NotSupportedException ( "Unsupported AWS Application Signals export protocol: " + protocolString ) ;
451- }
452-
453- options . Endpoint = new Uri ( applicationSignalsEndpoint ) ;
454- options . Protocol = protocol ;
455-
456- Logger . Log (
457- LogLevel . Debug , "AWS Application Signals export protocol: %{0}" , options . Protocol ) ;
458- Logger . Log (
459- LogLevel . Debug , "AWS Application Signals export endpoint: %{0}" , options . Endpoint ) ;
460-
522+ ConfigureOtlpExporterOptions ( options ) ;
461523 return new OtlpMetricExporter ( options ) ;
462524 }
463525
526+ private ScopeBasedOtlpMetricExporter CreateScopeBasedOtlpMetricExporter ( HashSet < string > registeredScopeNames )
527+ {
528+ var options = new ScopeBasedOtlpMetricExporter . ScopeBasedOtlpExporterOptions ( ) ;
529+ ConfigureOtlpExporterOptions ( options ) ;
530+ options . RegisteredScopeNames = registeredScopeNames ;
531+ return new ScopeBasedOtlpMetricExporter ( options ) ;
532+ }
533+
464534 private bool HasCustomTracesEndpoint ( )
465535 {
466536 // detect if running in AWS Lambda environment
0 commit comments