From 1e48b8185b0242a944f5b68d26b86a99cdbff201 Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Fri, 8 May 2026 09:12:35 -0400 Subject: [PATCH] Log execution outcomes (success / failure / timeout / launch error) Hook runs were silently dropping their result into the void after returning the HTTP response. For sync runs the body went to the caller but nothing was logged; for async runs the result vanished unless a callback was configured. That made debugging RunAs failures (logon errors, missing executables) effectively impossible since the service log only showed the 202. Now every run emits one log line at INF (success) or WRN (non-zero exit / timeout / launch error) with runId, slug, exit code, duration, and truncated stdout/stderr. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/WebhookServer.Service/WebhookRouter.cs | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/WebhookServer.Service/WebhookRouter.cs b/src/WebhookServer.Service/WebhookRouter.cs index 7382ba5..5095b73 100644 --- a/src/WebhookServer.Service/WebhookRouter.cs +++ b/src/WebhookServer.Service/WebhookRouter.cs @@ -126,6 +126,7 @@ public sealed class WebhookRouter } var result = await RunAsync(endpoint, ctx, http.RequestAborted).ConfigureAwait(false); + LogResult(endpoint, ctx, result); DispatchCallback(endpoint, ctx, result); if (result.LaunchError is not null) @@ -157,6 +158,7 @@ public sealed class WebhookRouter try { var result = await RunAsync(endpoint, ctx, ct).ConfigureAwait(false); + LogResult(endpoint, ctx, result); DispatchCallback(endpoint, ctx, result); } catch (Exception ex) @@ -165,6 +167,47 @@ public sealed class WebhookRouter } } + private void LogResult(EndpointConfig endpoint, ExecCtx ctx, ExecutionResult result) + { + if (result.LaunchError is not null) + { + _logger.LogWarning("Run {RunId} {Slug} failed to launch: {Error}", + ctx.RunId, ctx.Slug, result.LaunchError); + return; + } + if (result.TimedOut) + { + _logger.LogWarning("Run {RunId} {Slug} timed out after {Sec}s; process killed", + ctx.RunId, ctx.Slug, endpoint.TimeoutSeconds); + return; + } + + var stdout = TruncateForLog(result.Stdout, 512); + var stderr = TruncateForLog(result.Stderr, 512); + if (result.Succeeded) + { + _logger.LogInformation( + "Run {RunId} {Slug} ok exit={Exit} dur={Ms}ms stdout={Stdout}{StderrPart}", + ctx.RunId, ctx.Slug, result.ExitCode, (long)result.Duration.TotalMilliseconds, + stdout, string.IsNullOrEmpty(stderr) ? "" : $" stderr={stderr}"); + } + else + { + _logger.LogWarning( + "Run {RunId} {Slug} non-zero exit={Exit} dur={Ms}ms stdout={Stdout} stderr={Stderr}", + ctx.RunId, ctx.Slug, result.ExitCode, (long)result.Duration.TotalMilliseconds, + stdout, stderr); + } + } + + private static string TruncateForLog(string s, int max) + { + if (string.IsNullOrEmpty(s)) return "(empty)"; + var trimmed = s.Trim(); + if (trimmed.Length <= max) return trimmed; + return trimmed.Substring(0, max) + $"... [+{trimmed.Length - max} chars]"; + } + private async Task RunAsync(EndpointConfig endpoint, ExecCtx ctx, CancellationToken ct) { if (endpoint.Serialize)