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

如何在Startup.cs中从Azure KeyVault获取密钥配置Serilog SQL Sink

解决Azure KeyVault连接字符串在Serilog SQL Sink初始化时为空的问题

问题背景

  • 目标:通过Azure KeyVault获取连接字符串,配置Serilog的SQL Sink
  • 问题:应用启动阶段读取KeyVault密钥时始终为空,但在控制器等生命周期后期可正常获取,确认KeyVault配置无问题,推测是启动阶段密钥未完成加载。

用户提供的代码示例:

public class SqlLogOptions
{
    public string AppsettingKey { get; } = "SqlLog";
    public string ConnectionString { get; set; }
}

private readonly SqlLogConfig _SqlLogConfig = new SqlLogConfig();

public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
    configuration.GetSection(_SqlLogConfig.AppsettingKey).Bind(_SqlLogConfig);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IMapper mapper, IHostApplicationLifetime applicationLifetime)
{
    applicationLifetime.ApplicationStarted.Register(OnStarted);
    //...
}

private void OnStarted()
{
    // Handle configuration that needs to happen once the application has started
    ConfigureLogger();
}

private void ConfigureLogger()
{
    var sinkOptions = new MSSqlServerSinkOptions()
    {
        TableName = "SerilogLogs",
        AutoCreateSqlTable = false
    };

    // Read the connection string from KeyVault Secret
    var connectionString = _SqlLogConfig.ConnectionString;
    if (!string.IsNullOrEmpty(connectionString))
    {
        Log.Logger = new LoggerConfiguration()
                       .MinimumLevel.Information()
                       .WriteTo.MSSqlServer(connectionString, sinkOptions, null, null, Serilog.Events.LogEventLevel.Information)
                       .CreateLogger();

        Log.Logger.Information("***** Connection to SQL logging store could not be established ******");
    }
    else
    {
        throw new Exception("Connection to SQL logging store could not be established.");
    }
}

解决方案

核心原因是Azure KeyVault的配置加载为异步过程,在Startup构造函数或早期同步阶段读取时,密钥尚未完成加载。以下是三种实用解决方式:

方式1:延迟绑定配置至应用启动后

不在Startup构造函数中提前绑定配置,而是在ApplicationStarted事件触发后,重新从IConfiguration读取,确保KeyVault密钥已加载:

修改Startup类,保留IConfiguration引用:

private readonly IConfiguration _configuration;
private SqlLogConfig _sqlLogConfig;

public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
    _configuration = configuration;
    // 移除构造函数中的配置绑定操作
}

在OnStarted方法中重新绑定并初始化日志:

private void OnStarted()
{
    _sqlLogConfig = new SqlLogConfig();
    _configuration.GetSection(_sqlLogConfig.AppsettingKey).Bind(_sqlLogConfig);
    ConfigureLogger();
}

方式2:直接通过Serilog配置读取KeyVault密钥

利用Serilog的配置读取能力,直接从已加载KeyVault的IConfiguration中获取连接字符串,无需手动绑定:

  1. 在appsettings.json中添加Serilog配置(替换为你的KeyVault密钥路径):
{
  "Serilog": {
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "@Microsoft.KeyVault(SecretUri=https://your-vault-name.vault.azure.net/secrets/your-secret-name)",
          "tableName": "SerilogLogs",
          "autoCreateSqlTable": false,
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}
  1. 在Program.cs中配置Host时,让Serilog直接读取配置:
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            // 配置Azure KeyVault
            var builtConfig = config.Build();
            config.AddAzureKeyVault(
                new Uri(builtConfig["KeyVault:Uri"]),
                new DefaultAzureCredential());
        })
        .UseSerilog((context, loggerConfig) =>
        {
            // 直接从配置中读取Serilog设置
            loggerConfig.ReadFrom.Configuration(context.Configuration);
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

这种方式会自动等待KeyVault配置加载完成后初始化Serilog,彻底避免空值问题。

方式3:使用异步HostedService初始化Serilog

通过IHostedService的异步启动方法初始化Serilog,该阶段KeyVault配置已完成加载:

  1. 创建自定义HostedService:
public class SerilogInitializerHostedService : IHostedService
{
    private readonly IConfiguration _configuration;

    public SerilogInitializerHostedService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // 读取配置
        var sqlLogConfig = new SqlLogConfig();
        _configuration.GetSection(sqlLogConfig.AppsettingKey).Bind(sqlLogConfig);
        
        var connectionString = sqlLogConfig.ConnectionString;
        if (string.IsNullOrEmpty(connectionString))
            throw new Exception("Connection to SQL logging store could not be established.");

        // 初始化Serilog
        var sinkOptions = new MSSqlServerSinkOptions()
        {
            TableName = "SerilogLogs",
            AutoCreateSqlTable = false
        };

        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.MSSqlServer(connectionString, sinkOptions, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information)
            .CreateLogger();

        Log.Information("Serilog SQL Sink initialized successfully.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Log.CloseAndFlush();
        return Task.CompletedTask;
    }
}
  1. 在Startup的ConfigureServices中注册该服务:
public void ConfigureServices(IServiceCollection services)
{
    // 其他服务注册...
    services.AddHostedService<SerilogInitializerHostedService>();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 20:01:33