ฉันจะเขียนบันทึกจากภายใน Startup.cs ได้อย่างไร


131

ในการดีบักแอพ. NET Core ซึ่งล้มเหลวในการเริ่มต้นฉันต้องการเขียนบันทึกจากภายในไฟล์ startup.cs ฉันมีการตั้งค่าการบันทึกภายในไฟล์ที่สามารถใช้ในส่วนที่เหลือของแอพนอกไฟล์ startup.cs แต่ไม่แน่ใจว่าจะเขียนบันทึกจากภายในไฟล์ startup.cs ได้อย่างไร

คำตอบ:


199

.Net Core 3.1

น่าเสียดายสำหรับ ASP.NET Core 3.0 สถานการณ์แตกต่างไปจากเดิมเล็กน้อย เทมเพลตเริ่มต้นใช้HostBuilder(แทนWebHostBuilder) ซึ่งตั้งค่าโฮสต์ทั่วไปใหม่ที่สามารถโฮสต์แอปพลิเคชันต่างๆได้ไม่ จำกัด เฉพาะเว็บแอปพลิเคชัน ส่วนหนึ่งของโฮสต์ใหม่นี้ยังเป็นการลบคอนเทนเนอร์การฉีดพึ่งพาที่สองซึ่งก่อนหน้านี้มีอยู่สำหรับโฮสต์เว็บ ในที่สุดหมายความว่าคุณจะไม่สามารถแทรกการอ้างอิงใด ๆ นอกเหนือจากIConfigurationในStartupคลาสได้ ดังนั้นคุณจะไม่สามารถเข้าสู่ระบบระหว่างConfigureServicesวิธีการได้ อย่างไรก็ตามคุณสามารถฉีดคนตัดไม้ลงในConfigureวิธีการและเข้าสู่ระบบที่นั่น:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
    logger.LogInformation("Configure called");

    // …
}

หากคุณจำเป็นต้องเข้าสู่ระบบอย่างแท้จริงConfigureServicesคุณสามารถใช้สิ่งนี้ต่อไปWebHostBuilderซึ่งจะสร้างมรดกWebHostที่สามารถฉีดคนตัดไม้ลงในStartupชั้นเรียนได้ โปรดทราบว่ามีแนวโน้มว่าโฮสต์เว็บจะถูกลบออกในอนาคต ConfigureServicesดังนั้นคุณควรพยายามที่จะหาวิธีการแก้ปัญหาที่เหมาะกับคุณโดยไม่ต้องเข้าสู่ระบบภายใน


.NET Core 2.x

สิ่งนี้เปลี่ยนแปลงไปอย่างมากเมื่อมีการเปิดตัว ASP.NET Core 2.0 ใน ASP.NET Core 2.x การบันทึกจะถูกสร้างขึ้นที่ตัวสร้างโฮสต์ ซึ่งหมายความว่าการบันทึกจะพร้อมใช้งานผ่าน DI โดยค่าเริ่มต้นและสามารถแทรกเข้าไปในStartupคลาส

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

    public IConfiguration Configuration { get; }

    public Startup(ILogger<Startup> logger, IConfiguration configuration)
    {
        _logger = logger;
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        _logger.LogInformation("ConfigureServices called");

        // …
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        _logger.LogInformation("Configure called");

        // …
    }
}

4
ขอขอบคุณ. มันน่าทึ่งมากที่คุณสามารถเผาผลาญเวลาหาคำตอบสำหรับคำถามง่ายๆ @poke ขอบคุณ (อีกครั้ง) ที่แจ้งว่าทางเลือกของฉันคืออะไร คุณได้รับข้อมูลนี้มาจากไหน ฉันยืนยันว่าฉันสามารถบันทึกสิ่งต่างๆได้ใน Configure ซึ่งดีกว่าที่จะจิ้ม (เล่นสำนวนเจตนา) ด้วยไม้แหลม แต่อาจจะไม่เจ๋งเท่าความสามารถในระหว่าง ConfigureServices ในกรณีของฉันฉันต้องการบันทึกว่าฉันได้รับการตั้งค่า env หรือไม่บางทีอาจจะโพสต์ลงในบันทึก ไม่มีลูกเต๋า? เฮ้อ ... ไม่แน่ใจว่าทำไมถึงยากขนาดนี้ แต่อย่างน้อยต้องขอบคุณโพสต์นี้ที่ทำให้ฉันรู้ว่าอะไรทำได้และทำไม่ได้
Wellspring

