.NET Core 6 - 如何在启动期间的Program.cs中获得ILogger实例而无需依赖注入

33

由于我开始让一些试图帮助我的人感到困惑,因此我已更新了原始问题的内容。

我正在使用库“Microsoft.ApplicationInsights.AspNetCore”将日志发送到Azure。使用此库似乎面临的挑战之一是,在为该库创建服务之前,它不会向Azure发送任何日志事件。

鸡和蛋的情况是,在Net Core 6 Web应用程序的启动过程中的最早阶段(即实际上还没有创建App Insights所需的服务之前),我需要编写日志并将其发送到Azure。

我需要在应用程序启动过程的早期阶段编写日志以捕获用户登录的详细信息,其中微软登录页面将在.Net Core应用程序启动后立即弹出。

在下面的代码示例中,您可以看到我创建了一个记录器工厂实例,以便在剩余的服务构建和启动之前在program.cs文件中本地编写一些日志。虽然使用这种方法可以将日志写入控制台,但不会将任何事件发送到App Insights。我认为这是因为App Insights库要到程序的较晚阶段才被确立,即在program.cs文件中需要的服务被创建之后。

var builder = WebApplication.CreateBuilder(args);

// I create an instance of logger facroty
using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder
    .SetMinimumLevel(LogLevel.Trace)
    .AddConsole()
    .AddApplicationInsights(builder.Configuration["APPINSIGHTS_CONNECTIONSTRING"]));
    
// I use the logger factory to create an instance of Ilogger
ILogger logger = loggerFactory.CreateLogger<Program>();

// This code section here is related to Microsoft Identity Web library and is responsible for
// triggering methods based upon when a user signs into Mirosoft (as well as signing out)
// When them methods are triggered in this service, i need to write logs and send them to Azure.
// The issue is this service runs before Application Insights service has been created/started, see code section below...
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    // The claim in the Jwt token where App roles are available.
    options.TokenValidationParameters.RoleClaimType = "roles";
    // Advanced config - capturing user events. See OpenIdEvents class.
    options.Events ??= new OpenIdConnectEvents();
    options.Events.OnTokenValidated += openIdEvents.OnTokenValidatedFunc;
    // This is event is fired when the user is redirected to the MS Signout Page (before they've physically signed out)
    options.Events.OnRedirectToIdentityProviderForSignOut += openIdEvents.OnRedirectToIdentityProviderForSignOutFunc;
    // DO NOT DELETE - May use in the future.
    // OnSignedOutCallbackRedirect doesn't produce any user claims to read from for the user after they have signed out.
    options.Events.OnSignedOutCallbackRedirect += openIdEvents.OnSignedOutCallbackRedirectFunc;
});

// --- IMPORTANT NOTE -----
This log event is succesfully written to the console, BUT it does not get sent to Azure App Insights.
// --------------------------------------------------------------------------------------
The methods triggered in the code section above by Microsoft Identity Web are actually stored in a seperate class,
// however being unbale to write a test log message here means that it wont work in a seperate class either.
logger.LogInformation("This is test message");


// ----- Other general servics being created required for my app -----
// Add the AuthorizationPolicies for the AppRoles
builder.Services.AddAuthorizationClaimPolicies();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy.
    options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();
    
