case 4703 - report script console.error details in render errors

The page log showed console.error objects only as JSHandle@object and errors
without their stack. Each ERROR line is still logged at once; object and
Error arguments are then fetched from the page and added as detail lines
(object JSON, error stack without Puppeteer frames and with file names only).
A failing render waits up to 2 s for pending details. Page log appends are
locked. Report designer manual (Script errors) and changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 08:14:52 -07:00
parent 64edeea874
commit c55b2fc723
4 changed files with 83 additions and 16 deletions

View File

@@ -898,6 +898,14 @@ What could be the issue here?
}
log.LogDebug($"Report browser launch arguments: '{string.Join(" ", lo.Args)}'");
System.Text.StringBuilder PageLog = new System.Text.StringBuilder();
//case 4703: console events and the API relay add to the page log from other threads
void AppendPageLog(string line)
{
lock (PageLog)
PageLog.AppendLine(line);
}
//case 4703: details of console.error arguments are fetched from the page after the error line is logged
var PendingConsoleDetails = new List<Task>();
if (!ReportRenderManager.KeepGoing(job.GId))
return;
@@ -930,22 +938,21 @@ What could be the issue here?
switch (args.Message.Type)
{
case ConsoleType.Error:
try
AppendPageLog($"ERROR: {args.Message.Text}");
//case 4703: the message text shows an object only as JSHandle@object and an error without its stack, so
//for each argument the page is asked for an error's stack or an object's JSON; added as detail lines
if (args.Message.Args != null && args.Message.Args.Any())
{
//case 4586 todo force an error here and see what we can do with it old vs new behaviour test on current release same report with erorr triggered
//ok, I tested it with an exception and it worked just find so shrug emoji
PageLog.AppendLine($"ERROR: {args.Message.Text}");
//case 4586 this no longer works due to no executioncontext in arg in teh block below
// var errorArgs = await Task.WhenAll(args.Message.Args.Select(arg => arg.ExecutionContext.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg", arg)));
// PageLog.AppendLine($"ERROR: {args.Message.Text} args: [{string.Join<object>(", ", errorArgs)}]");
var detailTask = AppendConsoleErrorDetailsAsync(args.Message.Text ?? string.Empty, args.Message.Args, AppendPageLog);
lock (PendingConsoleDetails)
PendingConsoleDetails.Add(detailTask);
}
catch { }
break;
case ConsoleType.Warning:
PageLog.AppendLine($"WARNING: {args.Message.Text}");
AppendPageLog($"WARNING: {args.Message.Text}");
break;
default:
PageLog.AppendLine($"INFO: {args.Message.Text}");
AppendPageLog($"INFO: {args.Message.Text}");
break;
}
@@ -962,7 +969,7 @@ What could be the issue here?
pageClientMeta["DownloadToken"] = ReportApiRelay.DOWNLOAD_KEY_PLACEHOLDER;
//case 4632: inside IIS the page's API calls are also relayed by the server
log.LogDebug($"Handling report API calls under {apiUrl}{(relayTarget == null ? "" : $", relayed to {relayTarget}")}");
relayClient = await ReportApiRelay.AttachAsync(page, apiUrl, relayTarget, realAuthorization, realDownloadKey, TimeSpan.FromMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), s => PageLog.AppendLine(s), log);
relayClient = await ReportApiRelay.AttachAsync(page, apiUrl, relayTarget, realAuthorization, realDownloadKey, TimeSpan.FromMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), AppendPageLog, log);
log.LogDebug($"Preparing page: adding base reporting scripts to page");
if (!ReportRenderManager.KeepGoing(job.GId))
@@ -1258,18 +1265,27 @@ What could be the issue here?
//This is the error when a helper is used on the template but doesn't exist:
//Evaluation failed: d
//(it might also mean other things wrong with template)
if (PageLog.Length > 0)
//case 4703: let console.error details still being fetched reach the page log (at most 2 seconds)
Task[] pendingDetails;
lock (PendingConsoleDetails)
pendingDetails = PendingConsoleDetails.ToArray();
if (pendingDetails.Length > 0)
await Task.WhenAny(Task.WhenAll(pendingDetails), Task.Delay(2000));
string pageLogText;
lock (PageLog)
pageLogText = PageLog.ToString();
if (pageLogText.Length > 0)
{
log.LogInformation(
$"Exception caught while rendering report \"{report.Name}\", report Page console log:"
);
log.LogInformation(PageLog.ToString());
log.LogInformation(pageLogText);
var json = Newtonsoft.Json.JsonConvert.SerializeObject(
new
{
rendererror = new
{
pagelog = PageLog.ToString(),
pagelog = pageLogText,
exception = ExceptionUtil.ExtractAllExceptionMessages(ex),
},
},
@@ -1345,6 +1361,42 @@ What could be the issue here?
await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Failed);
}
//case 4703: for each console.error argument that is an Error (its stack: message, file and line) or an object (its
//JSON, cut at 1000 characters) adds an indented detail line to the page log, labelled with the start of the error's
//message text as details arrive after the error lines; other values are already in the message text.
//Stack lines from Puppeteer's own evaluation code are left out and file paths shortened to the file name
static async Task AppendConsoleErrorDetailsAsync(string messageText, IEnumerable<IJSHandle> consoleArgs, Action<string> appendPageLog)
{
const string detailScript = @"(arg) => {
if (arg instanceof Error) {
const s = arg.stack || String(arg);
return s.split('\n')
.filter(l => !/__puppeteer_evaluation_script__|RAFPoller|createFunction/.test(l))
.map(l => l.replace(/\((?:[^()]*[\\\/])?([^\\\/()]+:\d+:\d+)\)/, '($1)'))
.join('\n');
}
if (arg !== null && typeof arg === 'object') {
try { const s = JSON.stringify(arg); return s && s.length > 1000 ? s.substring(0, 1000) + '...' : s; }
catch (e) { return String(arg); }
}
return null;
}";
var label = messageText.Length > 60 ? messageText.Substring(0, 60) + "..." : messageText;
foreach (var arg in consoleArgs)
{
try
{
var detail = await arg.EvaluateFunctionAsync<string>(detailScript);
if (!string.IsNullOrWhiteSpace(detail))
appendPageLog($" detail for \"{label}\": " + detail.Replace("\n", "\n "));
}
catch
{
//the page may already be closed; the error line itself is logged regardless
}
}
}
//Report browser could not be downloaded or started: log it properly and give the client a specific error
//instead of the generic job failure it got when this escaped to the job processor
static async Task HandleBrowserFailure(OpsJob job, ILogger log, string what, Exception ex)