2
2
// Licensed under the MIT License. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
- using System . ComponentModel ;
6
- using System . Diagnostics ;
7
5
using System . Diagnostics . Tracing ;
8
6
using Azure . Core ;
9
7
using Azure . Identity ;
@@ -23,15 +21,29 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
23
21
{
24
22
internal static class OpenTelemetryConfigurationExtensions
25
23
{
26
- internal static void ConfigureOpenTelemetry ( this ILoggingBuilder loggingBuilder , HostBuilderContext context )
24
+ internal static void ConfigureOpenTelemetry ( this ILoggingBuilder loggingBuilder , HostBuilderContext context , TelemetryMode telemetryMode )
27
25
{
28
- string azMonConnectionString = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsConnectionString , context . Configuration ) ;
29
- TokenCredential credential = GetTokenCredential ( context . Configuration ) ;
30
-
31
- bool enableOtlp = false ;
32
- if ( ! string . IsNullOrEmpty ( GetConfigurationValue ( EnvironmentSettingNames . OtlpEndpoint , context . Configuration ) ) )
26
+ var connectionString = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsConnectionString , context . Configuration ) ;
27
+ var ( azMonConnectionString , credential , enableOtlp , enableAzureMonitor ) = telemetryMode switch
28
+ {
29
+ // Initializing OTel services during placeholder mode as well to avoid the cost of JITting these objects during specialization.
30
+ // Azure Monitor Exporter requires a connection string to be initialized. Use placeholder connection string if in placeholder mode.
31
+ TelemetryMode . Placeholder => (
32
+ "InstrumentationKey=00000000-0000-0000-0000-000000000000;" ,
33
+ null ,
34
+ true ,
35
+ true ) ,
36
+ _ => (
37
+ connectionString ,
38
+ GetTokenCredential ( context . Configuration ) ,
39
+ ! string . IsNullOrEmpty ( GetConfigurationValue ( EnvironmentSettingNames . OtlpEndpoint , context . Configuration ) ) ,
40
+ ! string . IsNullOrEmpty ( connectionString ) )
41
+ } ;
42
+
43
+ // If neither OTLP nor Azure Monitor is enabled, don't configure OpenTelemetry.
44
+ if ( ! enableOtlp && ! enableAzureMonitor )
33
45
{
34
- enableOtlp = true ;
46
+ return ;
35
47
}
36
48
37
49
loggingBuilder
@@ -42,82 +54,75 @@ internal static void ConfigureOpenTelemetry(this ILoggingBuilder loggingBuilder,
42
54
{
43
55
o . AddOtlpExporter ( ) ;
44
56
}
45
- if ( ! string . IsNullOrEmpty ( azMonConnectionString ) )
57
+ if ( enableAzureMonitor )
46
58
{
47
59
o . AddAzureMonitorLogExporter ( options => ConfigureAzureMonitorOptions ( options , azMonConnectionString , credential ) ) ;
48
60
}
49
61
o . IncludeFormattedMessage = true ;
50
62
o . IncludeScopes = false ;
51
63
} )
52
- // These are messages piped back to the host from the worker - we don't handle these anymore if the worker has OpenTelemetry enabled.
53
- // Instead, we expect the user's own code to be logging these where they want them to go.
54
- . AddFilter < OpenTelemetryLoggerProvider > ( "Function.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
55
- . AddFilter < OpenTelemetryLoggerProvider > ( "Azure.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
56
- // Host.Results and Host.Aggregator are used to emit metrics, ignoring these categories.
57
- . AddFilter < OpenTelemetryLoggerProvider > ( "Host.Results" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
58
- . AddFilter < OpenTelemetryLoggerProvider > ( "Host.Aggregator" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
59
- // Ignoring all Microsoft.Azure.WebJobs.* logs like /getScriptTag and /lock.
60
- . AddFilter < OpenTelemetryLoggerProvider > ( "Microsoft.Azure.WebJobs.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled ) ;
64
+ . AddDefaultOpenTelemetryFilters ( ) ;
61
65
62
66
// Azure SDK instrumentation is experimental.
63
67
AppContext . SetSwitch ( "Azure.Experimental.EnableActivitySource" , true ) ;
64
68
69
+ ConfigureTracing ( loggingBuilder , enableOtlp , enableAzureMonitor , azMonConnectionString , credential ) ;
70
+
71
+ ConfigureEventLogLevel ( loggingBuilder , context . Configuration ) ;
72
+ }
73
+
74
+ private static void ConfigureTracing ( ILoggingBuilder loggingBuilder , bool enableOtlp , bool enableAzureMonitor , string azMonConnectionString , TokenCredential credential )
75
+ {
65
76
loggingBuilder . Services . AddOpenTelemetry ( )
66
77
. ConfigureResource ( r => ConfigureResource ( r ) )
67
- . WithTracing ( b =>
78
+ . WithTracing ( builder =>
68
79
{
69
- b . AddSource ( "Azure.*" ) ;
70
- b . AddAspNetCoreInstrumentation ( ) ;
71
- b . AddHttpClientInstrumentation ( o =>
72
- {
73
- o . FilterHttpRequestMessage = _ =>
74
- {
75
- Activity activity = Activity . Current ? . Parent ;
76
- return activity == null || ! activity . Source . Name . Equals ( "Azure.Core.Http" ) ;
77
- } ;
78
- } ) ;
80
+ builder . AddSource ( "Azure.*" )
81
+ . AddAspNetCoreInstrumentation ( ) ;
79
82
80
83
if ( enableOtlp )
81
84
{
82
- b . AddOtlpExporter ( ) ;
85
+ builder . AddOtlpExporter ( ) ;
83
86
}
84
87
85
- if ( ! string . IsNullOrEmpty ( azMonConnectionString ) )
88
+ if ( enableAzureMonitor )
86
89
{
87
- b . AddAzureMonitorTraceExporter ( options => ConfigureAzureMonitorOptions ( options , azMonConnectionString , credential ) ) ;
88
- b . AddLiveMetrics ( options => ConfigureAzureMonitorOptions ( options , azMonConnectionString , credential ) ) ;
90
+ builder . AddAzureMonitorTraceExporter ( opt => ConfigureAzureMonitorOptions ( opt , azMonConnectionString , credential ) ) ;
91
+ builder . AddLiveMetrics ( opt => ConfigureAzureMonitorOptions ( opt , azMonConnectionString , credential ) ) ;
89
92
}
90
93
91
- b . AddProcessor ( ActivitySanitizingProcessor . Instance ) ;
92
- b . AddProcessor ( TraceFilterProcessor . Instance ) ;
94
+ builder . AddProcessor ( ActivitySanitizingProcessor . Instance ) ;
93
95
} ) ;
96
+ }
94
97
95
- string eventLogLevel = GetConfigurationValue ( EnvironmentSettingNames . OpenTelemetryEventListenerLogLevel , context . Configuration ) ;
96
- if ( ! string . IsNullOrEmpty ( eventLogLevel ) )
97
- {
98
- if ( Enum . TryParse ( eventLogLevel , ignoreCase : true , out EventLevel level ) )
99
- {
100
- loggingBuilder . Services . AddHostedService ( service => new OpenTelemetryEventListenerService ( level ) ) ;
101
- }
102
- else
103
- {
104
- throw new InvalidEnumArgumentException ( $ "Invalid '{ EnvironmentSettingNames . OpenTelemetryEventListenerLogLevel } ' of '{ eventLogLevel } '.") ;
105
- }
106
- }
107
- else
108
- {
109
- // Log all warnings and above by default.
110
- loggingBuilder . Services . AddHostedService ( service => new OpenTelemetryEventListenerService ( EventLevel . Warning ) ) ;
111
- }
98
+ private static ILoggingBuilder AddDefaultOpenTelemetryFilters ( this ILoggingBuilder loggingBuilder )
99
+ {
100
+ return loggingBuilder
101
+ // These are messages piped back to the host from the worker - we don't handle these anymore if the worker has OpenTelemetry enabled.
102
+ // Instead, we expect the user's own code to be logging these where they want them to go.
103
+ . AddFilter < OpenTelemetryLoggerProvider > ( "Function.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
104
+ . AddFilter < OpenTelemetryLoggerProvider > ( "Azure.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
105
+ // Host.Results and Host.Aggregator are used to emit metrics, ignoring these categories.
106
+ . AddFilter < OpenTelemetryLoggerProvider > ( "Host.Results" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
107
+ . AddFilter < OpenTelemetryLoggerProvider > ( "Host.Aggregator" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled )
108
+ // Ignoring all Microsoft.Azure.WebJobs.* logs like /getScriptTag and /lock.
109
+ . AddFilter < OpenTelemetryLoggerProvider > ( "Microsoft.Azure.WebJobs.*" , _ => ! ScriptHost . WorkerOpenTelemetryEnabled ) ;
110
+ }
112
111
113
- static ResourceBuilder ConfigureResource ( ResourceBuilder r )
114
- {
115
- r . AddDetector ( new FunctionsResourceDetector ( ) ) ;
112
+ private static void ConfigureEventLogLevel ( ILoggingBuilder loggingBuilder , IConfiguration configuration )
113
+ {
114
+ string eventLogLevel = GetConfigurationValue ( EnvironmentSettingNames . OpenTelemetryEventListenerLogLevel , configuration ) ;
115
+ EventLevel level = ! string . IsNullOrEmpty ( eventLogLevel ) &&
116
+ Enum . TryParse ( eventLogLevel , ignoreCase : true , out EventLevel parsedLevel )
117
+ ? parsedLevel
118
+ : EventLevel . Warning ;
116
119
117
- // Set the AI SDK to a key so we know all the telemetry came from the Functions Host
118
- // NOTE: This ties to \azure-sdk-for-net\sdk\monitor\Azure.Monitor.OpenTelemetry.Exporter\src\Internals\ResourceExtensions.cs :: AiSdkPrefixKey used in CreateAzureMonitorResource()
119
- return r ;
120
- }
120
+ loggingBuilder . Services . AddHostedService ( _ => new OpenTelemetryEventListenerService ( level ) ) ;
121
+ }
122
+
123
+ private static ResourceBuilder ConfigureResource ( ResourceBuilder builder )
124
+ {
125
+ return builder . AddDetector ( new FunctionsResourceDetector ( ) ) ;
121
126
}
122
127
123
128
private static string GetConfigurationValue ( string key , IConfiguration configuration = null )
0 commit comments