// HERE IS THE PART WHERE APPLICATION INSIGHTS SERVICE IS CREATED, 
// SO HAVING CREATED AN INSTANCE OF ILOGGER FACTORY BEFORE THIS STEP DOES NOT WORK
// ----- Configure Application Insights Telemetry -----
Microsoft.ApplicationInsights.AspNetCore.Extensions.ApplicationInsightsServiceOptions aiOptions = new();
aiOptions.ConnectionString = builder.Configuration["APPINSIGHTS_CONNECTIONSTRING"];
aiOptions.EnablePerformanceCounterCollectionModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnablePerformanceCounterCollectionModule");
aiOptions.EnableRequestTrackingTelemetryModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableRequestTrackingTelemetryModule");
aiOptions.EnableEventCounterCollectionModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableEventCounterCollectionModule");
aiOptions.EnableDependencyTrackingTelemetryModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableDependencyTrackingTelemetryModule");
aiOptions.EnableAppServicesHeartbeatTelemetryModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAppServicesHeartbeatTelemetryModule");
aiOptions.EnableAzureInstanceMetadataTelemetryModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAzureInstanceMetadataTelemetryModule");
aiOptions.EnableQuickPulseMetricStream = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableQuickPulseMetricStream");
aiOptions.EnableAdaptiveSampling = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAdaptiveSampling");
aiOptions.EnableHeartbeat = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableHeartbeat");
aiOptions.AddAutoCollectedMetricExtractor = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("AddAutoCollectedMetricExtractor");
aiOptions.RequestCollectionOptions.TrackExceptions = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("RequestCollectionOptions.TrackExceptions");
aiOptions.EnableDiagnosticsTelemetryModule = builder.Configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableDiagnosticsTelemetryModule");
// Build the serive with the options from above.
builder.Services.AddApplicationInsightsTelemetry(aiOptions);

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
//app.MapControllers(); // Default mapping, not in use, see below...
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

OpenIdEvents - 这是什么?这是你自定义的类吗? - Serge
是的,基本上我正在尝试将ILogger实例传递给这个类,但问题是该类中的方法在应用程序启动过程的最开始被调用,因此我不能依赖于依赖注入来获取ILogger的实例。我已经遵循Guru下面的建议,并尝试将在Progam.cs中创建的ILogger实例传递给这个单独的类,并且能够记录到控制台,但是当我在他的方法中添加“.AddApplicationInsights();”时,我仍然没有任何日志记录到应用程序洞察中。不幸的是,我认为现在这个问题有点偏离了。 - OJB1
为什么你不能在构造函数中手动创建Logger呢? - Serge
你是从哪里“发送日志”的?包含你的代码的进程在哪里运行?我猜它是托管在Azure App Svc上的? - Chef Gladiator
3个回答

32

这段代码使用了 net 6 进行测试

var builder = WebApplication.CreateBuilder(args);

using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder
    .SetMinimumLevel(LogLevel.Trace)
    .AddConsole());

ILogger<LogTest> logger = loggerFactory.CreateLogger<LogTest>();
var test = new LogTest(logger);

或者你可以采用这段代码

var builder = WebApplication.CreateBuilder(args);

builder.Logging.ClearProviders();
 builder.Logging.AddConsole();
....

var app = builder.Build();

ILogger <LogTest> logger = app.Services.GetRequiredService<ILogger<LogTest>>();
var test = new LogTest(logger);

测试类

public class LogTest
{
    private readonly ILogger<LogTest>  _logger;

    public LogTest(ILogger<LogTest> logger)
    {
        _logger = logger;  
        var message = $" LogTest logger created at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(message);
    }
}

你好,非常感谢您的帮助。将记录器实例传递给单独的类确实可以正常工作,以便写入控制台。但我仍然无法写入应用程序洞察。我在自定义类中调用的方法必须在“builder.Services.AddApplicationInsightsTelemetry”创建服务之前调用,所以这是一个先有鸡还是先有蛋的问题。非常抱歉,我在处理这个问题的方式上真的搞砸了。 - OJB1
我正在使用内置的MS Logger和Application Insights库。挑战在于App Insights库只有在创建服务后才能正常工作。我需要记录一些方法的事件,这些方法在任何服务被创建之前就已经运行了,即在应用程序启动过程的最开始阶段。因此,尽管ILogger实例可以在早期阶段传递,但似乎App Insights连接器直到服务被创建才会真正起作用。 - OJB1
1
所以我必须问一下,如果您可以在需要的时候添加自己的记录器,那么您不是可以将需要的任何数据存储在数组中,直到完成App Insights连接,然后将它们转储到记录器中并停止在自己的小记录器中处理更多数据吗? - Blindy
在管道的这个阶段中,没有办法获取由依赖注入使用配置创建的 ILogger 吗? - jjxtra