3
@Wellspring ใน 3.0 มัน "ยาก" เพราะเมื่อถึงเวลาที่ConfigureServicesทำงานคนตัดไม้ยังไม่มีอยู่จริง ดังนั้นคุณจะไม่สามารถเข้าสู่ระบบ ณ จุดนั้นได้เพียงเพราะยังไม่มีคนตัดไม้ ในด้านบวกนั้นยังคงช่วยให้คุณสามารถกำหนดค่าคนตัดไม้ภายในConfigureServicesเนื่องจากเป็นคอนเทนเนอร์ DI เดียวกันทั้งหมด (ซึ่งเป็นสิ่งที่ดี) - หากคุณจำเป็นต้องบันทึกสิ่งต่างๆอย่างแน่นอนตัวอย่างเช่นคุณสามารถรวบรวมข้อมูลแยกกัน (เช่นในรายการ) จากนั้นออกจากระบบทันทีที่มีคนตัดไม้
โผล่

ใน.NET 3.1ปัจจุบันคุณสามารถเข้าสู่ระบบภายในConfigureServicesเมธอดได้โดยไม่ต้องถอยกลับไปที่ไฟล์WebHostBuilder. ใช้คำตอบด้านล่าง: stackoverflow.com/a/61488490/2877982
Aage

2
@Aage สิ่งนี้จะมีข้อเสียหลายประการ: คุณจะต้องทำซ้ำการกำหนดค่าการบันทึกทั้งหมดของคุณการกำหนดค่าการบันทึกจะไม่แสดงถึงการกำหนดค่าแอปพลิเคชันของคุณ (เช่นระดับการบันทึกที่กำหนดค่าใน appsettings เป็นต้น) และโดยทั่วไปคุณกำลังตั้งค่าโครงสร้างพื้นฐานการบันทึกที่สอง ฉันยังคงแนะนำให้คุณมองหาวิธีแก้ปัญหาว่าคุณจะหลีกเลี่ยงการบันทึกระหว่างการตั้งค่า DI ได้อย่างไร
โผล่

2
.NET 5 : "ส่งผ่านพารามิเตอร์เพิ่มเติมไปยังStartupที่เริ่มต้นพร้อมกับโฮสต์" builder.UseStartup(context => new Startup(logger));- docs.microsoft.com/en-us/aspnet/core/release-notes/…
Pang

39

ตัวเลือกที่ 1:ใช้บันทึกโดยตรง (เช่น Serilog) ในการเริ่มต้น -

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        Log.Logger = new LoggerConfiguration()
           .MinimumLevel.Debug()
           .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Serilog-{Date}.txt"))
           .CreateLogger();

        Log.Information("Inside Startup ctor");
        ....
    }

    public void ConfigureServices(IServiceCollection services)
    {
        Log.Information("ConfigureServices");
        ....
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        Log.Information("Configure");
        ....
    }

เอาท์พุต:

serilog

การติดตั้ง Serilog ในการประยุกต์ใช้ asp.net-core ตรวจสอบแพคเกจ Serilog.AspNetCore บน GitHub


ทางเลือกที่ 2:กำหนดค่าการเข้าสู่ระบบ program.cs เช่นนี้ -

var host = new WebHostBuilder()
            .UseKestrel()
            .ConfigureServices(s => {
                s.AddSingleton<IFormatter, LowercaseFormatter>();
            })
            .ConfigureLogging(f => f.AddConsole(LogLevel.Debug))
            .UseStartup<Startup>()
            .Build();

host.Run();

ผู้ใช้ loggerFactory ในการเริ่มต้นเช่นนี้ -

public class Startup
{
    ILogger _logger;
    IFormatter _formatter;
    public Startup(ILoggerFactory loggerFactory, IFormatter formatter)
    {
        _logger = loggerFactory.CreateLogger<Startup>();
        _formatter = formatter;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        _logger.LogDebug($"Total Services Initially: {services.Count}");

        // register services
        //services.AddSingleton<IFoo, Foo>();
    }

    public void Configure(IApplicationBuilder app, IFormatter formatter)
    {
        // note: can request IFormatter here as well as via constructor
        _logger.LogDebug("Configure() started...");
        app.Run(async (context) => await context.Response.WriteAsync(_formatter.Format("Hi!")));
        _logger.LogDebug("Configure() complete.");
    }
}

รายละเอียดที่สมบูรณ์มีอยู่ในลิงค์นี้


12

