Skip to content

Commit 3d26767

Browse files
authored
Bugfixes and refactoring (#38)
* Fixed passing additional data (tags, extra, etc) to exception handler Added passing user data * Updated tests for use global Yii object * Fixed passing extra data * Removed old levels * Fixed setting ip if ip is null * Updated e2e tests to test logging user id and ip * Sentry init moved to constructor * Updated tests for configureScope() check * Added note about additional context * Added some notes * Fixed typo * Updated changelog * Fixed version * Context configuring moved to usage * Restored old methods * Changed version * Restored tests for deprecated method * Bootstrap setting moved to root according with deprecation in codeception 3 * Added scripts to composer for run tests * Added messages for e2e test command * Remove return type for bc * Changed release date * Changed release date
1 parent 0623dfc commit 3d26767

11 files changed

+303
-107
lines changed

Diff for: .travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ install:
1616

1717
script:
1818
- composer exec codecept build -v
19-
- composer exec codecept run -v
19+
- composer test -v

Diff for: CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Change log
22

3+
## 1.5.0-beta - 2020-05-18
4+
### Fixed
5+
* Fix message level (debug, info, warning, error) translating to sentry.
6+
* Fix message scope. For now every message has own scope and not affect to other.
7+
* Fix adding additional data (extra context, user data) for exception messages.
8+
### Changed
9+
* Sentry init will be invoking at application start, and not before log export started.
10+
### Added
11+
* Log user ID and IP, if available.
12+
* Added ability to add own context data for messages scope.
13+
314
## 1.4.2 - 2020-01-21
415
### Fixed
516
* Array export fix if text not contains message key.

Diff for: README.md

+26
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,32 @@ Example:
8989

9090
More about tags see https://docs.sentry.io/learn/context/#tagging-events
9191

92+
### Additional context
93+
94+
You can add additional context (such as user information, fingerprint, etc) by calling `\Sentry\configureScope()` before logging.
95+
For example in main configuration on `beforeAction` event (real place will dependant on your project):
96+
```php
97+
return [
98+
// ...
99+
'on beforeAction' => function (\yii\base\ActionEvent $event) {
100+
/** @var \yii\web\User $user */
101+
$user = Yii::$app->has('user', true) ? Yii::$app->get('user', false) : null;
102+
if ($user && ($identity = $user->getIdentity(false))) {
103+
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($identity) {
104+
$scope->setUser([
105+
// User ID and IP will be added by logger automatically
106+
'username' => $identity->username,
107+
'email' => $identity->email,
108+
], true); // Don't forget to set second param of setUser to true for merging data
109+
});
110+
}
111+
112+
return $event->isValid;
113+
},
114+
// ...
115+
];
116+
```
117+
92118
## Log levels
93119

94120
Yii2 log levels converts to Sentry levels:

Diff for: codeception.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
actor: Tester
2+
bootstrap: bootstrap.php
23
paths:
34
tests: tests
45
log: tests/_output
56
data: tests/_data
67
support: tests/_support
78
envs: tests/_envs
89
settings:
9-
bootstrap: bootstrap.php
1010
colors: true
1111
memory_limit: 1024M
1212
extensions:

Diff for: composer.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@
3131
"type": "composer",
3232
"url": "https://asset-packagist.org"
3333
}
34-
]
34+
],
35+
"scripts": {
36+
"test": "codecept run",
37+
"e2e": "php tests/sentry-fill"
38+
}
3539
}

Diff for: src/SentryTarget.php

+94-51
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66

77
namespace notamedia\sentry;
88

9+
use Sentry\Severity;
10+
use Sentry\State\Scope;
11+
use Throwable;
12+
use Yii;
913
use yii\helpers\ArrayHelper;
1014
use yii\log\Logger;
1115
use yii\log\Target;
16+
use yii\web\Request;
17+
use yii\web\User;
1218

1319
/**
1420
* SentryTarget records log messages in a Sentry.
@@ -33,19 +39,15 @@ class SentryTarget extends Target
3339
* @var callable Callback function that can modify extra's array
3440
*/
3541
public $extraCallback;
36-
/**
37-
* @var \Sentry
38-
*/
39-
protected $client;
4042

4143
/**
42-
* @inheritdoc
44+
* @inheritDoc
4345
*/
44-
public function collect($messages, $final)
46+
public function __construct($config = [])
4547
{
46-
\Sentry\init(array_merge(['dsn' => $this->dsn], $this->clientOptions));
48+
parent::__construct($config);
4749

48-
parent::collect($messages, $final);
50+
\Sentry\init(array_merge(['dsn' => $this->dsn], $this->clientOptions));
4951
}
5052