8
你可以手动创建 LoggerFactoryLogger:
using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder
    .SetMinimumLevel(LogLevel.Trace)
    .AddConsole());

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Example log message");

更新

您需要复制您的“普通”设置,这对您有用。您可以尝试以下内容:

var configuration = new ConfigurationBuilder() // add all configuration sources used
    .AddJsonFile("appsettings.json", false, true)
    .AddEnvironmentVariables()
    .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", true, true)
    .Build();
using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder
    .AddConfiguration(configuration)
    .AddApplicationInsights());
// create logger and use it ....

你好,这个方法也适用于应用程序洞察吗?即如何在不仅仅记录到控制台的情况下完成此操作?谢谢。 - OJB1
这个 ILogger 实例在 Program.cs 文件中可以工作,但是当将它传递给从 Program.cs 调用的分离的类(OpenIdEvents)的构造函数时,似乎没有任何作用,但是没有看到编译错误。 - OJB1
这是一个类,其中包含从Program.cs文件调用的方法。当用户登录/退出应用程序时,这些方法会触发。我需要在这些方法中编写日志,但我无法获得日志记录器的实例来工作。我尝试了您的建议,并且对于记录到控制台,这很有效,但我需要记录到Azure Application Insights。我尝试简单地将“.AddApplicationInsights()”添加到您的loggerfactory方法中,并将其传递给单独类的Ctor,但没有日志发送到App Insights,因此我错过了其他一些东西,我无法理解。 - OJB1
@OJB1,你需要复制你在“普通”ApplicationInsights日志记录中使用的相同设置。请查看更新-也许它会给你一些缺失的想法。 - Guru Stron
更新了我的问题,希望现在更清楚了 :) - OJB1
显示剩余2条评论

6

正如您发现的那样,在 DI 容器构建之前使用由 Azure 应用程序洞察支持的 ILogger 实例并不会将日志发送到 AppInsights。

由于 .NET 6 的新主机生成器确实巩固了与 DI 支持相结合的 Startup 类移动(它实际上需要两个 DI 容器,而且不是很理想,因为有几个原因),所以现在没有太好的选择。从我所见,微软似乎没有打算默认情况下支持启动期间复杂的记录提供者。

话虽如此,您可以尝试手动使用仪表板密钥实例化 TelemetryClient。从那里开始,您可以记录任何想要的内容。像自动添加事件属性之类的花哨操作可能不起作用,因为还没有真正设置任何东西。但是你应该能够向你的 AppInsights 发送数据。这还没有经过测试,但请尝试:

var client = new TelemetryClient(TelemetryConfiguration.CreateDefault());
client.InstrumentationKey = "YOUR_INSTRUMENTATION_KEY";
client.TrackTrace("Severe startup issue.", SeverityLevel.Critical);

嗨,我已经按照你提到的方法在我的program.cs文件中获取了Telemetry Client的实例,这最终使我能够在创建DI服务之前将数据发送到App Insights。在使用正确的服务创建日志记录之前,我只需要用它来完成一些事情。我还成功地序列化了一个属性类,而不是仅使用字符串作为消息,所以这应该可以满足我的需求,谢谢。 - OJB1
最后说明:在创建遥测客户端实例时,我使用了using语句,然后在使用后进行了清除--> 使用以下代码创建遥测配置并实例化遥测客户端:using var telemetryConfiguration = new TelemetryConfiguration("my_instrumentation_key");
telemetryClient = new TelemetryClient(telemetryConfiguration); telemetryClient.TrackTrace(JsonSerializer.Serialize(logEvent), SeverityLevel.Information); telemetryClient.Flush(); (其中logEvent是一系列属性的序列化列表)
- OJB1
@OJB1 是的,我只在非常特定的情况下使用这种技术,并且在任何正常请求上下文中都应该避免使用等等。此外,刷新也是明智的选择。另外,序列化有点棘手,我有一个代码片段,经常用来演示几种方法以及它们在AppIns中的表现,如果有帮助的话,可以在这里找到:https://gist.github.com/NickSpag/036fab2f30563f23083005f8569ea003 - NickSpag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接