33
44using System ;
55using System . Collections . Generic ;
6+ using System . ComponentModel ;
7+ using System . Diagnostics . Tracing ;
68using System . Runtime . InteropServices ;
9+ using System . Threading ;
10+ using Azure . Identity ;
711using Microsoft . ApplicationInsights ;
812using Microsoft . ApplicationInsights . Channel ;
913using Microsoft . ApplicationInsights . DataContracts ;
1216using Microsoft . ApplicationInsights . WindowsServer . TelemetryChannel ;
1317using Microsoft . Azure . WebJobs . Host ;
1418using Microsoft . Azure . WebJobs . Host . Executors ;
15- using Microsoft . Azure . WebJobs . Host . Scale ;
1619using Microsoft . Azure . WebJobs . Hosting ;
1720using Microsoft . Azure . WebJobs . Logging ;
1821using Microsoft . Azure . WebJobs . Logging . ApplicationInsights ;
4245using Microsoft . Extensions . Logging . Abstractions ;
4346using Microsoft . Extensions . Logging . Configuration ;
4447using Microsoft . Extensions . Options ;
48+ using TokenCredentialOptions = Microsoft . Azure . WebJobs . Logging . ApplicationInsights . TokenCredentialOptions ;
4549
4650namespace Microsoft . Azure . WebJobs . Script
4751{
@@ -232,8 +236,9 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
232236 }
233237 catch ( Exception ex )
234238 {
235- string appInsightsConnStr = context . Configuration [ EnvironmentSettingNames . AppInsightsConnectionString ] ;
236- RecordAndThrowExternalStartupException ( "Error configuring services in an external startup class." , ex , loggerFactory , appInsightsConnStr ) ;
239+ string appInsightsConnStr = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsConnectionString , context . Configuration ) ;
240+ string appInsightsAuthStr = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsAuthenticationString , context . Configuration ) ;
241+ RecordAndThrowExternalStartupException ( "Error configuring services in an external startup class." , ex , loggerFactory , appInsightsConnStr , appInsightsAuthStr ) ;
237242 }
238243 }
239244
@@ -260,8 +265,9 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
260265 catch ( Exception ex )
261266 {
262267 // Go directly to the environment; We have no valid configuration from the customer at this point.
263- string appInsightsConnStr = Environment . GetEnvironmentVariable ( EnvironmentSettingNames . AppInsightsConnectionString ) ;
264- RecordAndThrowExternalStartupException ( "Error building configuration in an external startup class." , ex , loggerFactory , appInsightsConnStr ) ;
268+ string appInsightsConnStr = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsConnectionString ) ;
269+ string appInsightsAuthStr = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsAuthenticationString ) ;
270+ RecordAndThrowExternalStartupException ( "Error building configuration in an external startup class." , ex , loggerFactory , appInsightsConnStr , appInsightsAuthStr ) ;
265271 }
266272 } ) ;
267273 }
@@ -402,16 +408,37 @@ public static IHostBuilder SetAzureFunctionsConfigurationRoot(this IHostBuilder
402408
403409 internal static void ConfigureApplicationInsights ( HostBuilderContext context , ILoggingBuilder builder )
404410 {
405- string appInsightsInstrumentationKey = context . Configuration [ EnvironmentSettingNames . AppInsightsInstrumentationKey ] ;
406- string appInsightsConnectionString = context . Configuration [ EnvironmentSettingNames . AppInsightsConnectionString ] ;
411+ string appInsightsInstrumentationKey = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsInstrumentationKey , context . Configuration ) ;
412+ string appInsightsConnectionString = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsConnectionString , context . Configuration ) ;
407413
408414 // Initializing AppInsights services during placeholder mode as well to avoid the cost of JITting these objects during specialization
409415 if ( ! string . IsNullOrEmpty ( appInsightsInstrumentationKey ) || ! string . IsNullOrEmpty ( appInsightsConnectionString ) || SystemEnvironment . Instance . IsPlaceholderModeEnabled ( ) )
410416 {
417+ string eventLogLevel = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsEventListenerLogLevel , context . Configuration ) ;
418+ string authString = GetConfigurationValue ( EnvironmentSettingNames . AppInsightsAuthenticationString , context . Configuration ) ;
419+ // Initialize ScriptTelemetryInitializer before any other telemetry initializers.
420+ // This will allow HostInstanceId to be removed as part of MetricsCustomDimensionOptimization.
421+ builder . Services . AddSingleton < ITelemetryInitializer , ScriptTelemetryInitializer > ( ) ;
411422 builder . AddApplicationInsightsWebJobs ( o =>
412423 {
413424 o . InstrumentationKey = appInsightsInstrumentationKey ;
414425 o . ConnectionString = appInsightsConnectionString ;
426+
427+ if ( ! string . IsNullOrEmpty ( eventLogLevel ) )
428+ {
429+ if ( Enum . TryParse ( eventLogLevel , ignoreCase : true , out EventLevel level ) )
430+ {
431+ o . DiagnosticsEventListenerLogLevel = level ;
432+ }
433+ else
434+ {
435+ throw new InvalidEnumArgumentException ( $ "Invalid `{ EnvironmentSettingNames . AppInsightsEventListenerLogLevel } `.") ;
436+ }
437+ }
438+ if ( ! string . IsNullOrEmpty ( authString ) )
439+ {
440+ o . TokenCredentialOptions = TokenCredentialOptions . ParseAuthenticationString ( authString ) ;
441+ }
415442 } , t =>
416443 {
417444 if ( t . TelemetryChannel is ServerTelemetryChannel channel )
@@ -425,7 +452,6 @@ internal static void ConfigureApplicationInsights(HostBuilderContext context, IL
425452
426453 builder . Services . ConfigureOptions < ApplicationInsightsLoggerOptionsSetup > ( ) ;
427454 builder . Services . AddSingleton < ISdkVersionProvider , FunctionsSdkVersionProvider > ( ) ;
428- builder . Services . AddSingleton < ITelemetryInitializer , ScriptTelemetryInitializer > ( ) ;
429455
430456 if ( SystemEnvironment . Instance . IsPlaceholderModeEnabled ( ) )
431457 {
@@ -502,7 +528,7 @@ private static T GetAndRemove<T>(this IDictionary<object, object> dictionary, st
502528 }
503529 }
504530
505- private static void RecordAndThrowExternalStartupException ( string message , Exception ex , ILoggerFactory loggerFactory , string appInsightsConnStr )
531+ private static void RecordAndThrowExternalStartupException ( string message , Exception ex , ILoggerFactory loggerFactory , string appInsightsConnStr , string appInsightsAuthString )
506532 {
507533 var startupEx = new ExternalStartupException ( message , ex ) ;
508534
@@ -511,22 +537,46 @@ private static void RecordAndThrowExternalStartupException(string message, Excep
511537
512538 // Send the error to App Insights if possible. This is happening during ScriptHost construction so we
513539 // have no existing TelemetryClient to use. Create a one-off client and flush it ASAP.
514- if ( appInsightsConnStr is not null )
540+ if ( ! string . IsNullOrEmpty ( appInsightsConnStr ) )
515541 {
516542 using TelemetryConfiguration telemetryConfiguration = new ( )
517543 {
518544 ConnectionString = appInsightsConnStr ,
519545 TelemetryChannel = new InMemoryChannel ( )
520546 } ;
521547
548+ if ( ! string . IsNullOrEmpty ( appInsightsAuthString ) )
549+ {
550+ TokenCredentialOptions credentialOptions = TokenCredentialOptions . ParseAuthenticationString ( appInsightsAuthString ) ;
551+ // Default is connection string based ingestion
552+ telemetryConfiguration . SetAzureTokenCredential ( new ManagedIdentityCredential ( credentialOptions . ClientId ) ) ;
553+ }
554+
522555 TelemetryClient telemetryClient = new ( telemetryConfiguration ) ;
523556 telemetryClient . Context . GetInternalContext ( ) . SdkVersion = new ApplicationInsightsSdkVersionProvider ( ) . GetSdkVersion ( ) ;
524557 telemetryClient . TrackTrace ( startupEx . ToString ( ) , SeverityLevel . Error ) ;
525558 telemetryClient . TrackException ( startupEx ) ;
526559 telemetryClient . Flush ( ) ;
560+ // Flush is async, sleep for 1 sec to give it time to flush
561+ Thread . Sleep ( 1000 ) ;
527562 }
528-
529563 throw startupEx ;
530564 }
565+
566+ private static string GetConfigurationValue ( string key , IConfiguration configuration = null )
567+ {
568+ if ( configuration != null && configuration [ key ] is string configValue )
569+ {
570+ return configValue ;
571+ }
572+ else if ( Environment . GetEnvironmentVariable ( key ) is string envValue )
573+ {
574+ return envValue ;
575+ }
576+ else
577+ {
578+ return null ;
579+ }
580+ }
531581 }
532582}
0 commit comments