5153
/**
@@ -62,69 +64,82 @@ protected function getContextMessage()
6264
public function export()
6365
{
6466
foreach ($this->messages as $message) {
65-
list($text, $level, $category, $timestamp, $traces) = $message;
67+
[$text, $level, $category] = $message;
6668

6769
$data = [
68-
'level' => static::getLevelName($level),
6970
'message' => '',
70-
'timestamp' => $timestamp,
71-
'tags' => ['category' => $category]
71+
'tags' => ['category' => $category],
72+
'extra' => [],
73+
'userData' => [],
7274
];
7375

74-
if ($text instanceof \Throwable || $text instanceof \Exception) {
75-
$data = $this->runExtraCallback($text, $data);
76-
\Sentry\captureException($text, $data);
77-
continue;
78-
} elseif (is_array($text)) {
79-
if (isset($text['msg'])) {
80-
$data['message'] = $text['msg'];
81-
unset($text['msg']);
82-
}
76+
$request = Yii::$app->getRequest();
77+
if ($request instanceof Request && $request->getUserIP()) {
78+
$data['userData']['ip_address'] = $request->getUserIP();
79+
}
8380

84-
if (isset($text['tags'])) {
85-
$data['tags'] = ArrayHelper::merge($data['tags'], $text['tags']);
86-
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($data): void {
87-
foreach ($data['tags'] as $key => $value) {
88-
$scope->setTag($key, $value);
89-
}
90-
});
91-
unset($text['tags']);
81+
try {
82+
/** @var User $user */
83+
$user = Yii::$app->has('user', true) ? Yii::$app->get('user', false) : null;
84+
if ($user && ($identity = $user->getIdentity(false))) {
85+
$data['userData']['id'] = $identity->getId();
86+
}
87+
} catch (Throwable $e) {}
88+
89+
\Sentry\withScope(function (Scope $scope) use ($text, $level, $data) {
90+
if (is_array($text)) {
91+
if (isset($text['msg'])) {
92+
$data['message'] = $text['msg'];
93+
unset($text['msg']);
94+
}
95+
96+
if (isset($text['tags'])) {
97+
$data['tags'] = ArrayHelper::merge($data['tags'], $text['tags']);
98+
unset($text['tags']);
99+
}
100+
101+
$data['extra'] = $text;
102+
} else {
103+
$data['message'] = (string) $text;
92104
}
93105

94-
$data['extra'] = $text;
95-
96-
if (!empty($data['extra'])) {
97-
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($data): void {
98-
foreach ($data['extra'] as $key => $value) {
99-
$scope->setExtra((string)$key, $value);
100-
}
101-
});
106+
if ($this->context) {
107+
$data['extra']['context'] = parent::getContextMessage();
102108
}
103-
104-
} else {
105-
$data['message'] = $text;
106-
}
107109

108-
if ($this->context) {
109-
$data['extra']['context'] = parent::getContextMessage();
110-
}
110+
$data = $this->runExtraCallback($text, $data);
111111

112-
$data = $this->runExtraCallback($text, $data);
113-
\Sentry\captureMessage($data['message']);
112+
$scope->setUser($data['userData'], true);
113+
foreach ($data['extra'] as $key => $value) {
114+
$scope->setExtra((string) $key, $value);
115+
}
116+
foreach ($data['tags'] as $key => $value) {
117+
if ($value) {
118+
$scope->setTag($key, $value);
119+
}
120+
}
121+
122+
if ($text instanceof Throwable) {
123+
\Sentry\captureException($text);
124+
} else {
125+
\Sentry\captureMessage($data['message'], $this->getLogLevel($level));
126+
}
127+
});
114128
}
115129
}
116130

117131
/**
118132
* Calls the extra callback if it exists
119133
*
120-
* @param $text
121-
* @param $data
134+
* @param mixed $text
135+
* @param array $data
136+
*
122137
* @return array
123138
*/
124139
public function runExtraCallback($text, $data)
125140
{
126141
if (is_callable($this->extraCallback)) {
127-
$data['extra'] = call_user_func($this->extraCallback, $text, isset($data['extra']) ? $data['extra'] : []);
142+
$data['extra'] = call_user_func($this->extraCallback, $text, $data['extra'] ?? []);
128143
}
129144

130145
return $data;
@@ -133,7 +148,10 @@ public function runExtraCallback($text, $data)
133148
/**
134149
* Returns the text display of the specified level for the Sentry.
135150
*
136-
* @param integer $level The message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]].
151+
* @deprecated Deprecated from 1.5, will remove in 2.0
152+
*
153+
* @param int $level The message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]].
154+
*
137155
* @return string
138156
*/
139157
public static function getLevelName($level)
@@ -147,6 +165,31 @@ public static function getLevelName($level)
147165
Logger::LEVEL_PROFILE_END => 'debug',
148166
];
149167

