Skip to content

Commit 1167ec1

Browse files
author
Enno Woortmann
committed
Add debug output formatter
1 parent 9b26f04 commit 1167ec1

18 files changed

Lines changed: 306 additions & 67 deletions

src/Stage/Next/AllowNextExecuteWorkflow.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPWorkflow\Exception\WorkflowControl\SkipWorkflowException;
99
use PHPWorkflow\Exception\WorkflowException;
1010
use PHPWorkflow\State\ExecutionLog\ExecutionLog;
11+
use PHPWorkflow\State\ExecutionLog\Summary;
1112
use PHPWorkflow\State\WorkflowContainer;
1213
use PHPWorkflow\State\WorkflowResult;
1314
use PHPWorkflow\State\WorkflowState;
@@ -38,12 +39,12 @@ public function executeWorkflow(
3839

3940
$workflowState->getExecutionLog()->stopExecution();
4041
$workflowState->setStage(WorkflowState::STAGE_SUMMARY);
41-
$workflowState->addExecutionLog('Workflow execution');
42+
$workflowState->addExecutionLog(new Summary('Workflow execution'));
4243
} catch (Exception $exception) {
4344
$workflowState->getExecutionLog()->stopExecution();
4445
$workflowState->setStage(WorkflowState::STAGE_SUMMARY);
4546
$workflowState->addExecutionLog(
46-
'Workflow execution',
47+
new Summary('Workflow execution'),
4748
$exception instanceof SkipWorkflowException ? ExecutionLog::STATE_SKIPPED : ExecutionLog::STATE_FAILED,
4849
$exception->getMessage(),
4950
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State\ExecutionLog;
6+
7+
/**
8+
* Interface Describable
9+
*
10+
* @package PHPWorkflow\State\ExecutionLog
11+
*/
12+
interface Describable
13+
{
14+
/**
15+
* Describe in a few words what this step does
16+
*/
17+
public function getDescription(): string;
18+
}

src/State/ExecutionLog/ExecutionLog.php

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PHPWorkflow\State\ExecutionLog;
66

7+
use PHPWorkflow\State\ExecutionLog\OutputFormat\OutputFormat;
78
use PHPWorkflow\State\WorkflowState;
89
use PHPWorkflow\Step\WorkflowStep;
910

@@ -15,7 +16,7 @@ class ExecutionLog
1516

1617
/** @var Step[][] */
1718
private array $stages = [];
18-
/** @var string[] Collect additional debug info concerning the current step */
19+
/** @var StepInfo[] Collect additional debug info concerning the current step */
1920
private array $stepInfo = [];
2021
/** @var string[][] Collect all warnings which occurred during the workflow execution */
2122
private array $warnings = [];
@@ -30,32 +31,22 @@ public function __construct(WorkflowState $workflowState)
3031
$this->workflowState = $workflowState;
3132
}
3233

33-
public function addStep(int $stage, string $step, string $state, ?string $reason): void {
34+
public function addStep(int $stage, Describable $step, string $state, ?string $reason): void {
3435
$stage = $this->mapStage($stage);
3536

3637
$this->stages[$stage][] = new Step($step, $state, $reason, $this->stepInfo, $this->warningsDuringStep);
3738
$this->stepInfo = [];
3839
$this->warningsDuringStep = 0;
3940
}
4041

41-
public function __toString(): string
42+
public function debug(OutputFormat $formatter)
4243
{
43-
$debug = "Process log for workflow '{$this->workflowState->getWorkflowName()}':\n";
44-
45-
foreach ($this->stages as $stage => $steps) {
46-
$debug .= "$stage:\n";
47-
48-
foreach ($steps as $step) {
49-
$debug .= ' - ' . $step . "\n";
50-
}
51-
}
52-
53-
return trim($debug);
44+
return $formatter->format($this->workflowState->getWorkflowName(), $this->stages);
5445
}
5546

56-
public function attachStepInfo(string $info): void
47+
public function attachStepInfo(string $info, array $context = []): void
5748
{
58-
$this->stepInfo[] = $info;
49+
$this->stepInfo[] = new StepInfo($info, $context);
5950
}
6051

6152
public function addWarning(string $message, bool $workflowReportWarning = false): void
@@ -74,11 +65,11 @@ public function startExecution(): void
7465

7566
public function stopExecution(): void
7667
{
77-
$this->attachStepInfo("Execution time: " . number_format(1000 * (microtime(true) - $this->startAt), 5) . 'ms');
68+
$this->attachStepInfo('Execution time: ' . number_format(1000 * (microtime(true) - $this->startAt), 5) . 'ms');
7869

7970
if ($this->warnings) {
8071
$warnings = sprintf(
81-
"Got %s warning%s during the execution:",
72+
'Got %s warning%s during the execution:',
8273
$amount = count($this->warnings, COUNT_RECURSIVE) - count($this->warnings),
8374
$amount > 1 ? 's' : '',
8475
);
@@ -87,7 +78,7 @@ public function stopExecution(): void
8778
$warnings .= implode(
8879
'',
8980
array_map(
90-
fn (string $warning): string => sprintf("\n %s: %s", $stage, $warning),
81+
fn (string $warning): string => sprintf(PHP_EOL . ' %s: %s', $stage, $warning),
9182
$stageWarnings,
9283
),
9384
);
@@ -107,7 +98,7 @@ private function mapStage(int $stage): string
10798
case WorkflowState::STAGE_ON_ERROR: return 'On Error';
10899
case WorkflowState::STAGE_ON_SUCCESS: return 'On Success';
109100
case WorkflowState::STAGE_AFTER: return 'After';
110-
case WorkflowState::STAGE_SUMMARY: return "\nSummary";
101+
case WorkflowState::STAGE_SUMMARY: return PHP_EOL . 'Summary';
111102
}
112103
}
113104

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State\ExecutionLog\OutputFormat;
6+
7+
use PHPWorkflow\State\ExecutionLog\Step;
8+
9+
interface OutputFormat
10+
{
11+
/**
12+
* @param string $workflowName
13+
* @param Step[][] $steps Contains a list of the executed steps, grouped by the executed stages
14+
*
15+
* @return mixed
16+
*/
17+
public function format(string $workflowName, array $steps);
18+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State\ExecutionLog\OutputFormat;
6+
7+
use PHPWorkflow\State\ExecutionLog\Step;
8+
use PHPWorkflow\State\ExecutionLog\StepInfo;
9+
use PHPWorkflow\State\WorkflowResult;
10+
11+
class StringLog implements OutputFormat
12+
{
13+
public function format(string $workflowName, array $steps): string
14+
{
15+
$debug = "Process log for workflow '$workflowName':" . PHP_EOL;
16+
17+
foreach ($steps as $stage => $stageSteps) {
18+
$debug .= "$stage:" . PHP_EOL;
19+
20+
foreach ($stageSteps as $step) {
21+
$debug .= ' - ' . $this->formatStep($step) . PHP_EOL;
22+
}
23+
}
24+
25+
return trim($debug);
26+
}
27+
28+
private function formatStep(Step $step): string
29+
{
30+
$stepLog = "{$step->getDescription()}: {$step->getState()}" .
31+
($step->getReason() ? " ({$step->getReason()})" : '') .
32+
($step->getWarnings()
33+
? " ({$step->getWarnings()} warning" . ($step->getWarnings() > 1 ? 's' : '') . ")"
34+
: ''
35+
);
36+
37+
foreach ($step->getStepInfo() as $info) {
38+
$stepLog .= PHP_EOL . " - " . $this->formatInfo($info);
39+
}
40+
41+
return $stepLog;
42+
}
43+
44+
private function formatInfo(StepInfo $info): string
45+
{
46+
switch ($info->getInfo()) {
47+
case StepInfo::NESTED_WORKFLOW:
48+
/** @var WorkflowResult $nestedWorkflowResult */
49+
$nestedWorkflowResult = $info->getContext()['result'];
50+
51+
return str_replace(
52+
PHP_EOL . ' ' . PHP_EOL,
53+
PHP_EOL . PHP_EOL,
54+
str_replace(PHP_EOL, PHP_EOL . ' ', $nestedWorkflowResult->debug($this)),
55+
);
56+
default: return $info->getInfo();
57+
}
58+
}
59+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State\ExecutionLog\OutputFormat;
6+
7+
use Exception;
8+
use PHPWorkflow\State\ExecutionLog\ExecutionLog;
9+
use PHPWorkflow\State\ExecutionLog\Step;
10+
use PHPWorkflow\State\ExecutionLog\StepInfo;
11+
12+
class WorkflowGraph implements OutputFormat
13+
{
14+
private string $path;
15+
16+
public function __construct(string $path)
17+
{
18+
$this->path = $path;
19+
}
20+
21+
public function format(string $workflowName, array $steps): string
22+
{
23+
$dotScript = "digraph \"$workflowName\" {\n";
24+
$stepIndex = 0;
25+
$stageIndex = 0;
26+
27+
$dotScript .= sprintf(" %s [label=\"$workflowName\"]\n", $stepIndex++);
28+
foreach ($steps as $stage => $stageSteps) {
29+
$dotScript .= sprintf(" subgraph cluster_%s {\n label = $stage", $stageIndex++);
30+
/** @var Step $step */
31+
foreach ($stageSteps as $step) {
32+
$dotScript .= sprintf(
33+
' %s [label=%s shape="box" color="%s"]' . "\n",
34+
$stepIndex++,
35+
"<{$step->getDescription()} ({$step->getState()})"
36+
. ($step->getReason() ? "<BR/><FONT POINT-SIZE=\"10\">{$step->getReason()}</FONT>" : '')
37+
. join('', array_map(
38+
fn (StepInfo $info): string => "<BR/><FONT POINT-SIZE=\"10\">{$info->getInfo()}</FONT>",
39+
$step->getStepInfo(),
40+
))
41+
. ">",
42+
$this->mapColor($step),
43+
);
44+
}
45+
$dotScript .= " }\n";
46+
}
47+
48+
for ($i = 0; $i < $stepIndex - 1; $i++) {
49+
$dotScript .= sprintf(" %s -> %s\n", $i, $i + 1);
50+
}
51+
$dotScript .= '}';
52+
53+
$this->generateImageFromScript(
54+
$dotScript,
55+
$filePath = $this->path . DIRECTORY_SEPARATOR . $workflowName . '_' . uniqid() . '.svg',
56+
);
57+
58+
return $filePath;
59+
}
60+
61+
private function generateImageFromScript(string $script, string $file)
62+
{
63+
$tmp = tempnam(sys_get_temp_dir(), 'graphviz');
64+
if ($tmp === false) {
65+
throw new Exception('Unable to get temporary file name for graphviz script');
66+
}
67+
68+
$ret = file_put_contents($tmp, $script, LOCK_EX);
69+
if ($ret === false) {
70+
throw new Exception('Unable to write graphviz script to temporary file');
71+
}
72+
73+
$ret = 0;
74+
75+
system('dot -T svg ' . escapeshellarg($tmp) . ' -o ' . escapeshellarg($file), $ret);
76+
if ($ret !== 0) {
77+
throw new Exception('Unable to invoke "dot" to create image file (code ' . $ret . ')');
78+
}
79+
80+
unlink($tmp);
81+
}
82+
83+
private function mapColor(Step $step): string
84+
{
85+
if ($step->getState() === ExecutionLog::STATE_SUCCESS && $step->getWarnings()) {
86+
return 'yellow';
87+
}
88+
89+
return [
90+
ExecutionLog::STATE_SUCCESS => 'green',
91+
ExecutionLog::STATE_SKIPPED => 'grey',
92+
ExecutionLog::STATE_FAILED => 'red',
93+
][$step->getState()];
94+
}
95+
}

src/State/ExecutionLog/Step.php

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
class Step
88
{
9-
private string $step;
9+
private Describable $step;
1010
private string $state;
1111
private ?string $reason;
1212
private array $stepInfo;
1313
private int $warnings;
1414

15-
public function __construct(string $step, string $state, ?string $reason, array $stepInfo, int $warnings)
15+
public function __construct(Describable $step, string $state, ?string $reason, array $stepInfo, int $warnings)
1616
{
1717
$this->step = $step;
1818
$this->state = $state;
@@ -21,16 +21,31 @@ public function __construct(string $step, string $state, ?string $reason, array
2121
$this->warnings = $warnings;
2222
}
2323

24-
public function __toString(): string
24+
public function getDescription(): string
2525
{
26-
$stepLog = "{$this->step}: {$this->state}" .
27-
($this->reason ? " ({$this->reason})" : '') .
28-
($this->warnings ? " ({$this->warnings} warning" . ($this->warnings > 1 ? 's' : '') . ")" : '');
26+
return $this->step->getDescription();
27+
}
28+
29+
public function getState(): string
30+
{
31+
return $this->state;
32+
}
33+
34+
public function getReason(): ?string
35+
{
36+
return $this->reason;
37+
}
2938

30-
foreach ($this->stepInfo as $info) {
31-
$stepLog .= "\n - $info";
32-
}
39+
/**
40+
* @return StepInfo[]
41+
*/
42+
public function getStepInfo(): array
43+
{
44+
return $this->stepInfo;
45+
}
3346

34-
return $stepLog;
47+
public function getWarnings(): int
48+
{
49+
return $this->warnings;
3550
}
3651
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State\ExecutionLog;
6+
7+
class StepInfo
8+
{
9+
public const NESTED_WORKFLOW = 'STEP_NESTED_WORKFLOW';
10+
public const LOOP = 'STEP_LOOP';
11+
12+
private string $info;
13+
private array $context;
14+
15+
public function __construct(string $info, array $context)
16+
{
17+
$this->info = $info;
18+
$this->context = $context;
19+
}
20+
21+
public function getInfo(): string
22+
{
23+
return $this->info;
24+
}
25+
26+
public function getContext(): array
27+
{
28+
return $this->context;
29+
}
30+
}

0 commit comments

Comments
 (0)