You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Azure Function App(.NET 6.0)中配置Serilog的文件日志问题

.NET 6 Azure Function App集成Serilog日志问题

我在.NET 6的Azure Function App中集成Serilog时遇到两个问题:

  1. 控制台能看到[15:58:00 INF] Hello World!,但日志文件里完全没内容;
  2. 日志中出现[16:03:24 INF] null的奇怪条目。

我试过两种Startup配置方式(分别实现IWebJobsStartup和FunctionsStartup),问题都没解决。我的最终目标是把日志写入Azure Blob,求解决办法。


第一种配置(IWebJobsStartup)

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Serilog;
using Serilog.Events;
using Serilog.Extensions.Logging;
using SerilogFunctionApp;
using ILogger = Microsoft.Extensions.Logging.ILogger;

[assembly: WebJobsStartup(typeof(Startup))]

namespace SerilogFunctionApp
{
  public class Startup : IWebJobsStartup
  {
    public void Configure(IWebJobsBuilder builder)
    {
      string basedir = AppDomain.CurrentDomain.BaseDirectory;

      Log.Logger = new LoggerConfiguration()
          .MinimumLevel.Information()
          .WriteTo.Console()
          .WriteTo.Debug()
          .WriteTo.File(basedir + "/logs/log-.txt", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
        .CreateLogger();

      builder.Services.AddLogging(lb => { lb.AddSerilog(Log.Logger, true); });
    }
  }

  public class Function1
  {
    //other code
    private ILogger _log;

    public Function1(ILoggerFactory loggerFactory)
    {
      _log = loggerFactory.CreateLogger<Function1>();
    }
    
    [FunctionName("Function1")]
    public void Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, ILogger log)
    {
      _log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

      Log.Information("Hello World!");
    }
  }
}

第二种配置(FunctionsStartup)

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;

[assembly: FunctionsStartup(typeof(SerilogFunctionApp.Startup))]

namespace SerilogFunctionApp
{
  public class Startup : FunctionsStartup
  {
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
      var context = builder.GetContext();

      //builder.ConfigurationBuilder
      //  .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
      //  .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false);

    }
    public override void Configure(IFunctionsHostBuilder builder)
    {
      // get the configuration from the builder
      var configuration = builder.GetContext().Configuration;

      string basedir = AppDomain.CurrentDomain.BaseDirectory;

      Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Information()
        .WriteTo.Console()
        .WriteTo.Debug()
        .WriteTo.File(basedir + "/logs/log-.txt", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
        .CreateLogger();

      builder.Services.AddLogging(lb => { lb.AddSerilog(Log.Logger, true); });
    }
  }

  public class Function1
  {
    //other code
    private ILogger _log;

    public Function1(ILoggerFactory loggerFactory)
    {
      _log = loggerFactory.CreateLogger<Function1>();
    }
    
    [FunctionName("Function1")]
    public void Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, ILogger log)
    {
      _log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

      Log.Information("Hello World!");
    }
  }
}

问题分析与解决

1. 文件无日志的原因及修复

Azure Function App运行时的当前工作目录并非AppDomain.CurrentDomain.BaseDirectory,直接用该路径写文件会导致日志写入无权限或找不到的位置;另外Serilog不会自动创建不存在的目录,也会导致写文件失败。

修复步骤:

  • 改用Environment.GetEnvironmentVariable("HOME")获取Function根目录(本地调试指向项目bin目录,Azure环境指向宿主目录);
  • 手动创建日志目录;
  • 统一使用注入的ILogger实例,避免混用Serilog静态Log类,确保所有日志走Serilog配置管道。

修改后的Logger配置片段:

var homeDir = Environment.GetEnvironmentVariable("HOME") ?? AppDomain.CurrentDomain.BaseDirectory;
var logDir = Path.Combine(homeDir, "logs");
Directory.CreateDirectory(logDir); // 确保目录存在

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.File(Path.Combine(logDir, "log-.txt"), rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
    .CreateLogger();

2. 解决null日志条目问题

该问题由Azure Functions默认日志系统输出的未格式化日志导致,可通过Serilog的过滤规则排除:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Filter.ByExcluding(evt => evt.MessageTemplate.Text == "null") // 过滤null日志
    .WriteTo.Console()
    .WriteTo.File(Path.Combine(logDir, "log-.txt"), rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
    .CreateLogger();

3. 实现Azure Blob日志写入(最终目标)

使用Serilog.Sinks.AzureBlobStorage包实现Blob日志写入,步骤如下:

第一步:安装NuGet包

Install-Package Serilog.Sinks.AzureBlobStorage

第二步:配置Blob Sink

在Logger配置中添加Blob写入逻辑,可复用Function的存储连接字符串:

var blobConnectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Filter.ByExcluding(evt => evt.MessageTemplate.Text == "null")
    .WriteTo.Console()
    .WriteTo.AzureBlobStorage(
        connectionString: blobConnectionString,
        storageContainerName: "function-logs",
        blobNameFormat: "logs/log-{Date}.txt"
    )
    .CreateLogger();

第三步:权限配置

  • 本地调试:确保开发账号拥有Blob容器的写入权限;
  • Azure环境:为Function App的系统身份分配Storage Blob Data Contributor角色权限。

最终完整Startup配置(推荐用FunctionsStartup)

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Serilog;

[assembly: FunctionsStartup(typeof(SerilogFunctionApp.Startup))]

namespace SerilogFunctionApp
{
  public class Startup : FunctionsStartup
  {
    public override void Configure(IFunctionsHostBuilder builder)
    {
      var blobConnectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
      var homeDir = Environment.GetEnvironmentVariable("HOME") ?? AppDomain.CurrentDomain.BaseDirectory;
      var logDir = Path.Combine(homeDir, "logs");
      Directory.CreateDirectory(logDir);

      Log.Logger = new LoggerConfiguration()
          .MinimumLevel.Information()
          .Filter.ByExcluding(evt => evt.MessageTemplate.Text == "null")
          .WriteTo.Console()
          .WriteTo.File(Path.Combine(logDir, "log-.txt"), rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
          .WriteTo.AzureBlobStorage(
              blobConnectionString,
              storageContainerName: "function-logs",
              blobNameFormat: "logs/log-{Date}.txt"
          )
          .CreateLogger();

      builder.Services.AddLogging(lb => 
      {
        lb.ClearProviders(); // 清除默认日志提供器,确保仅使用Serilog
        lb.AddSerilog(Log.Logger, dispose: true);
      });
    }
  }

  public class Function1
  {
    private readonly ILogger<Function1> _log;

    // 直接注入ILogger<Function1>,无需手动从Factory创建
    public Function1(ILogger<Function1> log)
    {
      _log = log;
    }
    
    [FunctionName("Function1")]
    public void Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer)
    {
      _log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
      _log.LogInformation("Hello World!"); // 统一使用注入的Logger
    }
  }
}

内容的提问来源于stack exchange,提问作者PKonstant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 06:45:15