ใน. NET Core 3.1คุณสามารถสร้างคนตัดไม้ได้โดยตรงโดยใช้ LogFactory

var loggerFactory = LoggerFactory.Create(builder =>
{
     builder.AddConsole();                
});

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

สำหรับ log4net จะเดือดถึงILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();. อย่างไรก็ตามหากคุณบันทึกเฉพาะข้อยกเว้นก็จะไม่เปิดเผยมากไปกว่าสิ่งที่ปรากฏใน Windows EventLog (เมื่อใช้ IIS)
Louis Somers

6

ขณะนี้วิธีแก้ปัญหาอย่างเป็นทางการคือการตั้งค่า LoggerFactory ในพื้นที่เช่นนี้:

    using var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Information);
        builder.AddConsole();
        builder.AddEventSourceLogger();
    });
    var logger = loggerFactory.CreateLogger("Startup");
    logger.LogInformation("Hello World");

ดูเพิ่มเติม: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667


6

สำหรับ. NET Core 3.0 เอกสารอย่างเป็นทางการกล่าวถึง: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

ไม่รองรับการเขียนบันทึกก่อนเสร็จสิ้นการตั้งค่าคอนเทนเนอร์ DI ในStartup.ConfigureServicesวิธีนี้:

  • Startupไม่รองรับการฉีด Logger เข้าไปในคอนสตรัคเตอร์
  • Startup.ConfigureServicesไม่รองรับการแทรก Logger ลงในลายเซ็นของวิธีการ

แต่ตามที่กล่าวไว้ในเอกสารคุณสามารถกำหนดค่าบริการที่ขึ้นอยู่กับ ILogger ได้ดังนั้นหากคุณเขียนคลาส StartupLogger:

public class StartupLogger
{
    private readonly ILogger _logger;

    public StartupLogger(ILogger<StartupLogger> logger)
    {
        _logger = logger;
    }

    public void Log(string message)
    {
        _logger.LogInformation(message);
    }
}

จากนั้นใน Startup.ConfigureServices เพิ่มบริการจากนั้นคุณต้องสร้างผู้ให้บริการเพื่อเข้าถึงคอนเทนเนอร์ DI:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(provider =>
    {
        var service = provider.GetRequiredService<ILogger<StartupLogger>>();
        return new StartupLogger(service);
    });
    var logger = services.BuildServiceProvider().GetRequiredService<StartupLogger>();
    logger.Log("Startup.ConfigureServices called");
}

แก้ไข: สิ่งนี้สร้างคำเตือนคอมไพเลอร์เพื่อประโยชน์ในการดีบักคลาส StartUp ของคุณสิ่งนี้ควรจะใช้ได้ แต่ไม่ใช่สำหรับการผลิต:

Startup.cs (39, 32): [ASP0000] การเรียก 'BuildServiceProvider' จากรหัสแอปพลิเคชันส่งผลให้มีการสร้างสำเนาเพิ่มเติมของบริการซิงเกิลตัน พิจารณาทางเลือกอื่น ๆ เช่นบริการการฉีดการพึ่งพาเป็นพารามิเตอร์เพื่อ 'กำหนดค่า'


4

ฉันใช้วิธีหลีกเลี่ยงไม่ให้ผู้ตัดไม้บุคคลที่สามใช้ "บัฟเฟอร์คนตัดไม้" กับอินเทอร์เฟซของILogger

public class LoggerBuffered : ILogger
{
    class Entry
    {
        public LogLevel _logLevel;
        public EventId  _eventId;
        public string   _message;
    }
    LogLevel            _minLogLevel;
    List<Entry>         _buffer;
    public LoggerBuffered(LogLevel minLogLevel)
    {
        _minLogLevel = minLogLevel;
        _buffer = new List<Entry>();
    }
    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel >= _minLogLevel;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (IsEnabled(logLevel)) {
            var str = formatter(state, exception);
            _buffer.Add(new Entry { _logLevel = logLevel, _eventId = eventId, _message = str });
        }
    }
    public void CopyToLogger (ILogger logger)
    {
        foreach (var entry in _buffer)
        {
            logger.Log(entry._logLevel, entry._eventId, entry._message);
        }
        _buffer.Clear();
    }
}

การใช้งานใน startup.cs นั้นง่ายแน่นอนว่าคุณจะได้รับเอาต์พุตบันทึกหลังจากเรียก Configure แต่ดีกว่าไม่มีอะไร. :

