This commit is contained in:
Brett Samblanet 2022-07-29 12:30:09 -07:00 коммит произвёл GitHub
Родитель 4aaf5cbed4
Коммит 7b42c24f16
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
39 изменённых файлов: 1542 добавлений и 316 удалений

Просмотреть файл

@ -100,6 +100,7 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Blobs", "extensions\Worker.Extensions.Storage.Blobs\src\Worker.Extensions.Storage.Blobs.csproj", "{FC352905-BD72-4049-8D32-3CBB9304FDC8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Tables", "extensions\Worker.Extensions.Storage.Tables\src\Worker.Extensions.Storage.Tables.csproj", "{2B2B47E9-2973-4269-AC5D-E5C32BDD5346}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generators", "sdk\Sdk.Generators\Sdk.Generators.csproj", "{F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generator.Tests", "test\Sdk.Generator.Tests\Sdk.Generator.Tests.csproj", "{18A09B24-8646-40A6-BD85-2773AF567453}"
@ -112,6 +113,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample-In
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFxWorker", "samples\NetFxWorker\NetFxWorker.csproj", "{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.ApplicationInsights", "src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj", "{65DE66B6-568F-46AC-8F0D-C79A02F48214}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -274,6 +277,10 @@ Global
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.Build.0 = Release|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -322,6 +329,7 @@ Global
{922A387F-8595-4C74-ABF1-AEFF9530950C} = {B5821230-6E0A-4535-88A9-ED31B6F07596}
{22FCE0DF-65FE-4650-8202-765832C40E6D} = {922A387F-8595-4C74-ABF1-AEFF9530950C}
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6}
{65DE66B6-568F-46AC-8F0D-C79A02F48214} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A}

Просмотреть файл

@ -5,6 +5,7 @@
"sdk\\Sdk.Analyzers\\Sdk.Analyzers.csproj",
"sdk\\Sdk.Generators\\Sdk.Generators.csproj",
"sdk\\Sdk\\Sdk.csproj",
"src\\DotNetWorker.ApplicationInsights\\DotNetWorker.ApplicationInsights.csproj",
"src\\DotNetWorker.Core\\DotNetWorker.Core.csproj",
"src\\DotNetWorker.Grpc\\DotNetWorker.Grpc.csproj",
"src\\DotNetWorker\\DotNetWorker.csproj"

Просмотреть файл

@ -22,6 +22,7 @@
<ProjectReference Include="..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker\DotNetWorker.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -17,7 +18,12 @@ namespace FunctionApp
//<docsnippet_startup>
var host = new HostBuilder()
//<docsnippet_configure_defaults>
.ConfigureFunctionsWorkerDefaults()
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder
.AddApplicationInsights()
.AddApplicationInsightsLogger();
})
//</docsnippet_configure_defaults>
//<docsnippet_dependency_injection>
.ConfigureServices(s =>

Просмотреть файл

@ -1,7 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true"
}
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true"
}
}

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Microsoft.Azure.Functions.Worker.ApplicationInsights</PackageId>
<AssemblyName>Microsoft.Azure.Functions.Worker.ApplicationInsights</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.ApplicationInsights</RootNamespace>
<MajorProductVersion>1</MajorProductVersion>
<MinorProductVersion>0</MinorProductVersion>
<PatchProductVersion>0</PatchProductVersion>
<VersionSuffix>-preview1</VersionSuffix>
</PropertyGroup>
<Import Project="..\..\build\Common.props" />
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.20.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetWorker.Core\DotNetWorker.Core.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Diagnostics;
namespace Microsoft.Azure.Functions.Worker.Core.Diagnostics
{
/// Note: This class will eventually move in to the core worker assembly. Including it in the
/// ApplicationInsights package so we can utilize it during preview.
internal static class FunctionActivitySource
{
private const string InvocationIdKey = "InvocationId";
private const string NameKey = "Name";
private const string ProcessIdKey = "ProcessId";
private static readonly ActivitySource _activitySource = new("Microsoft.Azure.Functions.Worker");
private static readonly string _processId = Process.GetCurrentProcess().Id.ToString();
public static Activity? StartInvoke(FunctionContext context)
{
var activity = _activitySource.StartActivity("Invoke", ActivityKind.Internal, context.TraceContext.TraceParent);
if (activity is not null)
{
activity.AddTag(InvocationIdKey, context.InvocationId);
activity.AddTag(NameKey, context.FunctionDefinition.Name);
activity.AddTag(ProcessIdKey, _processId);
}
return activity;
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Core.Diagnostics;
using Microsoft.Azure.Functions.Worker.Middleware;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights;
internal class FunctionActivitySourceMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
using (FunctionActivitySource.StartInvoke(context))
{
await next.Invoke(context);
}
}
}

Просмотреть файл

@ -0,0 +1,85 @@
// 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.Linq;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.WorkerService;
using Microsoft.Azure.Functions.Worker.ApplicationInsights;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
namespace Microsoft.Azure.Functions.Worker
{
public static class FunctionsApplicationInsightsExtensions
{
/// <summary>
/// Adds Application Insights support by internally calling <see cref="ApplicationInsightsExtensions.AddApplicationInsightsTelemetryWorkerService(IServiceCollection)"/>.
/// </summary>
/// <param name="builder">The <see cref="IFunctionsWorkerApplicationBuilder"/></param>
/// <param name="configureOptions">Action to configure ApplicationInsights services.</param>
/// <returns>The <see cref="IFunctionsWorkerApplicationBuilder"/></returns>
public static IFunctionsWorkerApplicationBuilder AddApplicationInsights(this IFunctionsWorkerApplicationBuilder builder, Action<ApplicationInsightsServiceOptions>? configureOptions = null)
{
builder.AddCommonServices();
builder.Services.AddApplicationInsightsTelemetryWorkerService(options =>
{
configureOptions?.Invoke(options);
});
return builder;
}
/// <summary>
/// Adds the <see cref="ApplicationInsightsLoggerProvider"/> and disables the Functions host passthrough logger.
/// </summary>
/// <param name="builder">The <see cref="IFunctionsWorkerApplicationBuilder"/></param>
/// <param name="configureOptions">Action to configure ApplicationInsights logger.</param>
/// <returns>The <see cref="IFunctionsWorkerApplicationBuilder"/></returns>
public static IFunctionsWorkerApplicationBuilder AddApplicationInsightsLogger(this IFunctionsWorkerApplicationBuilder builder, Action<ApplicationInsightsLoggerOptions>? configureOptions = null)
{
builder.AddCommonServices();
builder.Services.AddLogging(logging =>
{
logging.AddApplicationInsights(options =>
{
options.IncludeScopes = false;
configureOptions?.Invoke(options);
});
});
return builder;
}
private static IFunctionsWorkerApplicationBuilder AddCommonServices(this IFunctionsWorkerApplicationBuilder builder)
{
builder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ITelemetryInitializer), typeof(FunctionsTelemetryInitializer), ServiceLifetime.Singleton));
builder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ITelemetryModule), typeof(FunctionsTelemetryModule), ServiceLifetime.Singleton));
// User logs will be written directly to Application Insights; this prevents duplicate logging.
builder.Services.AddSingleton<IUserLogWriter>(_ => NullUserLogWriter.Instance);
// This middleware is temporary for the preview. Eventually this behavior will move into the
// core worker assembly.
if (!builder.Services.Any(p => p.ImplementationType == typeof(FunctionActivitySourceMiddleware)))
{
builder.Services.AddSingleton<FunctionActivitySourceMiddleware>();
builder.Use(next =>
{
return async context =>
{
var middleware = context.InstanceServices.GetRequiredService<FunctionActivitySourceMiddleware>();
await middleware.Invoke(context, next);
};
});
}
return builder;
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// 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.Diagnostics;
using System.Reflection;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights
{
internal class FunctionsTelemetryInitializer : ITelemetryInitializer
{
private const string NameKey = "Name";
private readonly string _sdkVersion;
private readonly string _roleInstanceName;
internal FunctionsTelemetryInitializer(string sdkVersion, string roleInstanceName)
{
_sdkVersion = sdkVersion;
_roleInstanceName = roleInstanceName;
}
public FunctionsTelemetryInitializer() :
this(GetSdkVersion(), GetRoleInstanceName())
{
}
private static string GetSdkVersion()
{
return "azurefunctions-netiso: " + typeof(FunctionsTelemetryInitializer).Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()!.Version;
}
private static string GetRoleInstanceName()
{
const string ComputerNameKey = "COMPUTERNAME";
const string WebSiteInstanceIdKey = "WEBSITE_INSTANCE_ID";
const string ContainerNameKey = "CONTAINER_NAME";
string? instanceName = Environment.GetEnvironmentVariable(WebSiteInstanceIdKey);
if (string.IsNullOrEmpty(instanceName))
{
instanceName = Environment.GetEnvironmentVariable(ComputerNameKey);
if (string.IsNullOrEmpty(instanceName))
{
instanceName = Environment.GetEnvironmentVariable(ContainerNameKey);
}
}
return instanceName ?? Environment.MachineName;
}
public void Initialize(ITelemetry telemetry)
{
if (telemetry == null)
{
return;
}
telemetry.Context.Cloud.RoleInstance = _roleInstanceName;
telemetry.Context.GetInternalContext().SdkVersion = _sdkVersion;
telemetry.Context.Location.Ip ??= "0.0.0.0";
if (Activity.Current is not null)
{
foreach (var tag in Activity.Current.Tags)
{
switch (tag.Key)
{
case NameKey:
telemetry.Context.Operation.Name = tag.Value;
continue;
default:
break;
}
if (telemetry is ISupportProperties properties && !tag.Key.StartsWith("ai_"))
{
properties.Properties[tag.Key] = tag.Value;
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// 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.Diagnostics;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights
{
internal class FunctionsTelemetryModule : ITelemetryModule, IDisposable
{
private TelemetryClient _telemetryClient = default!;
private ActivityListener? _listener;
public void Initialize(TelemetryConfiguration configuration)
{
_telemetryClient = new TelemetryClient(configuration);
_listener = new ActivityListener
{
ShouldListenTo = source => source.Name.StartsWith("Microsoft.Azure.Functions.Worker"),
ActivityStarted = activity =>
{
var dependency = new DependencyTelemetry("Azure.Functions", activity.OperationName, activity.OperationName, null);
activity.SetCustomProperty("_depTel", dependency);
dependency.Start();
},
ActivityStopped = activity =>
{
var dependency = activity.GetCustomProperty("_depTel") as DependencyTelemetry;
dependency.Stop();
_telemetryClient.TrackDependency(dependency);
},
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
SampleUsingParentId = (ref ActivityCreationOptions<string> _) => ActivitySamplingResult.AllData
};
ActivitySource.AddActivityListener(_listener);
}
public void Dispose()
{
_telemetryClient?.Flush();
_listener?.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights
{
internal class NullUserLogWriter : IUserLogWriter
{
private NullUserLogWriter()
{
}
public static NullUserLogWriter Instance = new NullUserLogWriter();
public void WriteUserLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")]

Просмотреть файл

@ -4,7 +4,7 @@
namespace Microsoft.Azure.Functions.Worker.Diagnostics
{
/// <summary>
/// Represents an interface for sending logs directly to the Fucctions host.
/// Represents an interface for sending logs directly to the Functions host.
/// </summary>
internal interface IWorkerDiagnostics
{

Просмотреть файл

@ -1,33 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<PackageId>Microsoft.Azure.Functions.Worker.Core</PackageId>
<Description>This library provides the core functionality to build an Azure Functions .NET Worker, adding support for the isolated, out-of-process execution model.</Description>
<AssemblyName>Microsoft.Azure.Functions.Worker.Core</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.Core</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<MinorProductVersion>6</MinorProductVersion>
</PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<PackageId>Microsoft.Azure.Functions.Worker.Core</PackageId>
<Description>This library provides the core functionality to build an Azure Functions .NET Worker, adding support for the isolated, out-of-process execution model.</Description>
<AssemblyName>Microsoft.Azure.Functions.Worker.Core</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.Core</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<MinorProductVersion>7</MinorProductVersion>
<VersionSuffix>-preview1</VersionSuffix>
</PropertyGroup>
<Import Project="..\..\build\Common.props" />
<Import Project="..\..\build\Common.props" />
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<Compile Remove="Context\Features\IDictionaryExtensions.cs" />
<Compile Remove="FunctionsDebugger.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<Compile Remove="Context\Features\IDictionaryExtensions.cs" />
<Compile Remove="FunctionsDebugger.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Core" Version="1.10.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Core" Version="1.10.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
</ItemGroup>
</Project>

Просмотреть файл

@ -11,10 +11,12 @@ using Microsoft.Azure.Functions.Worker.Context.Features;
using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Core;
using Microsoft.Azure.Functions.Worker.Invocation;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Azure.Functions.Worker.OutputBindings;
using Microsoft.Azure.Functions.Worker.Pipeline;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
@ -78,6 +80,12 @@ namespace Microsoft.Extensions.DependencyInjection
}
});
services.AddSingleton<ILoggerProvider, WorkerLoggerProvider>();
services.AddSingleton(NullLogWriter.Instance);
services.AddSingleton<IUserLogWriter>(s => s.GetRequiredService<NullLogWriter>());
services.AddSingleton<ISystemLogWriter>(s => s.GetRequiredService<NullLogWriter>());
services.AddSingleton<IUserMetricWriter>(s => s.GetRequiredService<NullLogWriter>());
if (configure != null)
{
services.Configure(configure);

Просмотреть файл

@ -0,0 +1,27 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// An abstraction for writing system logs.
/// </summary>
public interface ISystemLogWriter
{
/// <summary>
/// Writes a system log entry.
/// </summary>
/// <typeparam name="TState">The type of the object to be written.</typeparam>
/// <param name="scopeProvider">The provider of scope data.</param>
/// <param name="categoryName">The category name for messages produced by the logger.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">Id of the event.</param>
/// <param name="state">The entry to be written. Can be also an object.</param>
/// <param name="exception">The exception related to this entry.</param>
/// <param name="formatter"> Function to create a <see cref="System.String"/> message of the state and exception.</param>
void WriteSystemLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// An abstraction for writing user logs.
/// </summary>
public interface IUserLogWriter
{
/// <summary>
/// Writes a user log entry.
/// </summary>
/// <typeparam name="TState">The type of the object to be written.</typeparam>
/// <param name="scopeProvider">The provider of scope data.</param>
/// <param name="categoryName">The category name for messages produced by the logger.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">Id of the event.</param>
/// <param name="state">The entry to be written. Can be also an object.</param>
/// <param name="exception">The exception related to this entry.</param>
/// <param name="formatter"> Function to create a <see cref="System.String"/> message of the state and exception.</param>
void WriteUserLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// An abstraction for writing user metrics.
/// </summary>
internal interface IUserMetricWriter
{
/// <summary>
/// Writes user metrics.
/// </summary>
/// <param name="scopeProvider">The provider of scope data.</param>
/// <param name="state">Additional properties.</param>
void WriteUserMetric(IExternalScopeProvider scopeProvider, IDictionary<string, object> state);
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// Minimalistic LogWriter that does nothing.
/// </summary>
internal class NullLogWriter : IUserLogWriter, ISystemLogWriter, IUserMetricWriter
{
private NullLogWriter()
{
}
/// <summary>
/// Returns the shared instance of <see cref="NullLogWriter"/>.
/// </summary>
public static NullLogWriter Instance = new NullLogWriter();
/// <inheritdoc/>
public void WriteSystemLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
/// <inheritdoc/>
public void WriteUserLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
/// <inheritdoc/>
public void WriteUserMetric(IExternalScopeProvider scopeProvider, string metricName, string metricValue, IDictionary<string, object> properties)
{
}
/// <inheritdoc/>
public void WriteUserMetric(IExternalScopeProvider scopeProvider, IDictionary<string, object> state)
{
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker.Logging.ApplicationInsights;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
internal class WorkerLogger : ILogger
{
private readonly string _category;
private readonly ISystemLogWriter _systemLogWriter;
private readonly IUserLogWriter _userLogWriter;
private readonly IUserMetricWriter _userMetricWriter;
private readonly IExternalScopeProvider _scopeProvider;
public WorkerLogger(string category, ISystemLogWriter systemLogWriter, IUserLogWriter userLogWriter, IUserMetricWriter userMetricWriter, IExternalScopeProvider scopeProvider)
{
_category = category;
_systemLogWriter = systemLogWriter;
_userLogWriter = userLogWriter;
_userMetricWriter = userMetricWriter;
_scopeProvider = scopeProvider;
}
public IDisposable BeginScope<TState>(TState state)
{
// The built-in DI wire-up guarantees that scope provider will be set.
return _scopeProvider.Push(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (WorkerMessage.IsSystemLog)
{
_systemLogWriter.WriteSystemLog(_scopeProvider, _category, logLevel, eventId, state, exception, formatter);
}
else
{
if (eventId.Name == LogConstants.MetricEventId.Name)
{
_userMetricWriter.WriteUserMetric(_scopeProvider, (state as IDictionary<string, object>) ?? new Dictionary<string, object>());
return;
}
_userLogWriter.WriteUserLog(_scopeProvider, _category, logLevel, eventId, state, exception, formatter);
}
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Logging
{
internal class WorkerLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private readonly ISystemLogWriter _systemLogWriter;
private readonly IUserLogWriter _userLogWriter;
private readonly IUserMetricWriter _userMetricWriter;
private IExternalScopeProvider? _scopeProvider;
public WorkerLoggerProvider(ISystemLogWriter systemLogWriter, IUserLogWriter userLogWriter, IUserMetricWriter userMetricWriter)
{
_systemLogWriter = systemLogWriter ?? throw new ArgumentNullException(nameof(systemLogWriter));
_userLogWriter = userLogWriter ?? throw new ArgumentNullException(nameof(userLogWriter));
_userMetricWriter = userMetricWriter ?? throw new ArgumentNullException(nameof(userMetricWriter));
}
public ILogger CreateLogger(string categoryName)
{
return new WorkerLogger(categoryName, _systemLogWriter, _userLogWriter, _userMetricWriter, _scopeProvider!);
}
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
}
public void Dispose()
{
}
}
}

Просмотреть файл

@ -0,0 +1,125 @@
// 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.Threading.Channels;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Grpc.Messages;
using Microsoft.Azure.Functions.Worker.Rpc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static Microsoft.Azure.Functions.Worker.Grpc.Messages.RpcLog.Types;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// A logger that sends logs back to the Functions host.
/// </summary>
internal class GrpcFunctionsHostLogWriter : ISystemLogWriter, IUserLogWriter, IUserMetricWriter
{
private readonly ChannelWriter<StreamingMessage> _channelWriter;
private readonly ObjectSerializer _serializer;
public GrpcFunctionsHostLogWriter(GrpcHostChannel channel, IOptions<WorkerOptions> workerOptions)
{
_channelWriter = channel?.Channel?.Writer ?? throw new ArgumentNullException(nameof(channel));
_serializer = workerOptions.Value.Serializer ?? throw new ArgumentNullException(nameof(workerOptions.Value.Serializer), "Serializer on WorkerOptions cannot be null");
}
public void WriteUserLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
Log(RpcLogCategory.User, scopeProvider, categoryName, logLevel, eventId, state, exception, formatter);
}
public void WriteUserMetric(IExternalScopeProvider scopeProvider, IDictionary<string, object> properties)
{
var response = new StreamingMessage();
var rpcMetric = new RpcLog
{
LogCategory = RpcLogCategory.CustomMetric,
};
foreach (var kvp in properties)
{
rpcMetric.PropertiesMap.Add(kvp.Key, kvp.Value.ToRpc(_serializer));
}
// Grab the invocation id from the current scope, if present.
rpcMetric = AppendInvocationIdToLog(rpcMetric, scopeProvider);
response.RpcLog = rpcMetric;
_channelWriter.TryWrite(response);
}
public void WriteSystemLog<TState>(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
Log(RpcLogCategory.System, scopeProvider, categoryName, logLevel, eventId, state, exception, formatter);
}
public void Log<TState>(RpcLogCategory category, IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var response = new StreamingMessage();
var rpcLog = new RpcLog
{
EventId = eventId.ToString(),
Exception = exception.ToRpcException(),
Category = categoryName,
LogCategory = category,
Level = ToRpcLogLevel(logLevel),
Message = formatter(state, exception)
};
// Grab the invocation id from the current scope, if present.
rpcLog = AppendInvocationIdToLog(rpcLog, scopeProvider);
response.RpcLog = rpcLog;
_channelWriter.TryWrite(response);
}
private RpcLog AppendInvocationIdToLog(RpcLog rpcLog, IExternalScopeProvider scopeProvider)
{
scopeProvider.ForEachScope((scope, log) =>
{
if (scope is IEnumerable<KeyValuePair<string, object>> properties)
{
foreach (var pair in properties)
{
if (pair.Key == FunctionInvocationScope.FunctionInvocationIdKey)
{
log.InvocationId = pair.Value?.ToString();
break;
}
}
}
},
rpcLog);
return rpcLog;
}
private static Level ToRpcLogLevel(LogLevel logLevel) =>
logLevel switch
{
LogLevel.Trace => Level.Trace,
LogLevel.Debug => Level.Debug,
LogLevel.Information => Level.Information,
LogLevel.Warning => Level.Warning,
LogLevel.Error => Level.Error,
LogLevel.Critical => Level.Critical,
_ => Level.None,
};
private class EmptyDisposable : IDisposable
{
public static IDisposable Instance = new EmptyDisposable();
public void Dispose()
{
}
}
}
}

Просмотреть файл

@ -1,143 +0,0 @@
// 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.Threading.Channels;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Grpc.Messages;
using Microsoft.Azure.Functions.Worker.Logging.ApplicationInsights;
using Microsoft.Azure.Functions.Worker.Rpc;
using Microsoft.Extensions.Logging;
using static Microsoft.Azure.Functions.Worker.Grpc.Messages.RpcLog.Types;
namespace Microsoft.Azure.Functions.Worker.Logging
{
/// <summary>
/// A logger that sends logs back to the Functions host.
/// </summary>
internal class GrpcFunctionsHostLogger : ILogger
{
private readonly string _category;
private readonly ChannelWriter<StreamingMessage> _channelWriter;
private readonly IExternalScopeProvider _scopeProvider;
private readonly ObjectSerializer _serializer;
public GrpcFunctionsHostLogger(string category, ChannelWriter<StreamingMessage> channelWriter, IExternalScopeProvider scopeProvider, ObjectSerializer serializer)
{
_category = category ?? throw new ArgumentNullException(nameof(category));
_channelWriter = channelWriter ?? throw new ArgumentNullException(nameof(channelWriter));
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}
public IDisposable BeginScope<TState>(TState state)
{
// The built-in DI wire-up guarantees that scope provider will be set.
return _scopeProvider!.Push(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (eventId.Name == LogConstants.MetricEventId.Name)
{
LogMetric((IDictionary<string, object>)state!);
}
else
{
var response = new StreamingMessage();
string message = formatter(state, exception);
var rpcLog = new RpcLog
{
EventId = eventId.ToString(),
Exception = exception.ToRpcException(),
Category = _category,
LogCategory = WorkerMessage.IsSystemLog ? RpcLogCategory.System : RpcLogCategory.User,
Level = ToRpcLogLevel(logLevel),
Message = message
};
// Grab the invocation id from the current scope, if present.
rpcLog = AppendInvocationIdToLog(rpcLog);
response.RpcLog = rpcLog;
_channelWriter.TryWrite(response);
}
}
private void LogMetric(IDictionary<string, object> state)
{
if (state == null)
{
return;
}
var response = new StreamingMessage();
var rpcMetric = new RpcLog
{
LogCategory = RpcLogCategory.CustomMetric,
};
foreach (var kvp in state)
{
rpcMetric.PropertiesMap.Add(kvp.Key, kvp.Value.ToRpc(_serializer));
}
// Grab the invocation id from the current scope, if present.
rpcMetric = AppendInvocationIdToLog(rpcMetric);
response.RpcLog = rpcMetric;
_channelWriter.TryWrite(response);
}
private RpcLog AppendInvocationIdToLog(RpcLog rpcLog)
{
_scopeProvider?.ForEachScope((scope, log) =>
{
if (scope is IEnumerable<KeyValuePair<string, object>> properties)
{
foreach (var pair in properties)
{
if (pair.Key == FunctionInvocationScope.FunctionInvocationIdKey)
{
log.InvocationId = pair.Value?.ToString();
break;
}
}
}
},
rpcLog);
return rpcLog;
}
private static Level ToRpcLogLevel(LogLevel logLevel) =>
logLevel switch
{
LogLevel.Trace => Level.Trace,
LogLevel.Debug => Level.Debug,
LogLevel.Information => Level.Information,
LogLevel.Warning => Level.Warning,
LogLevel.Error => Level.Error,
LogLevel.Critical => Level.Critical,
_ => Level.None,
};
private class EmptyDisposable : IDisposable
{
public static IDisposable Instance = new EmptyDisposable();
public void Dispose()
{
}
}
}
}

Просмотреть файл

@ -1,37 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading.Channels;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Azure.Functions.Worker.Grpc.Messages;
using Microsoft.Extensions.Logging;
using Azure.Core.Serialization;
using Microsoft.Extensions.Options;
using System;
namespace Microsoft.Azure.Functions.Worker.Diagnostics
{
internal class GrpcFunctionsHostLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private readonly ChannelWriter<StreamingMessage> _channelWriter;
private readonly ObjectSerializer _serializer;
private IExternalScopeProvider? _scopeProvider;
public GrpcFunctionsHostLoggerProvider(GrpcHostChannel outputChannel, IOptions<WorkerOptions> workerOptions)
{
_channelWriter = outputChannel.Channel.Writer;
_serializer = workerOptions?.Value?.Serializer ?? throw new ArgumentNullException(nameof(workerOptions.Value.Serializer), "Serializer on WorkerOptions cannot be null");
}
public ILogger CreateLogger(string categoryName) => new GrpcFunctionsHostLogger(categoryName, _channelWriter, _scopeProvider!, _serializer);
public void Dispose()
{
}
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
}
}
}

Просмотреть файл

@ -2,18 +2,17 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Channels;
using Grpc.Core;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Grpc;
using Microsoft.Azure.Functions.Worker.Grpc.Messages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static Microsoft.Azure.Functions.Worker.Grpc.Messages.FunctionRpc;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Azure.Functions.Worker.Grpc;
using Microsoft.Azure.Functions.Worker.Diagnostics;
#if NET5_0_OR_GREATER
using Grpc.Net.Client;
@ -43,12 +42,12 @@ namespace Microsoft.Extensions.DependencyInjection
// Channels
services.RegisterOutputChannel();
// Internal logging
services.AddLogging(logging =>
{
logging.Services.AddSingleton<ILoggerProvider, GrpcFunctionsHostLoggerProvider>();
logging.Services.AddSingleton<IWorkerDiagnostics, GrpcWorkerDiagnostics>();
});
// Internal logging
services.AddSingleton<GrpcFunctionsHostLogWriter>();
services.AddSingleton<IUserLogWriter>(p => p.GetRequiredService<GrpcFunctionsHostLogWriter>());
services.AddSingleton<ISystemLogWriter>(p => p.GetRequiredService<GrpcFunctionsHostLogWriter>());
services.AddSingleton<IUserMetricWriter>(p => p.GetRequiredService<GrpcFunctionsHostLogWriter>());
services.AddSingleton<IWorkerDiagnostics, GrpcWorkerDiagnostics>();
// FunctionMetadataProvider for worker driven function-indexing
services.AddSingleton<IFunctionMetadataProvider, DefaultFunctionMetadataProvider>();

Просмотреть файл

@ -1,47 +1,47 @@
// 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.Text.Json;
using Microsoft.Azure.Functions.Worker;
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Azure Functions extensions for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the core set of services for the Azure Functions worker.
/// This call also adds the default set of binding converters and gRPC support.
/// This call also adds a default ObjectSerializer that treats property names as case insensitive.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">The action used to configure <see cref="WorkerOptions"/>.</param>
/// <returns>The same <see cref="IFunctionsWorkerApplicationBuilder"/> for chaining.</returns>
public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerDefaults(this IServiceCollection services, Action<WorkerOptions>? configure = null)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddDefaultInputConvertersToWorkerOptions();
// Default Json serialization should ignore casing on property names
services.Configure<JsonSerializerOptions>(options =>
{
options.PropertyNameCaseInsensitive = true;
});
// Core services registration
var builder = services.AddFunctionsWorkerCore(configure);
// gRPC support
services.AddGrpc();
return builder;
}
}
}
using System;
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Azure Functions extensions for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the core set of services for the Azure Functions worker.
/// This call also adds the default set of binding converters and gRPC support.
/// This call also adds a default ObjectSerializer that treats property names as case insensitive.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">The action used to configure <see cref="WorkerOptions"/>.</param>
/// <returns>The same <see cref="IFunctionsWorkerApplicationBuilder"/> for chaining.</returns>
public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerDefaults(this IServiceCollection services, Action<WorkerOptions>? configure = null)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddDefaultInputConvertersToWorkerOptions();
// Default Json serialization should ignore casing on property names
services.Configure<JsonSerializerOptions>(options =>
{
options.PropertyNameCaseInsensitive = true;
});
// Core services registration
var builder = services.AddFunctionsWorkerCore(configure);
// gRPC support
services.AddGrpc();
return builder;
}
}
}

Просмотреть файл

@ -0,0 +1,208 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInsights.DependencyCollector;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector;
using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse;
using Microsoft.ApplicationInsights.WindowsServer;
using Microsoft.ApplicationInsights.WorkerService;
using Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers;
using Microsoft.Azure.Functions.Worker.ApplicationInsights;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.Tests.ApplicationInsights;
public class ApplicationInsightsConfigurationTests
{
[Fact]
public void AddApplicationInsights_AddsDefaults()
{
var builder = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker
.AddApplicationInsights();
});
IEnumerable<ServiceDescriptor> initializers = null;
IEnumerable<ServiceDescriptor> modules = null;
builder.ConfigureServices(services =>
{
initializers = services.Where(s => s.ServiceType == typeof(ITelemetryInitializer));
modules = services.Where(s => s.ServiceType == typeof(ITelemetryModule));
});
var provider = builder.Build().Services;
Assert.Collection(initializers,
t => Assert.Equal(typeof(FunctionsTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers.DomainNameRoleInstanceTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(HttpDependenciesParsingTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(ComponentVersionTelemetryInitializer), t.ImplementationType));
Assert.Collection(modules,
t => Assert.Equal(typeof(FunctionsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DiagnosticsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AppServicesHeartbeatTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AzureInstanceMetadataTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(PerformanceCollectorModule), t.ImplementationType),
t => Assert.Equal(typeof(QuickPulseTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DependencyTrackingTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(EventCounterCollectionModule), t.ImplementationType));
var middleware = provider.GetRequiredService<FunctionActivitySourceMiddleware>();
Assert.NotNull(middleware);
}
[Fact]
public void AddApplicationInsights_CallsConfigure()
{
bool called = false;
var builder = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker.AddApplicationInsights(o =>
{
Assert.NotNull(o);
called = true;
});
});
Assert.False(called);
var provider = builder.Build().Services;
var options = provider.GetRequiredService<IOptions<ApplicationInsightsServiceOptions>>();
Assert.NotNull(options.Value);
var middleware = provider.GetRequiredService<FunctionActivitySourceMiddleware>();
Assert.NotNull(middleware);
Assert.True(called);
}
[Fact]
public void AddApplicationInsightsLogger_AddsDefaults()
{
var builder = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker.AddApplicationInsightsLogger();
});
bool called = false;
builder.ConfigureServices(services =>
{
var loggerProviders = services.Where(s => s.ServiceType == typeof(ILoggerProvider));
Assert.Collection(loggerProviders,
t => Assert.Equal(typeof(WorkerLoggerProvider), t.ImplementationType),
t => Assert.Equal(typeof(ApplicationInsightsLoggerProvider), t.ImplementationType));
var initializers = services.Where(s => s.ServiceType == typeof(ITelemetryInitializer));
Assert.Collection(initializers,
t => Assert.Equal(typeof(FunctionsTelemetryInitializer), t.ImplementationType));
var modules = services.Where(s => s.ServiceType == typeof(ITelemetryModule));
Assert.Collection(modules,
t => Assert.Equal(typeof(FunctionsTelemetryModule), t.ImplementationType));
called = true;
});
var serviceProvider = builder.Build().Services;
var appInsightsOptions = serviceProvider.GetRequiredService<IOptions<ApplicationInsightsLoggerOptions>>();
Assert.False(appInsightsOptions.Value.IncludeScopes);
var userWriter = serviceProvider.GetRequiredService<IUserLogWriter>();
Assert.IsType<NullUserLogWriter>(userWriter);
var systemWriter = serviceProvider.GetRequiredService<ISystemLogWriter>();
Assert.IsNotType<NullLogWriter>(systemWriter);
var middleware = serviceProvider.GetRequiredService<FunctionActivitySourceMiddleware>();
Assert.NotNull(middleware);
Assert.True(called);
}
[Fact]
public void AddApplicationInsightsLogger_CallsConfigure()
{
bool called = false;
var builder = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker.AddApplicationInsightsLogger(o =>
{
Assert.NotNull(o);
called = true;
});
});
Assert.False(called);
var provider = builder.Build().Services;
var options = provider.GetRequiredService<IOptions<ApplicationInsightsLoggerOptions>>();
Assert.NotNull(options.Value);
var middleware = provider.GetRequiredService<FunctionActivitySourceMiddleware>();
Assert.NotNull(middleware);
Assert.True(called);
}
[Fact]
public void AddingServiceAndLogger_OnlyAddsServicesOnce()
{
var builder = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker =>
{
worker
.AddApplicationInsights()
.AddApplicationInsightsLogger();
});
IEnumerable<ServiceDescriptor> initializers = null;
IEnumerable<ServiceDescriptor> modules = null;
builder.ConfigureServices(services =>
{
initializers = services.Where(s => s.ServiceType == typeof(ITelemetryInitializer));
modules = services.Where(s => s.ServiceType == typeof(ITelemetryModule));
});
var provider = builder.Build().Services;
// Ensure that our Initializer and Module are added alongside the defaults
Assert.Collection(initializers,
t => Assert.Equal(typeof(FunctionsTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers.DomainNameRoleInstanceTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(HttpDependenciesParsingTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(ComponentVersionTelemetryInitializer), t.ImplementationType));
Assert.Collection(modules,
t => Assert.Equal(typeof(FunctionsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DiagnosticsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AppServicesHeartbeatTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AzureInstanceMetadataTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(PerformanceCollectorModule), t.ImplementationType),
t => Assert.Equal(typeof(QuickPulseTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DependencyTrackingTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(EventCounterCollectionModule), t.ImplementationType));
var middleware = provider.GetRequiredService<FunctionActivitySourceMiddleware>();
Assert.NotNull(middleware);
}
}

Просмотреть файл

@ -0,0 +1,142 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Azure.Functions.Worker.Context.Features;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Tests.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.Tests.ApplicationInsights;
public class EndToEndTests
{
private readonly TestTelemetryChannel _channel;
private readonly IHost _host;
private readonly IFunctionsApplication _application;
private readonly IInvocationFeaturesFactory _invocationFeatures;
public EndToEndTests()
{
_channel = new TestTelemetryChannel();
_host = new HostBuilder()
.ConfigureServices(services =>
{
var functionsBuilder = services.AddFunctionsWorkerCore();
functionsBuilder
.AddApplicationInsights(appInsightsOptions => appInsightsOptions.InstrumentationKey = "abc")
.AddApplicationInsightsLogger();
functionsBuilder.UseDefaultWorkerMiddleware();
services.AddDefaultInputConvertersToWorkerOptions();
// Register our own in-memory channel
services.AddSingleton<ITelemetryChannel>(_channel);
services.AddSingleton(_ => new Mock<IWorkerDiagnostics>().Object);
})
.Build();
_application = _host.Services.GetService<IFunctionsApplication>();
_invocationFeatures = _host.Services.GetService<IInvocationFeaturesFactory>();
}
[Fact]
public async Task Logger_SendsTraceAndDependencyTelemetry()
{
var def = new AppInsightsFunctionDefinition();
_application.LoadFunction(def);
var invocation = new TestFunctionInvocation(functionId: def.Id);
var features = _invocationFeatures.Create();
features.Set<FunctionInvocation>(invocation);
var inputConversionProvider = _host.Services.GetRequiredService<IInputConversionFeatureProvider>();
inputConversionProvider.TryCreate(typeof(DefaultInputConversionFeature), out var inputConversion);
features.Set<IFunctionBindingsFeature>(new TestFunctionBindingsFeature());
features.Set<IInputConversionFeature>(inputConversion);
var context = _application.CreateContext(features);
await _application.InvokeFunctionAsync(context);
void ValidateProperties(ISupportProperties props)
{
Assert.Equal(invocation.Id, props.Properties["InvocationId"]);
Assert.Contains("ProcessId", props.Properties.Keys);
}
var activity = AppInsightsFunctionDefinition.LastActivity;
// App Insights can potentially log this, which causes tests to be flaky. Explicitly ignore.
var aiTelemetry = _channel.Telemetries.Where(p => p is TraceTelemetry t && t.Message.Contains("AI: TelemetryChannel found a telemetry item"));
var telemetries = _channel.Telemetries.Except(aiTelemetry);
// Log written in test function should go to App Insights directly
Assert.Collection(telemetries,
t =>
{
var dependency = (DependencyTelemetry)t;
Assert.Equal("TestName", dependency.Context.Operation.Name);
Assert.Equal(activity.SpanId.ToString(), dependency.Context.Operation.ParentId);
ValidateProperties(dependency);
},
t =>
{
var trace = (TraceTelemetry)t;
Assert.Equal("Test", trace.Message);
Assert.Equal(SeverityLevel.Warning, trace.SeverityLevel);
// This ensures we've disabled scopes by default
Assert.DoesNotContain("AzureFunctions_InvocationId", trace.Properties.Keys);
Assert.Equal("TestName", trace.Context.Operation.Name);
Assert.Equal(activity.SpanId.ToString(), trace.Context.Operation.ParentId);
ValidateProperties(trace);
});
}
internal class AppInsightsFunctionDefinition : FunctionDefinition
{
public static readonly string DefaultPathToAssembly = typeof(AppInsightsFunctionDefinition).Assembly.Location;
public static readonly string DefaultEntryPoint = $"{typeof(AppInsightsFunctionDefinition).FullName}.{nameof(TestFunction)}";
public static readonly string DefaultId = "TestId";
public static readonly string DefaultName = "TestName";
public AppInsightsFunctionDefinition()
{
Parameters = (new[] { new FunctionParameter("context", typeof(FunctionContext)) }).ToImmutableArray();
}
public override ImmutableArray<FunctionParameter> Parameters { get; }
public override string PathToAssembly { get; } = DefaultPathToAssembly;
public override string EntryPoint { get; } = DefaultEntryPoint;
public override string Id { get; } = DefaultId;
public override string Name { get; } = DefaultName;
public override IImmutableDictionary<string, BindingMetadata> InputBindings { get; } = ImmutableDictionary<string, BindingMetadata>.Empty;
public override IImmutableDictionary<string, BindingMetadata> OutputBindings { get; } = ImmutableDictionary<string, BindingMetadata>.Empty;
public static Activity LastActivity;
public void TestFunction(FunctionContext context)
{
LastActivity = Activity.Current;
var logger = context.GetLogger("TestFunction");
logger.LogWarning("Test");
}
}
}

Просмотреть файл

@ -0,0 +1,45 @@
using System.Diagnostics;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.Azure.Functions.Worker.ApplicationInsights;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.Tests.ApplicationInsights;
public class FunctionsTelemetryInitializerTests
{
[Fact]
public void Initialize_SetsContextProperties()
{
var initializer = new FunctionsTelemetryInitializer("testversion", "testrolename");
var telemetry = new TraceTelemetry();
initializer.Initialize(telemetry);
Assert.Equal("testversion", telemetry.Context.GetInternalContext().SdkVersion);
Assert.Equal("testrolename", telemetry.Context.Cloud.RoleInstance);
}
[Fact]
public void Initialize_SetsProperties_WithActivityTags()
{
var activity = new Activity("operation");
var telemetry = new TraceTelemetry();
try
{
activity.Start();
activity.AddTag("Name", "MyFunction");
activity.AddTag("CustomKey", "CustomValue");
var initializer = new FunctionsTelemetryInitializer("testversion", "testrolename");
initializer.Initialize(telemetry);
}
finally
{
activity.Stop();
}
Assert.Equal("MyFunction", telemetry.Context.Operation.Name);
Assert.Equal("CustomValue", telemetry.Properties["CustomKey"]);
}
}

Просмотреть файл

@ -0,0 +1,29 @@
//// Copyright (c) .NET Foundation. All rights reserved.
//// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using Microsoft.ApplicationInsights.Channel;
namespace Microsoft.Azure.Functions.Worker.Tests.ApplicationInsights;
internal class TestTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> Telemetries { get; private set; } = new ConcurrentBag<ITelemetry>();
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public void Dispose()
{
}
public void Flush()
{
}
public void Send(ITelemetry item)
{
Telemetries.Add(item);
}
}

Просмотреть файл

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Grpc.Messages;
using Microsoft.Azure.Functions.Worker.Logging;
using Microsoft.Azure.Functions.Worker.Logging.ApplicationInsights;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -17,26 +18,24 @@ using static Microsoft.Azure.Functions.Worker.Grpc.Messages.RpcLog.Types;
namespace Microsoft.Azure.Functions.Worker.Tests.Diagnostics
{
public class GrpcHostLoggerTests
public class GrpcHostLogWriterTests
{
private readonly GrpcFunctionsHostLoggerProvider _provider;
private readonly Channel<StreamingMessage> _channel;
private readonly Channel<StreamingMessage> _channel = Channel.CreateUnbounded<StreamingMessage>();
private readonly IExternalScopeProvider _scopeProvider = new LoggerExternalScopeProvider();
private readonly GrpcFunctionsHostLogWriter _logWriter;
private readonly Func<string, Exception, string> _formatter = (s, e) => s;
public GrpcHostLoggerTests()
public GrpcHostLogWriterTests()
{
_channel = Channel.CreateUnbounded<StreamingMessage>();
var outputChannel = new GrpcHostChannel(_channel);
var workerOptions = Options.Create(new WorkerOptions { Serializer = new JsonObjectSerializer() });
_provider = new GrpcFunctionsHostLoggerProvider(outputChannel, workerOptions);
_provider.SetScopeProvider(new LoggerExternalScopeProvider());
_logWriter = new GrpcFunctionsHostLogWriter(outputChannel, workerOptions);
}
[Fact]
public async Task UserLog()
{
var logger = _provider.CreateLogger("TestLogger");
logger.LogInformation("user");
_logWriter.WriteUserLog(_scopeProvider, "TestLogger", LogLevel.Information, default(EventId), "user", null, _formatter);
_channel.Writer.Complete();
@ -58,10 +57,10 @@ namespace Microsoft.Azure.Functions.Worker.Tests.Diagnostics
[Fact]
public async Task CustomMetric()
{
var logger = _provider.CreateLogger("TestLogger");
logger.LogMetric("testMetric", 1d, new Dictionary<string, object>
_logWriter.WriteUserMetric(_scopeProvider, new Dictionary<string, object>
{
{"Name", "testMetric" },
{"Value", 1d },
{"foo", "bar" }
});
@ -94,10 +93,9 @@ namespace Microsoft.Azure.Functions.Worker.Tests.Diagnostics
[Fact]
public async Task SystemLog_WithException_AndScope()
{
var logger = _provider.CreateLogger("TestLogger");
Exception thrownException = null;
using (logger.BeginScope(new FunctionInvocationScope("MyFunction", "MyInvocationId")))
using (_scopeProvider.Push(new FunctionInvocationScope("MyFunction", "MyInvocationId")))
{
try
{
@ -105,14 +103,9 @@ namespace Microsoft.Azure.Functions.Worker.Tests.Diagnostics
}
catch (Exception ex)
{
// The only way to log a system log.
var log = WorkerMessage.Define<string>(LogLevel.Trace, new EventId(1, "One"), "system log with {param}");
log(logger, "this", ex);
_logWriter.WriteSystemLog(_scopeProvider, "TestLogger", LogLevel.Trace, new EventId(1, "One"), "system log", ex, _formatter);
thrownException = ex;
}
// make sure this is now user
logger.LogInformation("user");
}
_channel.Writer.Complete();
@ -124,27 +117,20 @@ namespace Microsoft.Azure.Functions.Worker.Tests.Diagnostics
msgs.Add(msg);
}
Assert.Collection(msgs,
p =>
{
Assert.Equal("TestLogger", p.RpcLog.Category);
Assert.Equal(RpcLogCategory.System, p.RpcLog.LogCategory);
Assert.Equal("system log with this", p.RpcLog.Message);
Assert.Equal("One", p.RpcLog.EventId);
Assert.Equal("MyInvocationId", p.RpcLog.InvocationId);
Assert.Equal(thrownException.ToString(), p.RpcLog.Exception.Message);
Assert.Equal("Microsoft.Azure.Functions.Worker.Tests", p.RpcLog.Exception.Source);
Assert.Contains(nameof(SystemLog_WithException_AndScope), p.RpcLog.Exception.StackTrace);
},
p =>
{
Assert.Equal("TestLogger", p.RpcLog.Category);
Assert.Equal(RpcLogCategory.User, p.RpcLog.LogCategory);
Assert.Equal("user", p.RpcLog.Message);
Assert.Equal("0", p.RpcLog.EventId);
Assert.Equal("MyInvocationId", p.RpcLog.InvocationId);
Assert.Null(p.RpcLog.Exception);
});
List<Action<StreamingMessage>> expected = new();
expected.Add(p =>
{
Assert.Equal("TestLogger", p.RpcLog.Category);
Assert.Equal(RpcLogCategory.System, p.RpcLog.LogCategory);
Assert.Equal("system log", p.RpcLog.Message);
Assert.Equal("One", p.RpcLog.EventId);
Assert.Equal("MyInvocationId", p.RpcLog.InvocationId);
Assert.Equal(thrownException.ToString(), p.RpcLog.Exception.Message);
Assert.Equal("Microsoft.Azure.Functions.Worker.Tests", p.RpcLog.Exception.Source);
Assert.Contains(nameof(SystemLog_WithException_AndScope), p.RpcLog.Exception.StackTrace);
});
Assert.Collection(msgs, expected.ToArray());
}
}
}

Просмотреть файл

@ -6,6 +6,7 @@
<AssemblyName>Microsoft.Azure.Functions.Worker.Tests</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.Tests</RootNamespace>
<SignAssembly>true</SignAssembly>
<LangVersion>preview</LangVersion>
<AssemblyOriginatorKeyFile>..\..\key.snk</AssemblyOriginatorKeyFile>
<Nullable>disable</Nullable>
</PropertyGroup>
@ -13,7 +14,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Core.NewtonsoftJson" Version="1.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
@ -24,11 +25,8 @@
<ItemGroup>
<ProjectReference Include="..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker\DotNetWorker.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Configuration\" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,99 @@
using System.Linq;
using Microsoft.ApplicationInsights.DependencyCollector;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector;
using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse;
using Microsoft.ApplicationInsights.WindowsServer;
using Microsoft.ApplicationInsights.WorkerService;
using Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests;
public class ApplicationInsightsConfigurationTests
{
[Fact]
public void AddApplicationInsights_AddsDefaults()
{
var builder = new TestAppBuilder().AddApplicationInsights();
// Ensure that our Initializer and Module are added alongside the defaults
var initializers = builder.Services.Where(s => s.ServiceType == typeof(ITelemetryInitializer));
Assert.Collection(initializers,
t => Assert.Equal(typeof(FunctionsTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers.DomainNameRoleInstanceTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(HttpDependenciesParsingTelemetryInitializer), t.ImplementationType),
t => Assert.Equal(typeof(ComponentVersionTelemetryInitializer), t.ImplementationType));
var modules = builder.Services.Where(s => s.ServiceType == typeof(ITelemetryModule));
Assert.Collection(modules,
t => Assert.Equal(typeof(FunctionsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DiagnosticsTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AppServicesHeartbeatTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(AzureInstanceMetadataTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(PerformanceCollectorModule), t.ImplementationType),
t => Assert.Equal(typeof(QuickPulseTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(DependencyTrackingTelemetryModule), t.ImplementationType),
t => Assert.Equal(typeof(EventCounterCollectionModule), t.ImplementationType));
}
[Fact]
public void AddApplicationInsights_CallsConfigure()
{
bool called = false;
var builder = new TestAppBuilder().AddApplicationInsights(o =>
{
Assert.NotNull(o);
called = true;
});
Assert.False(called);
var provider = builder.Services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<ApplicationInsightsServiceOptions>>();
Assert.NotNull(options.Value);
Assert.True(called);
}
[Fact]
public void AddApplicationInsightsLogger_AddsDefaults()
{
var builder = new TestAppBuilder().AddApplicationInsightsLogger();
var loggerProviders = builder.Services.Where(s => s.ServiceType == typeof(ILoggerProvider));
Assert.Collection(loggerProviders,
t => Assert.Equal(typeof(ApplicationInsightsLoggerProvider), t.ImplementationType));
var serviceProvider = builder.Services.BuildServiceProvider();
var workerOptions = serviceProvider.GetRequiredService<IOptions<WorkerOptions>>();
Assert.True(workerOptions.Value.DisableHostLogger);
}
[Fact]
public void AddApplicationInsightsLogger_CallsConfigure()
{
bool called = false;
var builder = new TestAppBuilder().AddApplicationInsightsLogger(o =>
{
Assert.NotNull(o);
called = true;
});
Assert.False(called);
var provider = builder.Services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<ApplicationInsightsLoggerOptions>>();
Assert.NotNull(options.Value);
Assert.True(called);
}
}

Просмотреть файл

@ -0,0 +1,102 @@
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.Azure.Functions.Worker.Context.Features;
using Microsoft.Azure.Functions.Worker.Diagnostics;
using Microsoft.Azure.Functions.Worker.Tests;
using Microsoft.Azure.Functions.Worker.Tests.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests;
public class EndToEndTests
{
private readonly TestTelemetryChannel _channel;
private readonly IHost _host;
private readonly IFunctionsApplication _application;
private readonly IInvocationFeaturesFactory _invocationFeatures;
public EndToEndTests()
{
_channel = new TestTelemetryChannel();
_host = new HostBuilder()
.ConfigureServices(services =>
{
var functionsBuilder = services.AddFunctionsWorkerCore();
functionsBuilder
.AddApplicationInsights(appInsightsOptions => appInsightsOptions.InstrumentationKey = "abc")
.AddApplicationInsightsLogger();
functionsBuilder.UseDefaultWorkerMiddleware();
services.AddDefaultInputConvertersToWorkerOptions();
// Register our own in-memory channel
services.AddSingleton<ITelemetryChannel>(_channel);
services.AddSingleton(_ => new Mock<IWorkerDiagnostics>().Object);
})
.Build();
_application = _host.Services.GetService<IFunctionsApplication>();
_invocationFeatures = _host.Services.GetService<IInvocationFeaturesFactory>();
}
[Fact]
public async Task DoIt()
{
var def = new AppInsightsFunctionDefinition();
_application.LoadFunction(def);
var invocation = new TestFunctionInvocation(functionId: def.Id);
var features = _invocationFeatures.Create();
features.Set<FunctionInvocation>(invocation);
var inputConversionProvider = _host.Services.GetRequiredService<IInputConversionFeatureProvider>();
inputConversionProvider.TryCreate(typeof(DefaultInputConversionFeature), out var inputConversion);
features.Set<IFunctionBindingsFeature>(new TestFunctionBindingsFeature());
features.Set<IInputConversionFeature>(inputConversion);
var context = _application.CreateContext(features);
await _application.InvokeFunctionAsync(context);
}
internal class AppInsightsFunctionDefinition : FunctionDefinition
{
public static readonly string DefaultPathToAssembly = typeof(AppInsightsFunctionDefinition).Assembly.Location;
public static readonly string DefaultEntryPoint = $"{typeof(AppInsightsFunctionDefinition).FullName}.{nameof(TestFunction)}";
public static readonly string DefaultId = "TestId";
public static readonly string DefaultName = "TestName";
public AppInsightsFunctionDefinition()
{
Parameters = (new[] { new FunctionParameter("context", typeof(FunctionContext)) }).ToImmutableArray();
}
public override ImmutableArray<FunctionParameter> Parameters { get; }
public override string PathToAssembly { get; } = DefaultPathToAssembly;
public override string EntryPoint { get; } = DefaultEntryPoint;
public override string Id { get; } = DefaultId;
public override string Name { get; } = DefaultName;
public override IImmutableDictionary<string, BindingMetadata> InputBindings { get; } = ImmutableDictionary<string, BindingMetadata>.Empty;
public override IImmutableDictionary<string, BindingMetadata> OutputBindings { get; } = ImmutableDictionary<string, BindingMetadata>.Empty;
public void TestFunction(FunctionContext context)
{
var logger = context.GetLogger("TestFunction");
logger.LogWarning("Test");
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
using System.Diagnostics;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Xunit;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests;
public class FunctionsTelemetryInitializerTests
{
[Fact]
public void Initialize_SetsContextProperties()
{
var initializer = new FunctionsTelemetryInitializer("testversion", "testrolename");
var telemetry = new TraceTelemetry();
initializer.Initialize(telemetry);
Assert.Equal("testversion", telemetry.Context.GetInternalContext().SdkVersion);
Assert.Equal("testrolename", telemetry.Context.Cloud.RoleInstance);
}
[Fact]
public void Initialize_SetsProperties_WithActivityTags()
{
var activity = new Activity("operation");
var telemetry = new TraceTelemetry();
try
{
activity.Start();
activity.AddTag("Name", "MyFunction");
activity.AddTag("CustomKey", "CustomValue");
var initializer = new FunctionsTelemetryInitializer("testversion", "testrolename");
initializer.Initialize(telemetry);
}
finally
{
activity.Stop();
}
Assert.Equal("MyFunction", telemetry.Context.Operation.Name);
Assert.Equal("CustomValue", telemetry.Properties["CustomKey"]);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests;
internal class TestAppBuilder : IFunctionsWorkerApplicationBuilder
{
public IServiceCollection Services { get; } = new ServiceCollection();
public IFunctionsWorkerApplicationBuilder Use(Func<FunctionExecutionDelegate, FunctionExecutionDelegate> middleware)
{
throw new NotImplementedException();
}
}

Просмотреть файл

@ -0,0 +1,29 @@
//// Copyright (c) .NET Foundation. All rights reserved.
//// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using Microsoft.ApplicationInsights.Channel;
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests;
internal class TestTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> Telemetries { get; private set; } = new ConcurrentBag<ITelemetry>();
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public void Dispose()
{
}
public void Flush()
{
}
public void Send(ITelemetry item)
{
Telemetries.Add(item);
}
}

Просмотреть файл

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<AssemblyName>Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests</RootNamespace>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\key.snk</AssemblyOriginatorKeyFile>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker\DotNetWorker.csproj" />
<ProjectReference Include="..\DotNetWorkerTests\DotNetWorkerTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>