44
55namespace Temporal \Tests \Acceptance \App ;
66
7+ use Google \Protobuf \Timestamp ;
78use Psr \Log \LoggerInterface ;
89use Spiral \Core \Container ;
910use Spiral \Core \Scope ;
11+ use Temporal \Api \Enums \V1 \EventType ;
12+ use Temporal \Api \Failure \V1 \Failure ;
13+ use Temporal \Client \WorkflowClientInterface ;
1014use Temporal \Client \WorkflowStubInterface ;
1115use Temporal \Tests \Acceptance \App \Logger \ClientLogger ;
1216use Temporal \Tests \Acceptance \App \Logger \LoggerFactory ;
@@ -59,6 +63,8 @@ function (Container $container): mixed {
5963 $ runner ->stop ();
6064 $ runner ->start ();
6165
66+ $ this ->printWorkflowException ($ container ->get (WorkflowClientInterface::class), $ args );
67+
6268 throw $ e ;
6369 } finally {
6470 // Cleanup: terminate injected workflow if any
@@ -75,4 +81,74 @@ function (Container $container): mixed {
7581 },
7682 );
7783 }
84+
85+ private function printWorkflowException (WorkflowClientInterface $ workflowClient , array $ args ): ?\Throwable
86+ {
87+ foreach ($ args as $ arg ) {
88+ if (!$ arg instanceof WorkflowStubInterface) {
89+ continue ;
90+ }
91+
92+ $ fnTime = static fn (?Timestamp $ ts ): float => $ ts === null
93+ ? 0
94+ : $ ts ->getSeconds () + \round ($ ts ->getNanos () / 1_000_000_000 , 6 );
95+
96+ foreach ($ workflowClient ->getWorkflowHistory (
97+ $ arg ->getExecution (),
98+ ) as $ event ) {
99+ $ start ??= $ fnTime ($ event ->getEventTime ());
100+ echo "\n" . \str_pad ((string ) $ event ->getEventId (), 3 , ' ' , STR_PAD_LEFT ) . ' ' ;
101+ # Calculate delta time
102+ $ deltaMs = \round (1_000 * ($ fnTime ($ event ->getEventTime ()) - $ start ));
103+ echo \str_pad (\number_format ($ deltaMs , 0 , '. ' , "' " ), 6 , ' ' , STR_PAD_LEFT ) . 'ms ' ;
104+ echo \str_pad (EventType::name ($ event ->getEventType ()), 40 , ' ' , STR_PAD_RIGHT ) . ' ' ;
105+
106+ $ cause = $ event ->getStartChildWorkflowExecutionFailedEventAttributes ()?->getCause()
107+ ?? $ event ->getSignalExternalWorkflowExecutionFailedEventAttributes ()?->getCause()
108+ ?? $ event ->getRequestCancelExternalWorkflowExecutionFailedEventAttributes ()?->getCause();
109+ if ($ cause !== null ) {
110+ echo "Cause: $ cause " ;
111+ continue ;
112+ }
113+
114+ $ failure = $ event ->getActivityTaskFailedEventAttributes ()?->getFailure()
115+ ?? $ event ->getWorkflowTaskFailedEventAttributes ()?->getFailure()
116+ ?? $ event ->getNexusOperationFailedEventAttributes ()?->getFailure()
117+ ?? $ event ->getWorkflowExecutionFailedEventAttributes ()?->getFailure()
118+ ?? $ event ->getChildWorkflowExecutionFailedEventAttributes ()?->getFailure()
119+ ?? $ event ->getNexusOperationCancelRequestFailedEventAttributes ()?->getFailure();
120+
121+ if ($ failure === null ) {
122+ continue ;
123+ }
124+
125+ # Render failure
126+ echo "Failure: \n" ;
127+ echo "========== BEGIN =========== \n" ;
128+ $ this ->renderFailure ($ failure , 1 );
129+ echo "=========== END ============ \n" ;
130+ }
131+ }
132+
133+ return null ;
134+ }
135+
136+ private function renderFailure (Failure $ failure , int $ level ): void
137+ {
138+ $ fnPad = static function (string $ str ) use ($ level ): string {
139+ $ pad = \str_repeat (' ' , $ level );
140+ return $ pad . \str_replace ("\n" , "\n$ pad " , $ str );
141+ };
142+ echo $ fnPad ('Source: ' . $ failure ->getSource ()) . "\n" ;
143+ echo $ fnPad ('Info: ' . $ failure ->getFailureInfo ()) . "\n" ;
144+ echo $ fnPad ('Message: ' . $ failure ->getMessage ()) . "\n" ;
145+ echo $ fnPad ("Stack trace: " ) . "\n" ;
146+ echo $ fnPad ($ failure ->getStackTrace ()) . "\n" ;
147+ $ previous = $ failure ->getCause ();
148+ if ($ previous !== null ) {
149+ echo $ fnPad ('———————————————————————————— ' ) . "\n" ;
150+ echo $ fnPad ('Caused by: ' ) . "\n" ;
151+ $ this ->renderFailure ($ previous , $ level + 1 );
152+ }
153+ }
78154}
0 commit comments