Skip to content

Commit 0b1ba18

Browse files
authored
Merge pull request #26 from homiedopie/feature/PHP-66-PHP-23-error-mask-explode
PHP-66, PHP-23 - Add error message stack trace masking and explode issue
2 parents a6a2c29 + 9e1173b commit 0b1ba18

15 files changed

Lines changed: 625 additions & 8 deletions

src/Stackify/Log/Builder/MessageBuilder.php

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
use Stackify\Log\Entities\Api\LogMsgGroup;
1313
use Stackify\Log\Entities\Agent\AgentMessage;
1414
use Stackify\Exceptions\InitializationException;
15+
use Stackify\Log\Filters\LogMsg\ErrorStackTraceMaskFilter;
16+
use Stackify\Log\Filters\LogMsgFilterable;
17+
use Stackify\Log\Transport\Config\Agent;
1518

1619
class MessageBuilder implements BuilderInterface
1720
{
@@ -20,8 +23,20 @@ class MessageBuilder implements BuilderInterface
2023
protected $appName;
2124
protected $environmentName;
2225
protected $logServerVariables;
26+
/**
27+
* Log message filters
28+
*
29+
* @var LogMsgFilterable[]
30+
*/
31+
protected $logMessageFilters;
32+
/**
33+
* Agent config
34+
*
35+
* @var Agent
36+
*/
37+
protected $agentConfig;
2338

24-
public function __construct($loggerName, $appName, $environmentName = null, $logServerVariables = false)
39+
public function __construct($loggerName, $appName, $environmentName = null, $logServerVariables = false, $config = null)
2540
{
2641
if (!function_exists('json_encode')) {
2742
throw new InitializationException('JSON extension is required for Stackify logger');
@@ -32,6 +47,10 @@ public function __construct($loggerName, $appName, $environmentName = null, $log
3247
$this->logServerVariables = $logServerVariables;
3348
// set state for environment details
3449
EnvironmentDetail::getInstance()->init($appName, $environmentName);
50+
$this->agentConfig = !empty($config) ? $config : Agent::getInstance();
51+
$this->logMessageFilters = array();
52+
53+
$this->addLogMessageFiltersFromConfig();
3554
}
3655

3756
public function getAgentMessage(LogMsg $logMsg)
@@ -64,6 +83,7 @@ public function createLogMsg(LogEntryInterface $logEntry)
6483
$logEntry->getMessage(),
6584
$logEntry->getMilliseconds()
6685
);
86+
6787
$errorWrapper = null;
6888
if ($exception = $logEntry->getException()) {
6989
$errorWrapper = new ErrorWrapper($exception);
@@ -72,16 +92,19 @@ public function createLogMsg(LogEntryInterface $logEntry)
7292
} elseif ($logEntry->isErrorLevel()) {
7393
$errorWrapper = new ErrorWrapper($logEntry);
7494
}
95+
7596
if (null !== $errorWrapper) {
7697
$error = new StackifyError($this->appName, $this->environmentName, $this->logServerVariables);
7798
$error->OccurredEpochMillis = $logEntry->getMilliseconds();
7899
$error->Error = $this->getErrorItem($errorWrapper);
79100
$logMsg->setError($error);
80101
}
102+
81103
if (null !== $logEntry->getContext()) {
82104
$logMsg->data = $this->encodeJSON($logEntry->getContext());
83105
}
84-
return $logMsg;
106+
107+
return $this->filterMessage($logMsg);
85108
}
86109

87110
/**
@@ -127,4 +150,78 @@ protected function validateNotEmpty($name, $value)
127150
return $result;
128151
}
129152

153+
/**
154+
* Filter log message
155+
*
156+
* @param LogMsg $logMsg Log message instance
157+
*
158+
* @return LogMsg
159+
*/
160+
protected function filterMessage(LogMsg $logMsg)
161+
{
162+
if (count($this->logMessageFilters)) {
163+
foreach ($this->logMessageFilters as $filterable) {
164+
$logMsg = $filterable->filter($logMsg);
165+
}
166+
}
167+
168+
return $logMsg;
169+
}
170+
171+
/**
172+
* Add log message filter
173+
*
174+
* @param string $filterClass Filter class string
175+
*
176+
* @return void
177+
*/
178+
protected function addLogMessageFilter($filterClass)
179+
{
180+
if (!is_string($filterClass)) {
181+
return;
182+
}
183+
184+
if (!class_exists($filterClass)) {
185+
return;
186+
}
187+
188+
if (!is_subclass_of($filterClass, LogMsgFilterable::class)) {
189+
return;
190+
}
191+
192+
$this->logMessageFilters[$filterClass] = new $filterClass();
193+
}
194+
195+
/**
196+
* Add log message filters from config
197+
*
198+
* @return void
199+
*/
200+
public function addLogMessageFiltersFromConfig()
201+
{
202+
if (!$this->agentConfig) {
203+
return;
204+
}
205+
206+
$configLogMessageFilters = $this->agentConfig->getLogMessageFilters();
207+
if ($configLogMessageFilters && count($configLogMessageFilters)) {
208+
foreach ($configLogMessageFilters as $filterClass) {
209+
$this->addLogMessageFilter($filterClass);
210+
}
211+
}
212+
213+
if ($this->agentConfig->isMaskErrorStackTraceArguments()) {
214+
$this->addLogMessageFilter(ErrorStackTraceMaskFilter::class);
215+
}
216+
}
217+
218+
/**
219+
* Get log message filters
220+
*
221+
* @return LogMsgFilterable[]
222+
*/
223+
public function getLogMessageFilters()
224+
{
225+
return $this->logMessageFilters;
226+
}
130227
}

