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

Serilog结构化日志在Google Cloud Functions(.NET)中无法正常工作

问题描述

我们在Google Cloud Kubernetes容器中的ASP.NET Core API,以及.NET Core编写的Google Cloud Functions中,均使用封装于.NET ILogger的Serilog进行日志记录。尝试添加结构化数据以显示在Google日志事件的jsonPayload中时,ASP.NET Core服务可正常实现,但.NET Core Functions无法生效。我们期望自定义DTO能显示在Google Cloud Logs Explorer的函数日志jsonPayload.properties字段中,猜测可能是API与Functions的HostBuilder对象存在差异(尽管两者均实现IHostBuilder),已安装所有要求的NuGet包。

所用代码

API初始化代码

public static WebApplicationBuilder AddStructuredLoggerToCloudApiService(this WebApplicationBuilder builder)
{
    var definition = DefineLoggingSetup(builder.Configuration);
    var logger = definition.CreateLogger();

    builder.Host.ConfigureLogging(logging => logging.AddSerilog(logger));

    // Chainable.
    return builder;
}

Functions初始化代码

public static IHostBuilder AddStructuredLoggerToCloudFunction(this IHostBuilder builder, WebHostBuilderContext context)
{
    var definition = DefineLoggingSetup(context.Configuration);
    var logger = definition.CreateLogger();

    builder.ConfigureLogging(logging => logging.AddSerilog(logger));

    // Chainable.
    return builder;
}

共享配置依赖初始化代码

private static LoggerConfiguration DefineLoggingSetup(IConfiguration config)
{
    var definition = new LoggerConfiguration()
        .ReadFrom.Configuration(config)   // Scoped to calling code: sinks, message template.
        .Enrich.FromLogContext();         // Supports later AddLogData, etc.

    return definition;
}

LogContext使用代码

public static IDisposable AddLogData<TLogData>(this ILogger _, TLogData data)
{
    return LogContext.PushProperty(typeof(TLogData).Name, data, true);
}

appsettings.json日志配置

"Serilog": {
    "Using": [ "Serilog.Sinks.GoogleCloudLogging" ],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "GoogleCloudLogging",
        "Args": {
          "projectID": "************",
          "restrictedToMinimumLevel": "Information",
          "outputTemplate": "****=> {Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
        }
      }
    ]
  }
问题分析与解决方法

核心原因

Google Cloud Functions的宿主模型与ASP.NET Core存在本质差异:

  1. Functions的日志管道默认由Google Functions Framework接管,AddSerilog仅作为附加日志提供器存在,无法完全替代默认日志处理,导致LogContext的 enrichment无法被正确传递到Google日志 sink。
  2. Functions的执行上下文是隔离的,每个请求的LogContext作用域可能未被正确捕获或序列化。

解决步骤

1. 替换Serilog集成方式为UseSerilog

将Functions的初始化代码从ConfigureLogging添加Serilog,改为使用UseSerilog直接接管日志管道:

public static IHostBuilder AddStructuredLoggerToCloudFunction(this IHostBuilder builder, WebHostBuilderContext context)
{
    return builder.UseSerilog((hostingContext, loggerConfiguration) => 
        loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration)
                          .Enrich.FromLogContext());
}

这种方式会让Serilog成为唯一的日志提供器,确保LogContext的结构化数据能被正确处理。

2. 调整Google Cloud Logging Sink配置

在appsettings.json的Serilog配置中,添加useJsonOutput: true并移除outputTemplate(结构化日志不需要文本模板),确保sink直接输出JSON格式日志:

"Serilog": {
    "Using": [ "Serilog.Sinks.GoogleCloudLogging" ],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "GoogleCloudLogging",
        "Args": {
          "projectID": "************",
          "restrictedToMinimumLevel": "Information",
          "useJsonOutput": true
        }
      }
    ]
  }

3. 确保LogContext作用域在函数执行内生效

在Functions的入口方法中,明确包裹LogContext的作用域,确保结构化数据被绑定到当前请求的日志上下文:

[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    using (log.AddLogData(new MyCustomDto { Id = 1, Name = "Test" }))
    {
        log.LogInformation("Function executed");
        return new OkObjectResult("Success");
    }
}

4. 验证序列化配置

确保自定义DTO的属性能被正确序列化,可添加[DataContract]和[DataMember]特性,或在Serilog配置中指定序列化规则:

private static LoggerConfiguration DefineLoggingSetup(IConfiguration config)
{
    var definition = new LoggerConfiguration()
        .ReadFrom.Configuration(config)
        .Enrich.FromLogContext()
        .Destructure.AsScalar<MyCustomDto>(); // 或根据需求使用Destructure.ToMaximumDepth等配置

    return definition;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:47:08