I have developed a .NET 8 Function App using the isolated worker model. Following the guidelines provided in the Microsoft documentation : https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto?tabs=app-insights&pivots=programming-language-csharp, I have integrated OpenTelemetry with Azure Functions.
Below are the details of my implementation:
host.json:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 20,
"excludedTypes": "Request;Exception"
},
"enableLiveMetrics": true
},
"logLevel": {
"default": "Information",
"Function": "Information",
"Host.Aggregator": "Trace",
"Host.Results": "Information"
}
},
"telemetryMode": "openTelemetry",
"retry": {
"strategy": "fixedDelay",
"maxRetryCount": 1,
"delayInterval": "00:00:04"
},
"extensions": {
"queues": {
"maxPollingInterval": "00:00:00.200",
"visibilityTimeout": "00:00:30",
"batchSize": 16,
"maxDequeueCount": 200,
"newBatchThreshold": 8
}
}
}
Project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="local.settings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Extensions" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.OpenTelemetry" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
<PackageReference Include="Microsoft.Data.SqlClient" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>
</Project>
private static void ConfigureOpenTelemetry(IServiceCollection services, IConfiguration config, string appName)
{
// Binds configuration to AppCloudServiceOptions and validates it
// Returns the validated configuration object
var appConfig = services.BindValidateReturn<AppCloudServiceOptions>(config);
// Configures logging services with OpenTelemetry
services.AddLogging(loggingBuilder =>
{
// Removes any existing logging providers
loggingBuilder.ClearProviders();
// Adds OpenTelemetry logging provider with specific configuration
loggingBuilder.AddOpenTelemetry(logging =>
{
// Enables including formatted log messages in telemetry
logging.IncludeFormattedMessage = true;
// Enables logging scopes to maintain context across log entries
logging.IncludeScopes = true;
});
});
// Adds OpenTelemetry services and configures Azure Monitor as the telemetry backend
services.AddOpenTelemetry().UseFunctionsWorkerDefaults().UseAzureMonitor();
// Configures OpenTelemetry with Application Insights connection string and application name
// This enables sending telemetry data to Azure Application Insights
services.AddOpenTelemetry(appConfig.AppInsightsConnString, appName);
}
internal static OpenTelemetryBuilder AddOpenTelemetry(this IServiceCollection services, string appInsightsConnString, string appName) =>
services.AddOpenTelemetry().UseAzureMonitor(options =>
{
options.ConnectionString = appInsightsConnString;
})
.ConfigureResource(resource => resource.AddService(appName))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddRedisInstrumentation()
.AddAzureMonitorTraceExporter(options => options.ConnectionString = appInsightsConnString))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddAzureMonitorMetricExporter(options => options.ConnectionString = appInsightsConnString));
I deployed the above application in one DEV environment and did some validation. On validation I found that no requests are getting logged for the functionapp case. I only see entries of itemType: trace, dependency, exception. Also all the trace, dependency, exception does not have operation_Name column. On the other hand when I try to compare these with API, I see entries of itemType : request, trace, dependency, exception including the operation_Name column.
Could anyone please help me understand the reasons for the issues I am encountering with this setup?
I am able to send traces to Application insights using below code:
host.json:
{
"version": "2.0",
"telemetryMode": "openTelemetry",
"logging": {
"applicationInsights": {
"enableLiveMetrics": true,
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 20,
"excludedTypes": "Request;Exception"
}
},
"logLevel": {
"default": "Information",
"Function": "Information",
"Host.Aggregator": "Trace",
"Host.Results": "Information"
}
}
}
local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"TestRith__testconstring": "InstrumentationKey=rithwik;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=cfrithwik35"
}
}
Program.cs:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using Azure.Monitor.OpenTelemetry.Exporter;
using FunctionApp12;
var ri_hst = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureAppConfiguration(config =>
{
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
var config = context.Configuration;
var rithConf = new RithServiceCldOps();
config.Bind("TestRith", rithConf);
string riappnme = "RithwikFunctionApp";
services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(riappnme))
.WithTracing(b =>
{
b.AddHttpClientInstrumentation()
.AddAzureMonitorTraceExporter(o =>
{
o.ConnectionString = rithConf.testconstring;
});
})
.WithMetrics(b =>
{
b.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddAzureMonitorMetricExporter(o =>
{
o.ConnectionString = rithConf.testconstring;
});
});
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddOpenTelemetry(tstlgr =>
{
tstlgr.IncludeFormattedMessage = true;
tstlgr.IncludeScopes = true;
tstlgr.ParseStateValues = true;
tstlgr.AddAzureMonitorLogExporter(o =>
{
o.ConnectionString = rithConf.testconstring;
});
});
});
})
.Build();
ri_hst.Run();
namespace FunctionApp12
{
public class RithServiceCldOps
{
public string testconstring { get; set; } = string.Empty;
}
}
Function.cs:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
namespace FunctionApp12
{
public class Function1
{
private readonly ILogger<Function1> rilg;
public Function1(ILogger<Function1> lg)
{
rilg = lg;
}
[Function("Function1")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
{
rilg.LogInformation("Rithwik Test info");
rilg.LogError("Rithwik Test error");
rilg.LogCritical("Rithwik Test critical");
rilg.LogWarning("Rithwik Test warning");
return new OkObjectResult("Test Rithwik!");
}
}
}
Output:
Application Insights Logs:
Here, I have used Http trigger, you can use your trigger.
Edit:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using Azure.Monitor.OpenTelemetry.Exporter;
using FunctionApp12;
var chohost = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureAppConfiguration(config =>
{
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
var config = context.Configuration;
var chcnfg = new RithServiceCldOps();
config.Bind("TestRith", chcnfg);
string riappnme = "TestRIthwik";
services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(riappnme))
.WithTracing(b =>
{
b.AddSource("Microsoft.Azure.Functions.Worker")
.AddHttpClientInstrumentation()
.AddAzureMonitorTraceExporter(o =>
{
o.ConnectionString = chcnfg.testconstring;
});
})
.WithMetrics(b =>
{
b.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddAzureMonitorMetricExporter(o =>
{
o.ConnectionString = chcnfg.testconstring;
});
});
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddOpenTelemetry(rilgr =>
{
rilgr.IncludeFormattedMessage = true;
rilgr.IncludeScopes = true;
rilgr.ParseStateValues = true;
rilgr.AddAzureMonitorLogExporter(o =>
{
o.ConnectionString = chcnfg.testconstring;
});
});
});
})
.Build();
chohost.Run();
namespace FunctionApp12
{
public class RithServiceCldOps
{
public string testconstring { get; set; } = string.Empty;
}
}
Output: