1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace ApiClients\Middleware\Log; |
4
|
|
|
|
5
|
|
|
use ApiClients\Foundation\Middleware\Annotation\Last; |
6
|
|
|
use ApiClients\Foundation\Middleware\MiddlewareInterface; |
7
|
|
|
use Psr\Http\Message\RequestInterface; |
8
|
|
|
use Psr\Http\Message\ResponseInterface; |
9
|
|
|
use Psr\Log\LoggerInterface; |
10
|
|
|
use React\Promise\CancellablePromiseInterface; |
11
|
|
|
use Throwable; |
12
|
|
|
use function React\Promise\reject; |
13
|
|
|
use function React\Promise\resolve; |
14
|
|
|
|
15
|
|
|
class LoggerMiddleware implements MiddlewareInterface |
16
|
|
|
{ |
17
|
|
|
const REQUEST = 'request'; |
18
|
|
|
const RESPONSE = 'response'; |
19
|
|
|
const ERROR = 'error'; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var LoggerInterface |
23
|
|
|
*/ |
24
|
|
|
private $logger; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private $context = []; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* LogMiddleware constructor. |
33
|
|
|
* @param LoggerInterface $logger |
34
|
|
|
*/ |
35
|
|
|
public function __construct(LoggerInterface $logger) |
36
|
|
|
{ |
37
|
|
|
$this->logger = $logger; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @param RequestInterface $request |
42
|
|
|
* @param array $options |
43
|
|
|
* @return CancellablePromiseInterface |
44
|
|
|
* |
45
|
|
|
* @Last() |
46
|
|
|
*/ |
47
|
|
|
public function pre( |
48
|
|
|
RequestInterface $request, |
49
|
|
|
string $transactionId, |
50
|
|
|
array $options = [] |
51
|
|
|
): CancellablePromiseInterface { |
52
|
|
View Code Duplication |
if (!isset($options[self::class][Options::LEVEL]) && !isset($options[self::class][Options::ERROR_LEVEL])) { |
|
|
|
|
53
|
|
|
return resolve($request); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
$this->context[$transactionId][self::REQUEST]['method'] = $request->getMethod(); |
57
|
|
|
$this->context[$transactionId][self::REQUEST]['uri'] = (string)$request->getUri(); |
58
|
|
|
$this->context[$transactionId][self::REQUEST]['protocol_version'] = (string)$request->getProtocolVersion(); |
59
|
|
|
$ignoreHeaders = $options[self::class][Options::IGNORE_HEADERS] ?? []; |
60
|
|
|
$this->context[$transactionId] = $this->iterateHeaders( |
61
|
|
|
$this->context[$transactionId], |
62
|
|
|
self::REQUEST, |
63
|
|
|
$request->getHeaders(), |
64
|
|
|
$ignoreHeaders |
65
|
|
|
); |
66
|
|
|
|
67
|
|
|
return resolve($request); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @param ResponseInterface $response |
72
|
|
|
* @param array $options |
73
|
|
|
* @return CancellablePromiseInterface |
74
|
|
|
* |
75
|
|
|
* @Last() |
76
|
|
|
*/ |
77
|
|
|
public function post( |
78
|
|
|
ResponseInterface $response, |
79
|
|
|
string $transactionId, |
80
|
|
|
array $options = [] |
81
|
|
|
): CancellablePromiseInterface { |
82
|
|
|
if (!isset($this->context[$transactionId])) { |
83
|
|
|
return resolve($response); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
$context = $this->context[$transactionId]; |
87
|
|
View Code Duplication |
if (!isset($options[self::class][Options::LEVEL]) && !isset($options[self::class][Options::ERROR_LEVEL])) { |
|
|
|
|
88
|
|
|
unset($this->context[$transactionId]); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
if (!isset($options[self::class][Options::LEVEL])) { |
92
|
|
|
return resolve($response); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$message = 'Request ' . $transactionId . ' completed.'; |
96
|
|
|
|
97
|
|
|
$context = $this->addResponseToContext($context, $response, $options); |
98
|
|
|
|
99
|
|
|
$this->logger->log($options[self::class][Options::LEVEL], $message, $context); |
100
|
|
|
|
101
|
|
|
return resolve($response); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @param Throwable $throwable |
106
|
|
|
* @param array $options |
107
|
|
|
* @return CancellablePromiseInterface |
108
|
|
|
* |
109
|
|
|
* @Last() |
110
|
|
|
*/ |
111
|
|
|
public function error( |
112
|
|
|
Throwable $throwable, |
113
|
|
|
string $transactionId, |
114
|
|
|
array $options = [] |
115
|
|
|
): CancellablePromiseInterface { |
116
|
|
|
if (!isset($this->context[$transactionId])) { |
117
|
|
|
return reject($throwable); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$context = $this->context[$transactionId]; |
121
|
|
|
unset($this->context[$transactionId]); |
122
|
|
|
|
123
|
|
|
if (!isset($options[self::class][Options::ERROR_LEVEL])) { |
124
|
|
|
return reject($throwable); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$message = $throwable->getMessage(); |
128
|
|
|
|
129
|
|
|
$response = null; |
130
|
|
|
if (method_exists($throwable, 'getResponse')) { |
131
|
|
|
$response = $throwable->getResponse(); |
132
|
|
|
} |
133
|
|
|
if ($response instanceof ResponseInterface) { |
134
|
|
|
$context = $this->addResponseToContext($context, $response, $options); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$context[self::ERROR]['code'] = $throwable->getCode(); |
138
|
|
|
$context[self::ERROR]['file'] = $throwable->getFile(); |
139
|
|
|
$context[self::ERROR]['line'] = $throwable->getLine(); |
140
|
|
|
$context[self::ERROR]['trace'] = $throwable->getTraceAsString(); |
141
|
|
|
|
142
|
|
|
if (method_exists($throwable, 'getContext')) { |
143
|
|
|
$context[self::ERROR]['context'] = $throwable->getContext(); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
$this->logger->log($options[self::class][Options::ERROR_LEVEL], $message, $context); |
147
|
|
|
|
148
|
|
|
return reject($throwable); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* @param string $prefix |
153
|
|
|
* @param array $headers |
154
|
|
|
* @param array $ignoreHeaders |
155
|
|
|
*/ |
156
|
|
|
protected function iterateHeaders( |
157
|
|
|
array $context, |
158
|
|
|
string $prefix, |
159
|
|
|
array $headers, |
160
|
|
|
array $ignoreHeaders |
161
|
|
|
): array { |
162
|
|
|
foreach ($headers as $header => $value) { |
163
|
|
|
if (in_array($header, $ignoreHeaders)) { |
164
|
|
|
continue; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$context[$prefix]['headers'][$header] = $value; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $context; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
private function addResponseToContext( |
174
|
|
|
array $context, |
175
|
|
|
ResponseInterface $response, |
176
|
|
|
array $options |
177
|
|
|
): array { |
178
|
|
|
$context[self::RESPONSE]['status_code'] = $response->getStatusCode(); |
179
|
|
|
$context[self::RESPONSE]['status_reason'] = $response->getReasonPhrase(); |
180
|
|
|
$context[self::RESPONSE]['protocol_version'] = $response->getProtocolVersion(); |
181
|
|
|
$ignoreHeaders = $options[self::class][Options::IGNORE_HEADERS] ?? []; |
182
|
|
|
$context = $this->iterateHeaders( |
183
|
|
|
$context, |
184
|
|
|
self::RESPONSE, |
185
|
|
|
$response->getHeaders(), |
186
|
|
|
$ignoreHeaders |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
return $context; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.