I am using Asp.NET Core 5 (v5.0.6) web API deployed as an Azure App Service and I am migrating from using appsettings.json in the API to integrating with Azure App Configuration service. I have the Azure App Configuration Service set up but the issue I am having is how to access values in my Azure App Configuration service for retrieving a connection string for database access while I am still in Program.cs.
Note that in the CreateHostBuilder() method I am examining the context.HostingEnvironment.IsDevelopment() environment variable and if it is "IsDevelopment", implying a local DEV, I am reading an Azure App Configuration Service connection string via User Secrets but if it is not a local DEV, than I rely on the Managed Identity and just pass in the endpoint value from appsettings.json.
The only values I want to get that are not in Azure App Configuration Service is the local DEV Azure App Configuration Service Connection string (from User Secrets) and the Azure App Configuration Service endpoint from Appsettings.json. All other settings should come from Azure App Configuration Service.
The problem I am trying to solve is how to access the values in Azure App Configuration Service, while still in Program.cs, to retrieve the connection string for access to the Azure SQL database I am using for logging.
In the code below, when I link the Azure App Configuration in the CreateHostBuilderMethod and call build, I expected the values in Azure App Configuration Service to then be available via the static Configuration property. However when I try to retrieve the connection string value, it is always null.
How can I correctly retrieve the values for properties in Azure App Configuration Service to use them in Program.cs?
Here is my Program.cs file;
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddUserSecrets<Startup>()
.Build();
public static void Main(string[] args)
{
var host = CreateHostBuilder(args)
.Build();
var connectionString = Configuration["CoreApi:Settings:LoggingDb"]; //<-- This returns null
const string tableName = "ApiLogs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore.Query"))
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: new MSSqlServerSinkOptions { TableName = tableName })
.CreateLogger();
// TODO Enable to debug any startup Serilog issues. Make sure to comment out for PROD
// Serilog.Debugging.SelfLog.Enable(msg =>
// {
// Debug.Print(msg);
// Debugger.Break();
// });
//var host = CreateHostBuilder(args)
// .Build();
try
{
Log.Information("Starting ConfirmDelivery API");
host.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "ConfirmDelivery API Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var settings = config.Build();
if (context.HostingEnvironment.IsDevelopment())
{
var connectionString = settings.GetConnectionString("AzureAppConfiguration");
config.AddAzureAppConfiguration(connectionString);
}
else
{
var endpoint = settings["AppConfigEndpoint"];
var credentials = new ManagedIdentityCredential();
config.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri(endpoint), credentials);
});
}
}).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Try the code below to get a config value:
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
namespace myapp
{
class Program
{
static void Main(string[] args)
{
var configItemName = "";
var appConfigConnectionString = "";
var builder = new ConfigurationBuilder();
builder.AddAzureAppConfiguration(appConfigConnectionString);
var config = builder.Build();
Console.WriteLine(config[configItemName]);
}
}
}
Result :
Related
We are trying to scale out our calendar application, that uses SignalR to push updates to clients based on their user's OrganizationId. Previously, the SignalR stuff was hosted within the single App Server, but to make it work across multiple servers we have opted to use Azure SignalR Services.
However, when the application uses the Azure solution, autorisation breaks.
Authentication is set up in Startup.cs to look for the token in the url/query-string when dealing with Hub endpoints:
//From: Startup.cs (abridged)
public IServiceProvider ConfigureServices(IServiceCollection services)
var authenticationBuilder = services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = OAuthValidationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OAuthValidationDefaults.AuthenticationScheme;
});
authenticationBuilder
.AddOAuthValidation(options => {
options.Events.OnRetrieveToken = async context => {
// Based on https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.0
var accessToken = context.HttpContext.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/signalr/calendar")) {
context.Token = accessToken;
}
return;
};
})
.AddOpenIdConnectServer(options => {
options.TokenEndpointPath = "/token";
options.ProviderType = typeof(ApplicationOAuthProvider);
/*...*/
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) {
app.UseAuthentication();
}
When using the Azure SignalR Service, the OnRetrieveToken event code is simply never hit, which makes sense given that the request is no longer directed at the App Service, but instead to the url of the Azure SignalR Service.
This Hub works while SignalR is hosted on the App Server:
[Authorize(Roles = "Manager, Seller")]
public class CalendarHub : Hub<ICalendarClient> {
private IHttpContextAccessor httpContextAccessor;
public CalendarHub(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor }
public override async Task OnConnectedAsync() {
await Groups.AddToGroupAsync(Context.ConnectionId, GetClaimValue("OrganizationId"));
await base.OnConnectedAsync();
}
private string GetClaimValue(string claimType) {
var identity = (ClaimsIdentity)httpContextAccessor?.HttpContext?.User.Identity;
var claim = identity?.FindFirst(c => c.Type == claimType);
if (claim == null)
throw new InvalidOperationException($"No claim of type {claimType} found.");
return claim.Value;
}
}
But when I switch to the Azure solution:
//From: Startup.cs (abridged)
public IServiceProvider ConfigureServices(IServiceCollection services)
services.AddSignalR().AddAzureSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) {
app.UseAzureSignalR(routes => routes.MapHub<CalendarHub>("/signalr/calendar"));
}
...connecting to the hub causes exception No claim of type OrganizationId found. because the identity is completely empty, as if no user was authenticated. This is especially strange, given that I've restricted access to users of specific roles.
Turns out the error is the same as this question where HttpContext is used to get the claim values, because that's what we do everywhere else. And this seems to work as long as it is the App Service itself handling the connection to the client.
But Azure SignalR Service supplies the claims somewhere else:
The correct way is using just Context which has the type HubCallerContext when accessed from a SignalR Hub. All the claims are available from here with no extra work.
So the method for getting the claim becomes
private string GetClaimValue(string claimType) {
var identity = Context.User.Identity;
var claim = identity.FindFirst(c => c.Type == claimType);
if (claim == null)
throw new InvalidOperationException($"No claim of type {claimType} found.");
return claim.Value;
}
I have configured my API to get the Azure Storage connection string from Azure KeyVault using Managed Identity.
Now the problem is that when I run the code locally in Visual Studio, it no longer uses the connection string from the appsettings.development.json which was "StorageAccount": "UseDevelopmentStorage=true"
Therefore I can't use the emulator when running locally.
I have created the following condition in my controller to work around this issue :
public FileController(IConfiguration configuration, IWebHostEnvironment env)
{
this.configuration = configuration;
if (env.IsDevelopment())
{
conn = this.configuration.GetConnectionString("StorageAccount");
}
else
{
conn = this.configuration["StorageConnectionString"];
}
}
Is this the proper way to do it ?
In local, if your ASPNETCORE_ENVIRONMENT set to Development, then it will read you local storage account like UseDevelopmentStorage=true.
When you publish to azure, it will use your webapp's MSI to get the connectionstring from key vault.
For more details, you could refer to the following code:
private IConfiguration _configuration;
private IWebHostEnvironment _env;
public WeatherForecastController(IConfiguration configuration, IWebHostEnvironment env)
{
_configuration = configuration;
_env = env;
}
if (_env.IsDevelopment())
{
con = _configuration.GetSection("StorageAccount").Value;
}
else
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
con = keyVaultClient.GetSecretAsync("https://xxxx.vault.azure.net/secrets/xxxx").GetAwaiter().GetResult().Value;
}
I have found out it is easy to connect to Azure KeyVault using Managed Identity. The documentation shows how to do it :
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
Then I realized it requires the package Microsoft.Azure.KeyVault which is deprecated. So I'm struggling to figure out how to do the above with SDK 4. All the documentation I find is related to SDK 3.
[EDIT]
I have found out the following code works to get the azure KeyVault Secret using Managed Identiy with SDK 4. However I can't see how to add this to my configuration. It used to be done with config.AddAzureKeyVault() from the Microsoft.Extensions.Configuration.AzureKeyVault Package however it is not compatible with the SDK 4 SecretClient:
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var azureCredentialOptions = new DefaultAzureCredentialOptions();
var credential = new DefaultAzureCredential(azureCredentialOptions);
var secretClient = new SecretClient(new System.Uri("https://mykeyvault.vault.azure.net/"), credential);
var secret = secretClient.GetSecret("StorageConnectionString");
config.AddAzureKeyVault()
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
As it turned out, I found the proper way to do it with SDK 4. I had to install the package azure.extensions.aspnetcore.configuration.secrets and then the code is simply :
var credential = new DefaultAzureCredential();
config.AddAzureKeyVault(new System.Uri("https://mykv.vault.azure.net/"), credential);
then to use it
configuration["StorageConnectionString"]
AS per June 2020
First thing is that Microsoft.Azure.KeyVault is not deprecated but replaced. Using the old nuget package is still a valid option.
I imagine in the future, the Microsoft.Extensions.Configuration.AzureKeyVault nuget package will use the new Azure.Security.KeyVault.Secrets package.
In my experience I would stick with the existing library and wait for future updates.
If you really want to use the Azure.Security.KeyVault.Secrets, you can implement your own custom configuration builder.
I had a look at the existing key vault configuration code on github and here is a simplified/modified version that you could use.
First install these nuget packages Azure.Identity and Azure.Security.KeyVault.Secrets.
The new key vault secrets package uses IAsyncEnumerable so you need to update your project to target C#8.0: update you csproj file with <LangVersion>8.0</LangVersion>.
Azure Key Vault Secret configuration code:
public interface IKeyVaultSecretManager
{
bool ShouldLoad(SecretProperties secret);
string GetKey(KeyVaultSecret secret);
}
public class DefaultKeyVaultSecretManager : IKeyVaultSecretManager
{
public bool ShouldLoad(SecretProperties secret) => true;
public string GetKey(KeyVaultSecret secret)
=> secret.Name.Replace("--", ConfigurationPath.KeyDelimiter);
}
public class AzureKeyVaultConfigurationProvider : ConfigurationProvider
{
private readonly SecretClient _client;
private readonly IKeyVaultSecretManager _manager;
public AzureKeyVaultConfigurationProvider(SecretClient client, IKeyVaultSecretManager manager)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
}
public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
private async Task LoadAsync()
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
await foreach (var secretProperties in _client.GetPropertiesOfSecretsAsync())
{
if (!_manager.ShouldLoad(secretProperties) || secretProperties?.Enabled != true)
continue;
var secret = await _client.GetSecretAsync(secretProperties.Name).ConfigureAwait(false);
var key = _manager.GetKey(secret.Value);
Data.Add(key, secret.Value.Value);
}
Data = data;
}
}
public class AzureKeyVaultConfigurationSource : IConfigurationSource
{
public SecretClient Client { get; set; }
public IKeyVaultSecretManager Manager { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new AzureKeyVaultConfigurationProvider(Client, Manager);
}
}
public static class AzureKeyVaultConfigurationExtensions
{
public static IConfigurationBuilder AddAzureKeyVault(
this IConfigurationBuilder configurationBuilder,
SecretClient client,
IKeyVaultSecretManager manager = null)
{
if (configurationBuilder == null)
throw new ArgumentNullException(nameof(configurationBuilder));
if (client == null)
throw new ArgumentNullException(nameof(client));
configurationBuilder.Add(new AzureKeyVaultConfigurationSource()
{
Client = client,
Manager = manager ?? new DefaultKeyVaultSecretManager()
});
return configurationBuilder;
}
}
You can now use this configuration builder in your project like that:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var azureCredentialOptions = new DefaultAzureCredentialOptions();
var credential = new DefaultAzureCredential(azureCredentialOptions);
var secretClient = new SecretClient(new System.Uri("https://mykeyvault.vault.azure.net/"), credential);
config.AddAzureKeyVault(secretClient);
})
.UseStartup<Startup>();
}
For your info, if you're using Azure Key Vault secrets in your App Service or Azure Functions application settings, you don't have to add extra code to get the key vault value.
You just need to change your app settings values (in azure portal), with your key vault references.
For the steps, look at https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
Setting example using key vault references:
{
"name": "DatabaseSettings:ConnectionString",
"value": "#Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/DatabaseConnectionSettingSecret/ec96f02080254fxxxxxxxxxxxxxxx)",
"slotSetting": false
}
But this doesn't work for a local development, instead you should use plain secret value. But for me it's okay, because your local development uses different secret value and you don't add your local.settings.json to source control.
Latest working solution with version 4 library. My stack is .netcore 3.1 and I am using it inside an Azure Web App to access a secret from Azure KeyVault.
First thing first - go through this MS Doc link
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
//..........
var kvUri = "https://YOURVAULTNAME.vault.azure.net/";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
KeyVaultSecret secret = client.GetSecret("SECRETNAME");
// Can also use await.....GetSecretAsync()
this.ConnectionString = secret.Value.ToString();
\\thats my internal variable, secret.Value.ToString() is required value
I assume here that you have
Created keyVault in Azure
created a Managed identity in App Service(Using 'Identity' option in app service)
Added above identity in Azure KeyVault('Access policies' option)
I am using something like this,
var keyVaultEndpoint = GetKeyVaultEndpoint();
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
// Pass appropriate connection string
var azureServiceTokenProvider = new
AzureServiceTokenProvider(certThumbprintConnectionString);
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
}
private static string GetKeyVaultEndpoint() => "https://<<key-vault-name>>.vault.azure.net";
I have an ASP.Net Core 2.1 Website that uses Azure SQL Database for the Microsoft Identity component.
I added a Logs table to that database and added Serilog to my website with the SQL Server Sink.
When I run the website locally, while still connected to the Azure SQL database, I can see my log entries in the Logs table just fine. However, when I deploy the website to my Azure App Service, I no longer get any log entries in the Logs table of the database.
Mind you, in the deployed version, I am connecting to and using the Azure SQL database for my MS Identity stuff just fine and I can create new users and edit existing users just fine. So I know the Connection String in my App Service Application Settings is correct.
I have reviewed the Serilog MSSQL Github to compare their configuration recommendations to my own and could not find anything that stood out.
I have this setup working correctly on an ASP.Net Core API that I deploy to another Azure App Service. That service uses a different database but it is on the same SQL Server resource.
I have reviewed the list of SO posts recommended when I started this question with no luck.
I ran the following SQL on the database when I first set up the user account;
EXEC sp_addrolemember N'db_datareader', N'myuser'
EXEC sp_addrolemember N'db_datawriter', N'myuser'
EXEC sp_addrolemember N'db_ddladmin', N'myuser'
And, as I stated, the user account can update and add user data in the AspNetUsers table just fine. So, it doesn't seem like a user account issue.
I have verified that the connection string in my Azure app service DEV deployment slot (the one I am testing), Application Settings, Connection Strings is the exact same as what I have in my local DEV UserSecrets. Plus, again, I can read/write to the AspNet* tables in that same database when deployed to Azure.
Here is my Program.cs class where I set up Serilog;
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddUserSecrets<Startup>()
.AddEnvironmentVariables()
.Build();
public static void Main(string[] args)
{
var connectionString = Configuration.GetConnectionString("MyConnectionString");
const string tableName = "Logs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.MSSqlServer(connectionString, tableName)
.CreateLogger();
// TODO Enable to debug any startup Serilog issues. Make sure to comment out for PROD
//Serilog.Debugging.SelfLog.Enable(msg =>
//{
// Debug.Print(msg);
// Debugger.Break();
//});
try
{
Log.Information("Starting Application");
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog();
}
}
The only difference between the API that I have deployed in Azure that is writing logs to Azure SQL and this website is that in the API, which is older, I have
public static IWebHost BuildWebHost(string[] args)
in program.cs whereas the newer website has
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
So... any ideas would be greatly appreciated.
[UPDATE 1/23/19]
I added the connection string directly to the
var connectionString
in Program.cs rather than getting it from
Configuration.GetConnectionString("MyConnectionString")
and it started logging to the database.
So it seems the issue is with Program.cs being able to read the connection string from Azure App Service deployment slot, Application Settings, Connection Strings section.
This connection string is being correctly read from Startup.cs and has worked since I first created the website.
So, is there some known issue with Azure not being able to read values from deployment slot Application Settings / Connection Strings from Program.cs?
Since there seems to be an issue with Azure, that it doesn't provide the application settings to the web app until CreateWebHostBuilder is invoked, a straightforward workaround (assuming that hardcoding the connection string in the source code isn't a viable option) would be to configure Serilog to use SqlServer in Startup.cs instead of Program.cs.
If it's important to log the initial events that occur during the app's starting up, Serilog can temporarily be configured in Program.cs to write to a file.
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var AzureLogFilePath = #"D:\home\LogFiles\Application\log.txt";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.File(path: AzureLogFilePath, fileSizeLimitBytes: 1_000_000, rollOnFileSizeLimit: true, shared: true);
.CreateLogger();
try
{
Log.Information("Starting Application");
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog();
}
Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName ?? "Production"}.json", optional: true)
.AddEnvironmentVariables();
if (env.IsDevelopment()) builder.AddUserSecrets<Startup>(); // according to the docs, AddUserSecrets should be used only in development
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("MyConnectionString");
const string tableName = "Logs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.MSSqlServer(connectionString, tableName)
.CreateLogger();
//...
}
}
I work on .NET Core 2.2 console application that uses Microsoft.Extensions.Logging and is configured to send logs to Azure Application Insights using Microsoft.ApplicationInsights.Extensibility by:
services.AddSingleton(x =>
new TelemetryClient(
new TelemetryConfiguration
{
InstrumentationKey = "xxxx"
}));
...
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddApplicationInsights(serviceProvider, logLevel);
It works ok: I can read logs in Application Insights. But the application can be started simultanously in few instances (in different Docker containers). How can I distinguish traces from different instances? I can use source FileName, but I don't know how I should inject it.
I tried to use Scope:
var logger = loggerFactory.CreateLogger<Worker>();
logger.BeginScope(dto.FileName);
logger.LogInformation($"Start logging.");
It's interesting that my configuration is almost identical as in example: https://github.com/MicrosoftDocs/azure-docs/issues/12673
But in my case I can't see the property "FileName" in Application Insights.
For console project, if you want to use the custom ITelemetryInitializer, you should use this format: .TelemetryInitializers.Add(new CustomInitializer());
Official doc is here.
I test it at my side, and it works. The role name can be set.
Sample code is below:
static void Main(string[] args)
{
TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault();
configuration.InstrumentationKey = "xxxxx";
configuration.TelemetryInitializers.Add(new CustomInitializer());
var client = new TelemetryClient(configuration);
ServiceCollection services = new ServiceCollection();
services.AddSingleton(x => client);
var provider = services.BuildServiceProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddApplicationInsights(provider, LogLevel.Information);
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("a test message 111...");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
Check the role name in azure portal:
If you really have no way to distinguish them you can use a custom telemetry initializer like this:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = Environment.MachineName;
}
}
and/or you can add a custom property:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if(telemetry is ISupportProperties)
{
((ISupportProperties)telemetry).Properties["MyIdentifier"] = Environment.MachineName;
}
}
}
In this example I used Environment.MachineName but you can of course use something else if needed. Like this work Id parameter of yours.
the wire it up using:
services.AddSingleton<ITelemetryInitializer, CustomInitializer>();