adding AI project (#944)
This commit is contained in:
Родитель
4aaf5cbed4
Коммит
7b42c24f16
|
@ -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>
|
Загрузка…
Ссылка в новой задаче