NAME
App::Actions::Runner::Report - what a run tells its user at the end
SYNOPSIS
my $report = App::Actions::Runner::Report->new(docker => $docker);
$report->record('test (perl=5.40)', 'success');
$report->record('lint', 'failure')->fail;
$report->print_summary; # === Workflow summary === ... Workflow FAILED
Keep the logs of a parallel run, and say where they went:
$report->keep_logs($parallel->logs, App::Actions::Logs::default_directory(), $run_id)
->print_kept_logs;
Tell the user what a run with keep left behind:
$report->print_kept_resources($containers->remaining, $temp_path);
Decide the exit status:
exit($report->failed ? 1 : 0);
DESCRIPTION
In short: this module writes what a run tells you at the end: one line per job, whether the workflow passed, and where the logs are. It is a part of App::Actions::Runner; you rarely use it on its own.
Collects one line per job as the run goes, says at the end whether the workflow succeeded, and keeps the logs of the jobs that ran in parallel: they are copied out of the temporary directory before it is removed, so a failed job's own account of what went wrong can be read after the run (and action-run logs lists them).
METHODS
new
ARGUMENTS
docker- used to mask secrets in job names.quiet- optional; print nothing.
RETURNS
An App::Actions::Runner::Report object.
SIDE EFFECTS
None.
EXAMPLE
my $report = App::Actions::Runner::Report->new(docker => $docker, quiet => 0);
API SPECIFICATION
INPUT
{
docker => { type => 'object' },
quiet => { type => 'boolean', optional => 1 },
}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
+---------------------------+------------------------------+-----------------------------+
| Message | Meaning | Resolution |
+---------------------------+------------------------------+-----------------------------+
| Invalid arguments to | docker is missing | Pass the Docker object |
| ...Report::new: REASON | | |
+---------------------------+------------------------------+-----------------------------+
record
$report->record($display_name, $result);
Adds a job's line to the summary. Returns the object.
EXAMPLE
$report->record('lint', 'skipped');
API SPECIFICATION
INPUT
{
name => { type => 'string' },
result => { type => 'string', memberof => [ 'success', 'failure', 'skipped' ] },
}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
None.
fail
Marks the workflow as failed. Returns the object.
EXAMPLE
$report->record($name, 'failure')->fail unless $continue_on_error;
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
None.
failed
Says whether the workflow has failed: 1 or 0.
EXAMPLE
return $report->failed ? 1 : 0;
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'boolean' }
MESSAGES
None.
print_summary
Prints one line per job and whether the workflow succeeded, unless quiet. Returns the object.
EXAMPLE
$report->print_summary;
#
# === Workflow summary ===
# test (perl=5.40) success
# Workflow succeeded
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
+---------------------------+------------------------------+-----------------------------+
| Message | Meaning | Resolution |
+---------------------------+------------------------------+-----------------------------+
| No jobs were run. | Every job was skipped or | Check --job and the jobs' |
| | none was selected | conditions |
| Workflow FAILED | A job failed without | Read the job's output |
| | continue-on-error | |
+---------------------------+------------------------------+-----------------------------+
report_images
$report->report_images($records);
Prints one line per image for a preflight that reports as text: what needs it, and whether it was pulled, present, failed, skipped or deferred. A deferred image is named by its expression and followed by the reason it could not be resolved, which the reader could not see otherwise. Returns the object.
EXAMPLE
$report->report_images($prepared->{records});
# image perl:5.40 (job 'test'): present
API SPECIFICATION
INPUT
{ records => { type => 'arrayref' } }
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
None.
keep_logs
$report->keep_logs($logs, $root, $run_id);
Copies the logs of a parallel run into a directory of their own under $root, named after the time and the run, so --keep is not needed to read them afterwards. A failure to copy them is a warning, not a failed run. Returns the object.
ARGUMENTS
$logs- from "logs" in App::Actions::Runner::Parallel.$root- where the run's log directories go.$run_id- the run's identifier.
SIDE EFFECTS
Creates a private directory and copies the logs into it; warns on failure.
EXAMPLE
$report->keep_logs($parallel->logs, App::Actions::Logs->default_directory, $run_id);
API SPECIFICATION
INPUT
{
logs => { type => 'arrayref' },
root => { type => 'string', min => 1 },
run_id => { type => 'string', min => 1 },
}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
+-----------------------------+------------------------------+-----------------------------+
| Message | Meaning | Resolution |
+-----------------------------+------------------------------+-----------------------------+
| action-run: could not keep | The directory could not be | Check the log directory's |
| the job logs: cannot copy | made or a log copied | space and permissions |
| 'X' to 'Y': E | | |
+-----------------------------+------------------------------+-----------------------------+
print_kept_logs
Says where "keep_logs" put the logs, one line per job, unless quiet or there were none. Returns the object.
EXAMPLE
$report->print_kept_logs;
#
# Job logs kept in /home/me/.cache/action-run/logs/20260101-120000-65f0a1b2c3
# test (n=one) job-1-test_n_one.log
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
None.
print_kept_resources
$report->print_kept_resources($containers->remaining, $temp_path);
Lists what a run that keeps its resources left behind: its containers, its networks and its temporary directory, so they can be inspected and removed. Prints nothing when there are none. Returns the object.
EXAMPLE
$report->print_kept_resources({ containers => [ 'c1' ], networks => [ 'n1' ] }, '/tmp/x');
# Kept Docker resources:
# container c1
# network n1
# temporary directory /tmp/x
API SPECIFICATION
INPUT
{
remaining => { type => 'hashref' },
temp_path => { type => 'string', optional => 1 },
}
OUTPUT
{ type => 'object', isa => 'App::Actions::Runner::Report' }
MESSAGES
None.
LIMITATIONS
The summary's name column is 50 characters wide; a longer matrix name pushes its result to the right.
Logs are kept by copying, so a run's logs briefly take twice the space. Nothing expires them except
action-run logs --prune.The result words (
success,failure,skipped) and image statuses are left untranslated: they are values a script may match, as in the JSON documents.
COMMON PITFALLS
Recording a failure does not fail the workflow. Call "fail" too; "print_summary" and "failed" look only at that flag. A skipped job does not fail the workflow.
Most methods return the object, so calls can be chained; but "failed" returns 0 or 1.
Kept logs are copies. They stay until you run
action-run logs --prune.quiethides the summary and the log list, but not "report_images" output for a text preflight.Result words are not translated.
success,failureandskippedstay in English so scripts can match them.
ENCODING
Job names in the summary are Perl character strings and may contain any Unicode text, including emoji; the name column is counted in characters, so wide characters (such as Chinese) can make the columns uneven. Messages are in the language of App::Actions::I18N. Paths of kept logs are shown as bytes. Log files are copied byte for byte.
AUTHOR
Nigel Horne, njh@nigelhorne.com
FORMAL SPECIFICATION
This section describes each routine in the Z notation, a precise mathematical language for describing programs. You do not need to read it to use the module: everything here is also said in plain words in the sections above. It is for readers who want to check exactly what a routine promises.
How to read it: a box names a routine. The lines above the middle rule declare the values it works with. A name ending in ? is an input, and a name ending in ! is an output. ΞState means the routine changes nothing, and ΔState means it can change the state. The lines below the rule are the conditions that are always true when the routine returns.
new
┌─ Report ──────────────────────────────────────────
│ entries : seq (NAME × RESULT)
│ failed : 𝔹
│ kept : optional DIRECTORY
├───────────────────────────────────────────────────
│ true
└───────────────────────────────────────────────────
record
┌─ Record ──────────────────────────────────────────
│ ΔReport
│ name?, result? : seq CHAR
├───────────────────────────────────────────────────
│ entries' = entries ⁀ ⟨(name?, result?)⟩
└───────────────────────────────────────────────────
fail
┌─ Fail ────────────────────────────────────────────
│ ΔReport
├───────────────────────────────────────────────────
│ failed' = true
└───────────────────────────────────────────────────
failed
┌─ Failed ──────────────────────────────────────────
│ ΞReport
│ f! : 𝔹
├───────────────────────────────────────────────────
│ f! = failed
└───────────────────────────────────────────────────
print_summary
┌─ PrintSummary ────────────────────────────────────
│ ΞReport
│ out! : seq LINE
├───────────────────────────────────────────────────
│ quiet ⇒ out! = ⟨⟩
│ ¬ quiet ⇒ out! = ⟨header⟩ ⁀ lines(entries) ⁀ ⟨verdict(failed)⟩
└───────────────────────────────────────────────────
report_images
┌─ ReportImages ────────────────────────────────────
│ ΞReport
│ records? : seq RECORD
│ out! : seq LINE
├───────────────────────────────────────────────────
│ #out! = #records? + #{r : ran records? | r.status = deferred ∧ r.error ≠ ⊥}
└───────────────────────────────────────────────────
keep_logs
┌─ KeepLogs ────────────────────────────────────────
│ ΔReport
│ logs? : seq LOG
├───────────────────────────────────────────────────
│ let present == {l : ran logs? | exists(l.path)}
│ present = ∅ ⇒ kept' = kept
│ present ≠ ∅ ∧ copied ⇒ kept' = dir ∧ files(dir) = {l.file | l ∈ present}
└───────────────────────────────────────────────────
print_kept_logs
┌─ PrintKeptLogs ───────────────────────────────────
│ ΞReport
├───────────────────────────────────────────────────
│ quiet ∨ kept = ⊥ ⇒ out! = ⟨⟩
└───────────────────────────────────────────────────
print_kept_resources
┌─ PrintKeptResources ──────────────────────────────
│ ΞReport
│ r? : RESOURCES
│ temp? : optional DIRECTORY
├───────────────────────────────────────────────────
│ r?.containers = ⟨⟩ ∧ r?.networks = ⟨⟩ ∧ temp? = ⊥ ⇒ out! = ⟨⟩
└───────────────────────────────────────────────────
STATE DIAGRAM
The report collects results while the run goes on, then prints once:
new(docker => ..., quiet => ...)
|
v
+-------------------+ record($name, 'success' | 'skipped')
| COLLECTING |<---------------------------------------+
| (flag: passing) | |
+-------------------+ |
| | record($name, 'failure')->fail |
| v |
| +-------------------+ more record() calls ------->+
| | COLLECTING |
| | (flag: failed) | failed() = 1 from now on
| +-------------------+
| |
+--+--+
| preflight with text output: report_images($records)
| (one line per image: pulled / present / failed /
| skipped / deferred)
v
+-------------------+ parallel run: keep_logs($logs, $root, $run_id)
| RUN FINISHED | copies logs to $root/DATE-TIME-RUNID/
+-------------------+ (a copy error is only a warning)
|
v
+-------------------+ "=== Workflow summary ===", one line per
| print_summary | job, then "Workflow succeeded" or
+-------------------+ "Workflow FAILED" (unless quiet)
|
+--> print_kept_logs: where the logs are (unless quiet)
+--> print_kept_resources: containers, networks and
temporary folder left by a run with keep
LICENSE AND COPYRIGHT
Copyright 2026 Nigel Horne.
This program is released under the GNU General Public License, version 2. See the LICENSE file that comes with this distribution. If you use it, please let me know.