src/Stackify/Log/Entities/Api/LogMsg.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,40 @@ public function setError(StackifyError $error)
7373
}
7474
}
7575
}
76-
76+
77+
/**
78+
* Get log message
79+
*
80+
* @return string
81+
*/
82+
public function getMessage()
83+
{
84+
return $this->Msg;
85+
}
86+
87+
/**
88+
* Set log message
89+
*
90+
* @param string $message New log message
91+
*
92+
* @return void
93+
*/
94+
public function setMessage($message)
95+
{
96+
if (!is_string($message)) {
97+
return;
98+
}
99+
100+
$this->Msg = $message;
101+
}
102+
103+
/**
104+
* Check if log message has error
105+
*
106+
* @return boolean
107+
*/
108+
public function hasError()
109+
{
110+
return $this->Ex && !empty($this->Ex);
111+
}
77112
}

src/Stackify/Log/Entities/Api/WebRequestDetail.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,12 @@ protected function getRequestUrl()
329329
$https = filter_input(INPUT_SERVER, 'HTTPS');
330330
$ssl = null !== $https && 'off' !== $https;
331331
$protocol = $ssl ? 'https' : 'http';
332-
list($url,) = explode('?', filter_input(INPUT_SERVER, 'REQUEST_URI'));
332+
$inputServerValue = filter_input(INPUT_SERVER, 'REQUEST_URI');
333+
$url = '';
334+
if (is_string($inputServerValue)) {
335+
list($url,) = explode('?', $inputServerValue);
336+
}
337+
333338
$serverName = filter_input(INPUT_SERVER, 'SERVER_NAME');
334339
if ($serverName && $url) {
335340
return "$protocol://$serverName" . $url;
@@ -359,4 +364,4 @@ protected function getHeaders($blacklist = null, $whitelist = null)
359364
return self::getRequestMap($headers, $blacklist, $whitelist);
360365
}
361366

362-
}
367+
}

src/Stackify/Log/Entities/ErrorWrapper.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,16 @@ private function getFullPathThatContains($searchPath)
168168
return $result;
169169
}
170170

171+
/**
172+
* Set error message
173+
*
174+
* @param string $message New error message
175+
*
176+
* @return void
177+
*/
178+
public function setMessage($message)
179+
{
180+
$this->message = $message;
181+
}
182+
171183
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Stackify\Log\Filters\LogMsg;
4+
5+
use Stackify\Log\Entities\Api\LogMsg;
6+
use Stackify\Log\Filters\LogMsgFilterable;
7+
8+
/**
9+
* Error Mask argument filter
10+
*/
11+
class ErrorStackTraceMaskFilter implements LogMsgFilterable
12+
{
13+
/**
14+
* Allows filtering of log messages before uploading
15+
*
16+
* @param LogMsg $logMsg Log message instance
17+
*
18+
* @return LogMsg
19+
*/
20+
public function filter(LogMsg $logMsg)
21+
{
22+
if (!$logMsg->hasError()) {
23+
return $logMsg;
24+
}
25+
26+
if (stripos($logMsg->getMessage(), "stack trace:\n") === false) {
27+
return $logMsg;
28+
}
29+
30+
$logMsg->setMessage(
31+
$this->mask($logMsg->getMessage())
32+
);
33+
34+
return $logMsg;
35+
}
36+
37+
/**
38+
* Masked php trace
39+
*
40+
* @param string $message Raw message
41+
*
42+
* @return void
43+
*/
44+
protected function mask($message)
45+
{
46+
if (!is_string($message)) {
47+
return $message;
48+
}
49+
50+
try {
51+
$delimeter = "\n";
52+
$hasObject = strpos($message, 'Object(') !== false; // Checks if we can detect an object(
53+
$messageParts = explode($delimeter, $message);
54+
55+
$patternObjectString = "/Object\\(([\\\w\\\\]|[^\\)])+\\)/";
56+
$pattern = "/(.*:(?:.*))(\\((?:\\'.*\\'|[\\d]|[\\w]|,|\\s|\\(|\\)|\\\\)+\\))/";
57+
$replacement = '$1()';
58+
$maskedList = array();
59+
60+
foreach ($messageParts as $part) {
61+
$newString = $part;
62+
if ($hasObject) {
63+
$newString = preg_replace($patternObjectString, '', $newString);
64+
}
65+
$maskedList[] = preg_replace($pattern, $replacement, $newString);
66+
}
67+
68+
return implode($delimeter, $maskedList);
69+
} catch (\Exception $ex) {
70+
return $message;
71+
}
72+
}
73+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Stackify\Log\Filters;
4+
5+
use Stackify\Log\Entities\Api\LogMsg;
6+
7+
/**
8+
* Log message filterable interface
9+
*/
10+
interface LogMsgFilterable
11+
{
12+
/**
13+
* Allows filtering of log messages before uploading
14+
*
15+
* @param LogMsg $logMsg Log message instance
16+
*
17+
* @return LogMsg
18+
*/
19+
public function filter(LogMsg $logMsg);
20+
}

0 commit comments

Comments
 (0)