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存在本质差异:
- Functions的日志管道默认由Google Functions Framework接管,
AddSerilog仅作为附加日志提供器存在,无法完全替代默认日志处理,导致LogContext的 enrichment无法被正确传递到Google日志 sink。 - 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

