1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Concat\Http\Middleware; |
4
|
|
|
|
5
|
|
|
use GuzzleHttp\Exception\RequestException; |
6
|
|
|
use GuzzleHttp\MessageFormatter; |
7
|
|
|
use GuzzleHttp\Promise; |
8
|
|
|
use Psr\Http\Message\RequestInterface; |
9
|
|
|
use Psr\Http\Message\ResponseInterface; |
10
|
|
|
use Psr\Log\LogLevel; |
11
|
|
|
use Psr\Log\LoggerInterface; |
12
|
|
|
|
13
|
|
|
use InvalidArgumentException; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Guzzle middleware which logs a request and its response. |
17
|
|
|
*/ |
18
|
|
|
class Logger |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* @var \Psr\Log\LoggerInterface|callable |
22
|
|
|
*/ |
23
|
|
|
protected $logger; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var \GuzzleHttp\MessageFormatter|callable |
27
|
|
|
*/ |
28
|
|
|
protected $formatter; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var string|callable Constant or callable that accepts a Response. |
32
|
|
|
*/ |
33
|
|
|
protected $logLevel; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var boolean Whether or not to log requests as they are made. |
37
|
|
|
*/ |
38
|
|
|
protected $logRequests; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Creates a callable middleware for logging requests and responses. |
42
|
|
|
* |
43
|
|
|
* @param LoggerInterface|callable $logger |
44
|
|
|
* @param string|callable Constant or callable that accepts a Response. |
45
|
|
|
*/ |
46
|
12 |
|
public function __construct($logger, $formatter = null) |
47
|
|
|
{ |
48
|
|
|
// Use the setters to take care of type validation |
49
|
12 |
|
$this->setLogger($logger); |
50
|
11 |
|
$this->setFormatter($formatter ?: $this->getDefaultFormatter()); |
51
|
11 |
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Returns the default formatter; |
55
|
|
|
* |
56
|
|
|
* @return MessageFormatter |
57
|
|
|
*/ |
58
|
10 |
|
protected function getDefaultFormatter() |
59
|
|
|
{ |
60
|
10 |
|
return new MessageFormatter(); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Sets whether requests should be logged before the response is received. |
65
|
|
|
* |
66
|
|
|
* @param boolean $logRequests |
67
|
|
|
*/ |
68
|
2 |
|
public function setRequestLoggingEnabled($logRequests = true) |
69
|
|
|
{ |
70
|
2 |
|
$this->logRequests = (bool) $logRequests; |
71
|
2 |
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Sets the logger, which can be a PSR-3 logger or a callable that accepts |
75
|
|
|
* a log level, message, and array context. |
76
|
|
|
* |
77
|
|
|
* @param LoggerInterface|callable $logger |
78
|
|
|
* |
79
|
|
|
* @throws InvalidArgumentException |
80
|
|
|
*/ |
81
|
12 |
|
public function setLogger($logger) |
82
|
|
|
{ |
83
|
12 |
|
if ($logger instanceof LoggerInterface || is_callable($logger)) { |
84
|
11 |
|
$this->logger = $logger; |
|
|
|
|
85
|
|
|
} else { |
86
|
1 |
|
throw new InvalidArgumentException( |
87
|
1 |
|
"Logger has to be a Psr\Log\LoggerInterface or callable" |
88
|
|
|
); |
89
|
|
|
} |
90
|
11 |
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Sets the formatter, which can be a MessageFormatter or callable that |
94
|
|
|
* accepts a request, response, and a reason if an error has occurred. |
95
|
|
|
* |
96
|
|
|
* @param MessageFormatter|callable $formatter |
97
|
|
|
* |
98
|
|
|
* @throws InvalidArgumentException |
99
|
|
|
*/ |
100
|
11 |
|
public function setFormatter($formatter) |
101
|
|
|
{ |
102
|
11 |
|
if ($formatter instanceof MessageFormatter || is_callable($formatter)) { |
103
|
11 |
|
$this->formatter = $formatter; |
104
|
|
|
} else { |
105
|
1 |
|
throw new InvalidArgumentException( |
106
|
1 |
|
"Formatter has to be a \GuzzleHttp\MessageFormatter or callable" |
107
|
|
|
); |
108
|
|
|
} |
109
|
11 |
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Sets the log level to use, which can be either a string or a callable |
113
|
|
|
* that accepts a response (which could be null). A log level could also |
114
|
|
|
* be null, which indicates that the default log level should be used. |
115
|
|
|
* |
116
|
|
|
* @param string|callable|null |
117
|
|
|
*/ |
118
|
2 |
|
public function setLogLevel($logLevel) |
119
|
|
|
{ |
120
|
2 |
|
$this->logLevel = $logLevel; |
121
|
2 |
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Logs a request and/or a response. |
125
|
|
|
* |
126
|
|
|
* @param RequestInterface $request |
127
|
|
|
* @param ResponseInterface|null $response |
128
|
|
|
* @param mixed $reason |
129
|
|
|
*/ |
130
|
|
|
protected function log( |
131
|
|
|
RequestInterface $request, |
132
|
|
|
ResponseInterface $response = null, |
133
|
|
|
$reason = null |
134
|
|
|
) { |
135
|
|
|
if ($reason instanceof RequestException) { |
136
|
|
|
$response = $reason->getResponse(); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$level = $this->getLogLevel($response); |
140
|
|
|
$message = $this->getLogMessage($request, $response, $reason); |
141
|
|
|
$context = compact('request', 'response', 'reason'); |
142
|
|
|
|
143
|
|
|
// Make sure that the content of the body is available again. |
144
|
|
|
if ($response) { |
145
|
|
|
$response->getBody()->seek(0);; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
if (is_callable($this->logger)) { |
149
|
|
|
return call_user_func($this->logger, $level, $message, $context); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
return $this->logger->log($level, $message, $context); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Formats a request and response as a log message. |
157
|
|
|
* |
158
|
|
|
* @param RequestInterface $request |
159
|
|
|
* @param ResponseInterface|null $response |
160
|
|
|
* @param mixed $reason |
161
|
|
|
* |
162
|
|
|
* @return string The formatted message. |
163
|
|
|
*/ |
164
|
|
|
protected function getLogMessage( |
165
|
|
|
RequestInterface $request, |
166
|
|
|
ResponseInterface $response = null, |
167
|
|
|
$reason = null |
168
|
|
|
) { |
169
|
|
|
if ($this->formatter instanceof MessageFormatter) { |
170
|
|
|
return $this->formatter->format( |
171
|
|
|
$request, |
172
|
|
|
$response, |
173
|
|
|
$reason |
174
|
|
|
); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return call_user_func($this->formatter, $request, $response, $reason); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Returns a log level for a given response. |
182
|
|
|
* |
183
|
|
|
* @param ResponseInterface $response The response being logged. |
184
|
|
|
* |
185
|
|
|
* @return string LogLevel |
186
|
|
|
*/ |
187
|
|
|
protected function getLogLevel(ResponseInterface $response = null) |
188
|
|
|
{ |
189
|
|
|
if ( ! $this->logLevel) { |
190
|
|
|
return $this->getDefaultLogLevel($response); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
if (is_callable($this->logLevel)) { |
194
|
|
|
return call_user_func($this->logLevel, $response); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return (string) $this->logLevel; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Returns the default log level for a response. |
202
|
|
|
* |
203
|
|
|
* @param ResponseInterface $response |
204
|
|
|
* |
205
|
|
|
* @return string LogLevel |
206
|
|
|
*/ |
207
|
|
|
protected function getDefaultLogLevel(ResponseInterface $response = null) { |
208
|
|
|
if ($response && $response->getStatusCode() >= 300) { |
209
|
|
|
return LogLevel::NOTICE; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return LogLevel::INFO; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Returns a function which is handled when a request was successful. |
217
|
|
|
* |
218
|
|
|
* @param RequestInterface $request |
219
|
|
|
* |
220
|
|
|
* @return Closure |
221
|
|
|
*/ |
222
|
|
|
protected function onSuccess(RequestInterface $request) |
223
|
|
|
{ |
224
|
|
|
return function ($response) use ($request) { |
225
|
|
|
$this->log($request, $response); |
226
|
|
|
return $response; |
227
|
|
|
}; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Returns a function which is handled when a request was rejected. |
232
|
|
|
* |
233
|
|
|
* @param RequestInterface $request |
234
|
|
|
* |
235
|
|
|
* @return Closure |
236
|
|
|
*/ |
237
|
|
|
protected function onFailure(RequestInterface $request) |
238
|
|
|
{ |
239
|
|
|
return function ($reason) use ($request) { |
240
|
|
|
|
241
|
|
|
// Only log a rejected request if it hasn't already been logged. |
242
|
|
|
if ( ! $this->logRequests) { |
243
|
|
|
$this->log($request, null, $reason); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return Promise\rejection_for($reason); |
|
|
|
|
247
|
|
|
}; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Called when the middleware is handled by the client. |
252
|
|
|
* |
253
|
|
|
* @param callable $handler |
254
|
|
|
* |
255
|
|
|
* @return Closure |
256
|
|
|
*/ |
257
|
|
|
public function __invoke(callable $handler) |
258
|
|
|
{ |
259
|
|
|
return function ($request, array $options) use ($handler) { |
260
|
|
|
|
261
|
|
|
// Only log requests if explicitly set to do so |
262
|
|
|
if ($this->logRequests) { |
263
|
|
|
$this->log($request); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return $handler($request, $options)->then( |
267
|
|
|
$this->onSuccess($request), |
268
|
|
|
$this->onFailure($request) |
269
|
|
|
); |
270
|
|
|
}; |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.