public class Startup
{
ILogger         _logger;

public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
    _logger = new LoggerBuffered(LogLevel.Debug);
    _logger.LogInformation($"Create Startup {env.ApplicationName} - {env.EnvironmentName}");

}

public void ConfigureServices(IServiceCollection services)
{
    _logger.LogInformation("ConfigureServices");
    services.AddControllersWithViews();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
    (_logger as LoggerBuffered).CopyToLogger(logger);
    _logger = logger;   // Replace buffered by "real" logger
    _logger.LogInformation("Configure");

    if (env.IsDevelopment())

2

ไม่มีคำตอบใดที่ใช้ได้ผลสำหรับฉัน ฉันใช้ NLog และแม้แต่สร้าง ServiceCollection ใหม่โดยเรียก. CreateBuilder () ในคอลเล็กชันบริการใด ๆ สร้างบริการบันทึก ... ไม่มีสิ่งใดที่จะเขียนลงในไฟล์บันทึกในระหว่าง ConfigureServices

ปัญหาคือการบันทึกไม่ใช่เรื่องจริงจนกว่าจะมีการสร้าง ServiceCollection และไม่ได้สร้างขึ้นระหว่าง ConfigureServices

โดยทั่วไปฉันต้องการ (ต้องการ) บันทึกสิ่งที่เกิดขึ้นระหว่างการเริ่มต้นในวิธีการขยายการกำหนดค่าเนื่องจากระดับเดียวที่ฉันมีปัญหาคือ PROD ซึ่งฉันไม่สามารถแนบดีบักเกอร์ได้

วิธีแก้ปัญหาที่ใช้ได้ผลสำหรับฉันคือใช้เมธอด. NET Framework NLog แบบเก่า:

private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

เพิ่มสิทธิ์นั้นให้กับคลาสเมธอดส่วนขยายและฉันสามารถเขียนลงในบันทึก ("the" log) ระหว่าง ConfigureServices และ after

ฉันไม่รู้ว่านี่เป็นความคิดที่ดีที่จะปล่อยลงในรหัสการผลิตจริง ๆ หรือไม่ (ฉันไม่รู้ว่า ILogger ที่ควบคุม. NET และ NLog นี้จะขัดแย้งกันหรือไม่) แต่ฉันต้องการเพียงเพื่อดูว่าเกิดอะไรขึ้น บน.


1

รหัสหลัก:

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

CreateDefaultBuilderตั้งค่าตัวบันทึกคอนโซลเริ่มต้น

... กำหนดค่า ILoggerFactory เพื่อล็อกเข้าสู่คอนโซลและดีบักเอาต์พุต

รหัสเริ่มต้น:

using Microsoft.Extensions.Logging;
...
public class Startup
{
    private readonly ILogger _logger;

    public Startup(IConfiguration configuration, ILoggerFactory logFactory)
    {
        _logger = logFactory.CreateLogger<Startup>();
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        _logger.LogInformation("hello stackoverflow");
    }

ฉันไม่สามารถฉีด ILogger ให้ทำงานได้ แต่อาจเป็นเพราะมันไม่ใช่ตัวควบคุม ยินดีต้อนรับข้อมูลเพิ่มเติม!

อ้างถึง:




-2

ฉันจัดการได้โดยการสร้างตัวบันทึกแบบคงที่ด้วย NLog ในไฟล์จากนั้นใช้สิ่งนี้ภายในวิธีการเริ่มต้น

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();

1
แม้ว่าจะใช้งานได้ฉันขอแนะนำให้ใช้อินเทอร์เฟซมาตรฐานที่มาพร้อมกับ ASP.NET Core และการฉีดแบบพึ่งพา (DI) - ทั้งในตัวหรือของบุคคลที่สามสำหรับการบันทึก เมื่อใช้อินเทอร์เฟซคุณสามารถ a) แทนที่การบันทึกที่มีให้หากจำเป็นและ b) พวกเขาจำลองได้ง่ายขึ้นเมื่อคุณทดสอบคลาส (เช่นคอนโทรลเลอร์) ที่คุณฉีด ILogger <TController> เป็นบริการ
Manfred

ฉันเพิ่งเห็นสิ่งนี้เป็นครั้งแรกหลังจากโพสต์คำตอบของตัวเองเกี่ยวกับสิ่งเดียวกัน @ มันเฟรดไม่มีวิธีอื่นที่ใช้ได้ผล ฉันเดาว่านอกเหนือจากSystem.IO.File.Write()วิธีการ
Emery.noel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.