在Azure Function App(.NET 6.0)中配置Serilog的文件日志问题
.NET 6 Azure Function App集成Serilog日志问题
我在.NET 6的Azure Function App中集成Serilog时遇到两个问题:
- 控制台能看到
[15:58:00 INF] Hello World!,但日志文件里完全没内容; - 日志中出现
[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
相关产品推荐
相关产品推荐

