1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace luya\traits; |
4
|
|
|
|
5
|
|
|
use Yii; |
6
|
|
|
use yii\helpers\Json; |
7
|
|
|
use Curl\Curl; |
8
|
|
|
use luya\helpers\Url; |
9
|
|
|
use luya\helpers\ObjectHelper; |
10
|
|
|
use luya\helpers\ArrayHelper; |
11
|
|
|
use yii\web\Application; |
12
|
|
|
use yii\web\HttpException; |
13
|
|
|
use yii\base\Exception; |
14
|
|
|
use yii\base\ErrorException; |
15
|
|
|
use luya\Boot; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* ErrorHandler trait to extend the renderException method with an api call if enabled. |
19
|
|
|
* |
20
|
|
|
* @author Basil Suter <[email protected]> |
21
|
|
|
* @since 1.0.0 |
22
|
|
|
*/ |
23
|
|
|
trait ErrorHandlerTrait |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var string The url of the error api without trailing slash. Make sure you have installed the error api |
27
|
|
|
* module on the requested api url (https://luya.io/guide/module/luyadev---luya-module-errorapi). |
28
|
|
|
* |
29
|
|
|
* An example when using the erroapi module, the url could look like this `https://luya.io/errorapi`. |
30
|
|
|
*/ |
31
|
|
|
public $api; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var boolean Enable the transfer of exceptions to the defined `$api` server. |
35
|
|
|
*/ |
36
|
|
|
public $transferException = false; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var \Curl\Curl|null The curl object from the last error api call. |
40
|
|
|
* @since 1.0.5 |
41
|
|
|
*/ |
42
|
|
|
public $lastTransferCall; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var array An array of exceptions which are whitelisted and therefore NOT TRANSFERED. |
46
|
|
|
* @since 1.0.5 |
47
|
|
|
*/ |
48
|
|
|
public $whitelist = ['yii\web\NotFoundHttpException']; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var array |
52
|
|
|
* @since 1.0.6 |
53
|
|
|
*/ |
54
|
|
|
public $sensitiveKeys = ['password', 'pwd', 'pass', 'passwort', 'pw', 'token', 'hash', 'authorization']; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Send a custom message to the api server event its not related to an exception. |
58
|
|
|
* |
59
|
|
|
* Sometimes you just want to pass informations from your application, this method allows you to transfer |
60
|
|
|
* a message to the error api server. |
61
|
|
|
* |
62
|
|
|
* Example of sending a message |
63
|
|
|
* |
64
|
|
|
* ```php |
65
|
|
|
* Yii::$app->errorHandler->transferMessage('Something went wrong here!', __FILE__, __LINE__); |
66
|
|
|
* ``` |
67
|
|
|
* |
68
|
|
|
* @param string $message The message you want to send to the error api server. |
69
|
|
|
* @param string $file The you are currently send the message (use __FILE__) |
70
|
|
|
* @param string $line The line you want to submit (use __LINE__) |
71
|
|
|
* @return bool|null |
72
|
|
|
*/ |
73
|
|
|
public function transferMessage($message, $file = __FILE__, $line = __LINE__) |
74
|
|
|
{ |
75
|
|
|
return $this->apiServerSendData($this->getExceptionArray([ |
76
|
|
|
'message' => $message, |
77
|
|
|
'file' => $file, |
78
|
|
|
'line' => $line, |
79
|
|
|
])); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Send the array data to the api server. |
84
|
|
|
* |
85
|
|
|
* @param array $data The array to be sent to the server. |
86
|
|
|
* @return boolean|null true/false if data has been sent to the api successfull or not, null if the transfer is disabled. |
87
|
|
|
*/ |
88
|
|
|
private function apiServerSendData(array $data) |
89
|
|
|
{ |
90
|
|
|
if ($this->transferException) { |
91
|
|
|
$curl = new Curl(); |
92
|
|
|
$curl->setOpt(CURLOPT_CONNECTTIMEOUT, 2); |
93
|
|
|
$curl->setOpt(CURLOPT_TIMEOUT, 2); |
94
|
|
|
$curl->post(Url::ensureHttp(rtrim($this->api, '/')).'/create', [ |
95
|
|
|
'error_json' => Json::encode($data), |
96
|
|
|
]); |
97
|
|
|
|
98
|
|
|
$this->lastTransferCall = $curl; |
99
|
|
|
|
100
|
|
|
return $curl->isSuccess(); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
return null; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @inheritdoc |
108
|
|
|
*/ |
109
|
|
|
public function renderException($exception) |
110
|
|
|
{ |
111
|
|
|
if (!ObjectHelper::isInstanceOf($exception, $this->whitelist, false) && $this->transferException) { |
112
|
|
|
$this->apiServerSendData($this->getExceptionArray($exception)); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
return parent::renderException($exception); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Get an readable array to transfer from an exception |
120
|
|
|
* |
121
|
|
|
* @param mixed $exception Exception object |
122
|
|
|
* @return array An array with transformed exception data |
123
|
|
|
*/ |
124
|
|
|
public function getExceptionArray($exception) |
125
|
|
|
{ |
126
|
|
|
$_message = 'Uknonwn exception object, not instance of \Exception.'; |
127
|
|
|
$_file = 'unknown'; |
128
|
|
|
$_line = 0; |
129
|
|
|
$_trace = []; |
130
|
|
|
$_previousException = []; |
131
|
|
|
$_exceptionClassName = 'Unknown'; |
132
|
|
|
|
133
|
|
|
if (is_object($exception)) { |
134
|
|
|
$_exceptionClassName = get_class($exception); |
135
|
|
|
$prev = $exception->getPrevious(); |
136
|
|
|
|
137
|
|
|
if (!empty($prev)) { |
138
|
|
|
$_previousException = [ |
139
|
|
|
'message' => $prev->getMessage(), |
140
|
|
|
'file' => $prev->getFile(), |
141
|
|
|
'line' => $prev->getLine(), |
142
|
|
|
'trace' => $this->buildTrace($prev), |
143
|
|
|
]; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
$_trace = $this->buildTrace($exception); |
147
|
|
|
$_message = $exception->getMessage(); |
148
|
|
|
$_file = $exception->getFile(); |
149
|
|
|
$_line = $exception->getLine(); |
150
|
|
|
} elseif (is_string($exception)) { |
151
|
|
|
$_message = 'exception string: ' . $exception; |
152
|
|
|
} elseif (is_array($exception)) { |
153
|
|
|
$_message = isset($exception['message']) ? $exception['message'] : 'exception array dump: ' . print_r($exception, true); |
154
|
|
|
$_file = isset($exception['file']) ? $exception['file'] : __FILE__; |
155
|
|
|
$_line = isset($exception['line']) ? $exception['line'] : __LINE__; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
$exceptionName = 'Exception'; |
159
|
|
|
|
160
|
|
|
if ($exception instanceof Exception) { |
161
|
|
|
$exceptionName = $exception->getName(); |
162
|
|
|
} elseif ($exception instanceof ErrorException) { |
163
|
|
|
$exceptionName = $exception->getName(); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return [ |
167
|
|
|
'message' => $_message, |
168
|
|
|
'file' => $_file, |
169
|
|
|
'line' => $_line, |
170
|
|
|
'requestUri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null, |
171
|
|
|
'serverName' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null, |
172
|
|
|
'date' => date('d.m.Y H:i'), |
173
|
|
|
'trace' => $_trace, |
174
|
|
|
'previousException' => $_previousException, |
175
|
|
|
'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, |
176
|
|
|
'get' => isset($_GET) ? ArrayHelper::coverSensitiveValues($_GET, $this->sensitiveKeys) : [], |
177
|
|
|
'post' => isset($_POST) ? ArrayHelper::coverSensitiveValues($_POST, $this->sensitiveKeys) : [], |
178
|
|
|
'bodyParams' => Yii::$app instanceof Application ? ArrayHelper::coverSensitiveValues(Yii::$app->request->bodyParams) : [], |
179
|
|
|
'session' => isset($_SESSION) ? ArrayHelper::coverSensitiveValues($_SESSION, $this->sensitiveKeys) : [], |
180
|
|
|
'server' => isset($_SERVER) ? ArrayHelper::coverSensitiveValues($_SERVER, $this->sensitiveKeys) : [], |
181
|
|
|
// since 1.0.20 |
182
|
|
|
'yii_debug' => YII_DEBUG, |
183
|
|
|
'yii_env' => YII_ENV, |
184
|
|
|
'status_code' => $exception instanceof HttpException ? $exception->statusCode : 500, |
185
|
|
|
'exception_name' => $exceptionName, |
186
|
|
|
'exception_class_name' => $_exceptionClassName, |
187
|
|
|
'php_version' => phpversion(), |
188
|
|
|
'luya_version' => Boot::VERSION, |
189
|
|
|
]; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Build trace array from exception. |
194
|
|
|
* |
195
|
|
|
* @param object $exception |
196
|
|
|
* @return array |
197
|
|
|
*/ |
198
|
|
|
private function buildTrace($exception) |
199
|
|
|
{ |
200
|
|
|
$_trace = []; |
201
|
|
|
foreach ($exception->getTrace() as $key => $item) { |
202
|
|
|
$_trace[$key] = $this->buildTraceItem($item); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
return $_trace; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
private function buildTraceItem(array $item) |
209
|
|
|
{ |
210
|
|
|
$file = isset($item['file']) ? $item['file'] : null; |
211
|
|
|
$line = isset($item['line']) ? $item['line'] : null; |
212
|
|
|
$contextLine = null; |
213
|
|
|
$preContext = []; |
214
|
|
|
$postContext = []; |
215
|
|
|
|
216
|
|
|
try { |
217
|
|
|
$lineInArray = $line -1; |
218
|
|
|
// load file from path |
219
|
|
|
$fileInfo = file($file, FILE_IGNORE_NEW_LINES); |
220
|
|
|
// load file if false from real path |
221
|
|
|
if (!$fileInfo) { |
|
|
|
|
222
|
|
|
$fileInfo = file(realpath($file), FILE_IGNORE_NEW_LINES); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if ($fileInfo && isset($fileInfo[$lineInArray])) { |
|
|
|
|
226
|
|
|
$contextLine = $fileInfo[$lineInArray]; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ($contextLine) { |
230
|
|
|
for ($i = 1; $i <= 6; $i++) { |
231
|
|
|
|
232
|
|
|
// pre context |
233
|
|
|
if (isset($fileInfo[$lineInArray - $i])) { |
234
|
|
|
$preContext[] = $fileInfo[$lineInArray - $i]; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
// post context |
238
|
|
|
if (isset($fileInfo[$i + $lineInArray])) { |
239
|
|
|
$postContext[] = $fileInfo[$i + $lineInArray]; |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$preContext = array_reverse($preContext); |
244
|
|
|
} |
245
|
|
|
} catch (\Exception $e) { |
246
|
|
|
|
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
unset($fileInfo); |
250
|
|
|
|
251
|
|
|
return [ |
252
|
|
|
'file' => $file, |
253
|
|
|
'abs_path' => realpath($file), |
254
|
|
|
'line' => $line, |
255
|
|
|
'context_line' => $contextLine, |
256
|
|
|
'pre_context' => $preContext, |
257
|
|
|
'post_context' => $postContext, |
258
|
|
|
'function' => isset($item['function']) ? $item['function'] : null, |
259
|
|
|
'class' => isset($item['class']) ? $item['class'] : null, |
260
|
|
|
// currently arguments wont be transmited due to large amount of informations based on base object |
261
|
|
|
//'args' => isset($item['args']) ? ArrayHelper::coverSensitiveValues($item['args'], $this->sensitiveKeys) : [], |
262
|
|
|
]; |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.