-
-
Notifications
You must be signed in to change notification settings - Fork 452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Process args in "extra" key of recorded message #848
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As explained here #844 (comment) (similar issue with Python SDK) reading blindly a key from monolog could be dangerous, since other handlers could use such a generic key to store whatever.
I agree, reading from |
@ste93cry but how handling |
Quoting from the Monolog documentation:
The main problem is that there is no guarantee that the data added to this key is data that you want to log in Sentry, that the "shape" of the data is compatible with what Sentry expects or that you want to blindly override what has been set by the user with the data coming from the processors if the share the same key
We can't of course, and this is one of the reasons why we want to avoid in the future blindly pulling information from the log data. I would say that we can be safer in thinking that the data coming from
To clarify, pulling data from |
Oh, support for What about introducing a processor that moves |
For the moment yes, you should implement in on your side. This way you can decide whether to blindly move everything or only certain things to the scope |
Hello everyone, I was looking to do exactly this (sending @prgTW How did you do it ? Should I "just" extend the Thanks! |
You should use composition to wrap the handler inside your custom one and then call use Monolog\Handler\AbstractProcessingHandler;
use Sentry\Scope;
use function Sentry\withScope;
final class CustomMonologHandler extends AbstractProcessingHandler
{
private $innerHandler;
public function __construct(HandlerInterface $handler)
{
$this->innerHandler = $handler;
}
public function handle(array $record)
{
withScope(function (Scope $scope): void {
$scope->setExtra('...', '...');
$this->innerHandler->handle($record);
});
}
} I coded this example on the fly so don't expect it to work as is, but it should give you an hint on how I would do it |
Thanks @ste93cry ! That makes sense 👍 |
Could also easily be handled in Monolog :)
|
To add extra information along the simple textual message, I also use the logging context to pass an array of data along the record. For example:
It was really important for me to get the context in Sentry issues. Thank you very much @ste93cry and @Spriz. I have created a custom handler with a mix of your two answers. Tested successfully with Symfony 3.4. I decorate the config.yml:
services.yml:
src/Acme/Monolog/SentryHandler.php: <?php
namespace Acme\Monolog;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Sentry\Monolog\Handler as InnerHandler;
use Sentry\Severity;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use function Sentry\withScope;
/**
* This Monolog handler logs every message to a Sentry's server using the given
* hub instance.
*
* If extra information are added to the simple textual message (through an array
* of data along the record), this custom Sentry handler iterate the array to set
* data in the extra Sentry context.
*
* @author Stefano Arlandini <sarlandini@alice.it>
*/
final class SentryHandler extends AbstractProcessingHandler
{
/**
* @var HubInterface
*/
private $hub;
/**
* @var InnerHandler
*/
private $handler;
/**
* SentryHandler constructor.
*
* @param InnerHandler $handler
* @param HubInterface $hub The hub to which errors are reported
* @param int $level The minimum logging level at which this
* handler will be triggered
* @param bool $bubble Whether the messages that are handled can
* bubble up the stack or not
*/
public function __construct(InnerHandler $handler, HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true)
{
$this->handler = $handler;
$this->hub = $hub;
parent::__construct($level, $bubble);
}
public function handle(array $record)
{
withScope(function (Scope $scope) use ($record): void {
if (isset($record['context']) && is_array($record['context'])) {
foreach ($record['context'] as $key => $value) {
if ($key === 'tags') {
// Handled natively by Sentry monolog handler
continue;
}
$scope->setExtra($key, is_string($value) && json_decode($value) !== null ? json_decode($value) : $value);
}
}
$this->handler->handle($record);
});
}
/**
* {@inheritdoc}
*/
protected function write(array $record): void
{
$payload = [
'level' => $this->getSeverityFromLevel($record['level']),
'message' => $record['message'],
'logger' => 'monolog.' . $record['channel'],
];
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
$payload['exception'] = $record['context']['exception'];
}
$this->hub->withScope(function (Scope $scope) use ($record, $payload): void {
$scope->setExtra('monolog.channel', $record['channel']);
$scope->setExtra('monolog.level', $record['level_name']);
if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) {
foreach ($record['context']['extra'] as $key => $value) {
$scope->setExtra((string) $key, $value);
}
}
if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) {
foreach ($record['context']['tags'] as $key => $value) {
$scope->setTag($key, $value);
}
}
$this->hub->captureEvent($payload);
});
}
/**
* Translates the Monolog level into the Sentry severity.
*
* @param int $level The Monolog log level
* @return Severity
*/
private function getSeverityFromLevel(int $level): Severity
{
switch ($level) {
case Logger::DEBUG:
return Severity::debug();
case Logger::INFO:
case Logger::NOTICE:
return Severity::info();
case Logger::WARNING:
return Severity::warning();
case Logger::ERROR:
return Severity::error();
case Logger::CRITICAL:
case Logger::ALERT:
case Logger::EMERGENCY:
return Severity::fatal();
default:
return Severity::info();
}
}
} The |
That's not really how I would expect a decorator to be written. Isn't this sufficient? class TestHandler implements HandlerInterface
{
/**
* @var HandlerInterface
*/
private $decoratedHandler;
public function __construct(HandlerInterface $decoratedHandler)
{
$this->decoratedHandler = $decoratedHandler;
}
public function isHandling(array $record): bool
{
return $this->decoratedHandler->isHandling($record);
}
public function handle(array $record): bool
{
$result = false;
withScope(function (Scope $scope) use ($record, &$result): void {
$scope->setExtra(...);
$result = $this->decoratedHandler->handle($record);
});
return $result;
}
public function handleBatch(array $records): void
{
$this->decoratedHandler->handleBatch($records);
}
public function close(): void
{
$this->decoratedHandler->close();
}
} |
Sorry, I'm not a Symfony developer. I have tested your code, and implementing HandlerInterface is much better! With my version of Monolog I had to implement also So the final code, tested successfully is: src/Acme/Monolog/SentryHandler.php: <?php
namespace Acme\Monolog;
use Monolog\Formatter\FormatterInterface;
use Monolog\Handler\HandlerInterface;
use Sentry\State\Scope;
use function Sentry\withScope;
/**
* This Monolog handler logs every message to a Sentry's server using the given
* hub instance.
*
* If extra information is added to the simple textual message (through an array
* of data along the record), this custom Sentry handler iterate the array to set
* data in the extra Sentry context.
*/
class SentryHandler implements HandlerInterface
{
/**
* @var HandlerInterface
*/
private $decoratedHandler;
/**
* SentryHandler constructor.
*
* @param HandlerInterface $decoratedHandler
*/
public function __construct(HandlerInterface $decoratedHandler)
{
$this->decoratedHandler = $decoratedHandler;
}
/**
* @inheritDoc
*/
public function isHandling(array $record): bool
{
return $this->decoratedHandler->isHandling($record);
}
/**
* @inheritDoc
*/
public function handle(array $record): bool
{
$result = false;
withScope(function (Scope $scope) use ($record, &$result): void {
if (isset($record['context']) && is_array($record['context'])) {
foreach ($record['context'] as $key => $value) {
if ($key === 'tags') {
// Handled natively by Sentry monolog handler
continue;
}
$scope->setExtra($key, is_string($value) && json_decode($value) !== null ? json_decode($value) : $value);
}
}
$result = $this->decoratedHandler->handle($record);
});
return $result;
}
/**
* @inheritDoc
*/
public function handleBatch(array $records): void
{
$this->decoratedHandler->handleBatch($records);
}
/**
* @inheritDoc
*/
public function close(): void
{
$this->decoratedHandler->close();
}
/**
* @inheritDoc
*/
public function pushProcessor($callback)
{
$this->decoratedHandler->pushProcessor($callback);
return $this;
}
/**
* @inheritDoc
*/
public function popProcessor()
{
$this->decoratedHandler->popProcessor();
}
/**
* @inheritDoc
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->decoratedHandler->setFormatter($formatter);
return $this;
}
/**
* @inheritDoc
*/
public function getFormatter()
{
return $this->decoratedHandler->getFormatter();
}
} Thank you @ste93cry :) |
Hi there! I have an issue using "withScope". I've decorated Sentry\Monolog\Handler, but "withScope" pushes modified scope in stack, so when i call write(), i got another scope, than modified in decorator. What do i do wrong? |
Scopes work with a stack, so you can push & pop as needed. sentry-php/src/State/HubInterface.php Lines 52 to 58 in d9fdc40
You're probably gonna need |
public function handle(array $record): bool
} I mean handle() method in callback has it's own withScope() call, so your changes to scope in decorator has no effect on final event. Also, configureScope has no effect. UPD: i see that pushed scope is cloned, so it should work, but it isn't. I'll check more. Thank you. |
It's a symfony + monolog-bundle issue: decorator has different hub instance (via function withScope) than Sentry\Monolog\Handler decorated instance. One from DI container, other from static SentrySdk call. |
… context issue (dedpikhto) This PR was merged into the 3.x-dev branch. Discussion ---------- Add Sentry Hub instance in DI container to resolve event context issue Fixing Symfony issue with different Hub's while decorating default Sentry handler for extended event context. Common way to extend log event context is to decorate Sentry\Monolog\Handler: ```php final class SentryContextHandlerDecorator extends AbstractProcessingHandler { private Handler $handler; public function __construct(Handler $handler) { $this->handler = $handler; parent::__construct($handler->getLevel(), $handler->getBubble()); } public function handle(array $record): bool { $result = false; withScope(function (Scope $scope) use ($record, &$result): void { if (isset($record['context']) && is_array($record['context']) && count($record['context']) > 0) { $scope->setContext('context', $record['context']); } $result = $this->handler->handle($record); }); return $result; } } ``` But if you do so with Symfony DI, you will get two instances of Hub: one from DI container, and one with SentrySdk in withScope(...) call. Changes to one Hub's scope won't affect second's hub scope. This PR adds ability to use shared Hub in custom decorators: ```php App\Logging\Handler\SentryContextHandlerDecorator: decorates: monolog.handler.sentry arguments: - '@app\Logging\Handler\SentryContextHandlerDecorator.inner' - '@monolog.handler.sentry.hub' ``` where `sentry` in `monolog.handler.sentry.hub` is name of handler. Decorator with shared Hub instance: ```php final class SentryContextHandlerDecorator extends AbstractProcessingHandler { private Handler $handler; private Hub $hub; public function __construct(Handler $handler, Hub $hub) { $this->handler = $handler; $this->hub = $hub; parent::__construct($handler->getLevel(), $handler->getBubble()); } public function handle(array $record): bool { $result = false; $this->hub->withScope(function (Scope $scope) use ($record, &$result): void { if (isset($record['context']) && is_array($record['context']) && count($record['context']) > 0) { $scope->setContext('context', $record['context']); } $result = $this->handler->handle($record); }); return $result; } } ``` More comments about problem: getsentry/sentry-php#848 (comment) Commits ------- 8a77eb9 Add Sentry Hub instance in DI container to resolve event context issue
… context issue (dedpikhto) This PR was merged into the 3.x-dev branch. Discussion ---------- Add Sentry Hub instance in DI container to resolve event context issue Fixing Symfony issue with different Hub's while decorating default Sentry handler for extended event context. Common way to extend log event context is to decorate Sentry\Monolog\Handler: ```php final class SentryContextHandlerDecorator extends AbstractProcessingHandler { private Handler $handler; public function __construct(Handler $handler) { $this->handler = $handler; parent::__construct($handler->getLevel(), $handler->getBubble()); } public function handle(array $record): bool { $result = false; withScope(function (Scope $scope) use ($record, &$result): void { if (isset($record['context']) && is_array($record['context']) && count($record['context']) > 0) { $scope->setContext('context', $record['context']); } $result = $this->handler->handle($record); }); return $result; } } ``` But if you do so with Symfony DI, you will get two instances of Hub: one from DI container, and one with SentrySdk in withScope(...) call. Changes to one Hub's scope won't affect second's hub scope. This PR adds ability to use shared Hub in custom decorators: ```php App\Logging\Handler\SentryContextHandlerDecorator: decorates: monolog.handler.sentry arguments: - '@app\Logging\Handler\SentryContextHandlerDecorator.inner' - '@monolog.handler.sentry.hub' ``` where `sentry` in `monolog.handler.sentry.hub` is name of handler. Decorator with shared Hub instance: ```php final class SentryContextHandlerDecorator extends AbstractProcessingHandler { private Handler $handler; private Hub $hub; public function __construct(Handler $handler, Hub $hub) { $this->handler = $handler; $this->hub = $hub; parent::__construct($handler->getLevel(), $handler->getBubble()); } public function handle(array $record): bool { $result = false; $this->hub->withScope(function (Scope $scope) use ($record, &$result): void { if (isset($record['context']) && is_array($record['context']) && count($record['context']) > 0) { $scope->setContext('context', $record['context']); } $result = $this->handler->handle($record); }); return $result; } } ``` More comments about problem: getsentry/sentry-php#848 (comment) Commits ------- c635444 Add Sentry Hub instance in DI container to resolve event context issue
This PR aims to fix the problem #836 (
extra
key of the recorded message is completely ignored) as quickly as possible not to loose data from logs