150-
return isset($levels[$level]) ? $levels[$level] : 'error';
168+
return $levels[$level] ?? 'error';
169+
}
170+
171+
/**
172+
* Translates Yii2 log levels to Sentry Severity.
173+
*
174+
* @param int $level
175+
*
176+
* @return Severity
177+
*/
178+
protected function getLogLevel($level): Severity
179+
{
180+
switch ($level) {
181+
case Logger::LEVEL_PROFILE:
182+
case Logger::LEVEL_PROFILE_BEGIN:
183+
case Logger::LEVEL_PROFILE_END:
184+
case Logger::LEVEL_TRACE:
185+
return Severity::debug();
186+
case Logger::LEVEL_WARNING:
187+
return Severity::warning();
188+
case Logger::LEVEL_ERROR:
189+
return Severity::error();
190+
case Logger::LEVEL_INFO:
191+
default:
192+
return Severity::info();
193+
}
151194
}
152195
}

Diff for: tests/commands/SentryController.php

+45-17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace tests\commands;
44

5+
use RuntimeException;
6+
use tests\models\User;
7+
use Yii;
58
use yii\console\Controller;
69
use yii\log\Logger;
710

@@ -13,16 +16,29 @@ class SentryController extends Controller
1316
public function actionFill()
1417
{
1518
/* @var $logger \yii\log\Logger */
16-
$logger = \Yii::createObject(Logger::class);
17-
\Yii::setLogger($logger);
18-
\Yii::$app->log->setLogger(\Yii::getLogger());
19+
$logger = Yii::createObject(Logger::class);
20+
Yii::setLogger($logger);
21+
Yii::$app->log->setLogger(Yii::getLogger());
1922

2023
foreach ($this->logsProvider() as $log) {
21-
\Yii::getLogger()->log($log['message'], $log['level'], $log['category']);
22-
unset($log);
23-
}
24+
Yii::$app->user->logout();
25+
if (isset($log['user'])) {
26+
Yii::$app->user->login($log['user']);
27+
}
28+
$_SERVER['REMOTE_ADDR'] = $log['ip'] ?? null;
29+
30+
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($log) {
31+
$scope->setUser([
32+
// configureScope modify global scope, so we revert changes from previous log message
33+
'username' => isset($log['user']) ? $log['user']->username : null,
34+
'email' => isset($log['user']) ? $log['user']->email : null,
35+
], true);
36+
});
2437

25-
\Yii::getLogger()->flush();
38+
Yii::getLogger()->log($log['message'], $log['level'], $log['category']);
39+
// We need to final flush logs for change ip and user on fly
40+
Yii::getLogger()->flush(true);
41+
}
2642
}
2743

2844
protected function logsProvider()
@@ -31,40 +47,52 @@ protected function logsProvider()
3147
[
3248
'level' => Logger::LEVEL_ERROR,
3349
'message' => [
34-
'msg' => new \RuntimeException('Connection error', 999, new \Exception),
50+
'msg' => new RuntimeException('Connection error', 999, new \Exception),
3551
'extra' => 'Hello, World!',
36-
'tags' => ['db-name' => 'bulling']
52+
'tags' => ['db-name' => 'bulling'],
3753
],
38-
'category' => 'dbms'
54+
'category' => 'dbms',
3955
],
4056
[
4157
'level' => Logger::LEVEL_ERROR,
42-
'message' => new \RuntimeException('Oops... This is exception.', 999, new \Exception),
43-
'category' => 'exceptions'
58+
'message' => new RuntimeException('Oops... This is exception.', 999, new \Exception),
59+
'category' => 'exceptions',
60+
'user' => new User([
61+
'id' => 47,
62+
'username' => 'Agent 47',
63+
'email' => '[email protected]',
64+
]),
65+
'ip' => '127.0.0.42',
4466
],
4567
[
4668
'level' => Logger::LEVEL_INFO,
4769
'message' => [
4870
'msg' => 'Message from bulling service',
4971
'extra' => 'Hello, World!',
50-
'tags' => ['currency' => 'RUB']
72+
'tags' => ['currency' => 'RUB'],
5173
],
52-
'category' => 'monitoring'
74+
'category' => 'monitoring',
75+
'user' => new User([
76+
'id' => 543,
77+
'username' => 'John',
78+
'email' => '[email protected]',
79+
]),
80+
'ip' => '2607:f0d0:1002:51::4',
5381
],
5482
[
5583
'level' => Logger::LEVEL_WARNING,
5684
'message' => 'Invalid request',
57-
'category' => 'UI'
85+
'category' => 'UI',
5886
],
5987
[
6088
'level' => null,
6189
'message' => [1, 2, 3],
62-
'category' => null
90+
'category' => null,
6391
],
6492
[
6593
'level' => '',
6694
'message' => ['one' => 'value 1', 'two' => 'value 2'],
67-
'category' => null
95+
'category' => null,
6896
],
6997
];
7098
}

0 commit comments

Comments
 (0)