src/Microsoft.Azure.WebJobs.Logging.ApplicationInsights/Initializers/WebJobsTelemetryInitializer.cs (167 lines of code) (raw):
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.Azure.WebJobs.Logging.ApplicationInsights.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Azure.WebJobs.Logging.ApplicationInsights
{
internal class WebJobsTelemetryInitializer : ITelemetryInitializer
{
private static readonly string _currentProcessId = Process.GetCurrentProcess().Id.ToString();
private readonly string _sdkVersion;
private readonly string _roleInstanceName;
private readonly ApplicationInsightsLoggerOptions _options;
public WebJobsTelemetryInitializer(ISdkVersionProvider versionProvider, IRoleInstanceProvider roleInstanceProvider, IOptions<ApplicationInsightsLoggerOptions> options)
{
if (versionProvider == null)
{
throw new ArgumentNullException(nameof(versionProvider));
}
if (roleInstanceProvider == null)
{
throw new ArgumentNullException(nameof(roleInstanceProvider));
}
_sdkVersion = versionProvider.GetSdkVersion();
_roleInstanceName = roleInstanceProvider.GetRoleInstanceName();
_options = options.Value;
}
public void Initialize(ITelemetry telemetry)
{
if (telemetry == null)
{
return;
}
var telemetryContext = telemetry.Context;
telemetryContext.Cloud.RoleInstance = _roleInstanceName;
if (telemetryContext.Location.Ip == null)
{
telemetryContext.Location.Ip = LoggingConstants.ZeroIpAddress;
}
// Do not apply state properties if optimization is enabled.
if (_options.EnableMetricsCustomDimensionOptimization && telemetry is MetricTelemetry)
{
// Remove the Host instance ID property, since it's not needed.
telemetryContext.Properties.Remove(LoggingConstants.HostInstanceIdKey);
return;
}
else
{
telemetryContext.Properties[LogConstants.ProcessIdKey] = _currentProcessId;
// Apply our special scope properties
IReadOnlyDictionary<string, object> scopeProps = DictionaryLoggerScope.GetMergedStateDictionaryOrNull();
// this could be telemetry tracked in scope of function call - then we should apply the logger scope
// or RequestTelemetry tracked by the WebJobs SDK or AppInsight SDK - then we should apply Activity.Tags
if (scopeProps?.Count > 0)
{
if (!telemetryContext.Properties.ContainsKey(LogConstants.InvocationIdKey))
{
if (scopeProps?.GetValueOrDefault<string>(ScopeKeys.FunctionInvocationId) is string invocationId)
{
telemetryContext.Properties[LogConstants.InvocationIdKey] = invocationId;
}
}
telemetryContext.Operation.Name = scopeProps.GetValueOrDefault<string>(ScopeKeys.FunctionName);
// Apply Category, LogLevel event details to all telemetry
if (!telemetryContext.Properties.ContainsKey(LogConstants.CategoryNameKey))
{
if (scopeProps.GetValueOrDefault<string>(LogConstants.CategoryNameKey) is string category)
{
telemetryContext.Properties[LogConstants.CategoryNameKey] = category;
}
}
if (!telemetryContext.Properties.ContainsKey(LogConstants.LogLevelKey))
{
if (scopeProps.GetValueOrDefault<LogLevel?>(LogConstants.LogLevelKey) is LogLevel logLevel)
{
telemetryContext.Properties[LogConstants.LogLevelKey] = logLevel.ToStringOptimized();
}
}
if (!telemetryContext.Properties.ContainsKey(LogConstants.EventIdKey))
{
if (scopeProps.GetValueOrDefault<int>(LogConstants.EventIdKey) is int eventId && eventId != 0)
{
telemetryContext.Properties[LogConstants.EventIdKey] = eventId.ToString(CultureInfo.InvariantCulture);
}
}
if (!telemetryContext.Properties.ContainsKey(LogConstants.EventNameKey))
{
if (scopeProps.GetValueOrDefault<string>(LogConstants.EventNameKey) is string eventName)
{
telemetryContext.Properties[LogConstants.EventNameKey] = eventName;
}
}
}
// we may track traces/dependencies after function scope ends - we don't want to update those
if (telemetry is RequestTelemetry request)
{
UpdateRequestProperties(request);
Activity currentActivity = Activity.Current;
if (currentActivity != null)
{
foreach (var tag in currentActivity.Tags)
{
// Apply well-known tags and custom properties,
// but ignore internal ai tags
if (!TryApplyProperty(request, tag) &&
!tag.Key.StartsWith("ai_"))
{
request.Properties[tag.Key] = tag.Value;
}
}
}
}
}
}
/// <summary>
/// Changes properties of the RequestTelemetry to match what Functions expects.
/// </summary>
/// <param name="request">The RequestTelemetry to update.</param>
private void UpdateRequestProperties(RequestTelemetry request)
{
request.Context.GetInternalContext().SdkVersion = _sdkVersion;
// If the code hasn't been set, it's not an HttpRequest (could be auto-tracked SB, etc).
// So we'll initialize it to 0
if (string.IsNullOrEmpty(request.ResponseCode))
{
request.ResponseCode = "0";
}
// If the Url is not null, it's an actual HttpRequest, as opposed to a
// Service Bus or other function invocation that we're tracking as a Request
if (request.Url != null)
{
if (!request.Properties.ContainsKey(LogConstants.HttpMethodKey))
{
// App Insights sets request.Name as 'VERB /path'. We want to extract the VERB.
var verbEnd = request.Name.IndexOf(' ');
if (verbEnd > 0)
{
request.Properties.Add(LogConstants.HttpMethodKey, request.Name.Substring(0, verbEnd));
}
}
if (!request.Properties.ContainsKey(LogConstants.HttpPathKey))
{
request.Properties.Add(LogConstants.HttpPathKey, request.Url.LocalPath);
}
// sanitize request Url - remove query string
if (!_options.EnableQueryStringTracing)
{
request.Url = new Uri(request.Url.GetLeftPart(UriPartial.Path));
}
}
}
/// <summary>
/// Tries to apply well-known properties from a KeyValuePair onto the RequestTelemetry.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="activityTag">Tag on the request activity.</param>
/// <returns>True if the tag was applied. Otherwise, false.</returns>
private bool TryApplyProperty(RequestTelemetry request, KeyValuePair<string, string> activityTag)
{
bool wasPropertySet = false;
if (activityTag.Key == LogConstants.NameKey)
{
request.Context.Operation.Name = activityTag.Value;
request.Name = activityTag.Value;
wasPropertySet = true;
}
else if (activityTag.Key == LogConstants.SucceededKey &&
bool.TryParse(activityTag.Value, out bool success))
{
// no matter what App Insights says about the response, we always
// want to use the function's result for Succeeded
request.Success = success;
wasPropertySet = true;
// Remove the Succeeded property if set
if (request.Properties.ContainsKey(LogConstants.SucceededKey))
{
request.Properties.Remove(LogConstants.SucceededKey);
}
}
else if (activityTag.Key == LoggingConstants.ClientIpKey)
{
request.Context.Location.Ip = activityTag.Value;
wasPropertySet = true;
}
return wasPropertySet;
}
}
}