Skip to content

Commit 9b169cc

Browse files
Litarnuscleptric
andauthored
feat(logs): introduce logging for symfony (#940)
Co-authored-by: Michi Hoffmann <[email protected]>
1 parent 286b63e commit 9b169cc

File tree

13 files changed

+506
-0
lines changed

13 files changed

+506
-0
lines changed

src/EventListener/ConsoleListener.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Sentry\Event;
88
use Sentry\EventHint;
99
use Sentry\ExceptionMechanism;
10+
use Sentry\Logs\Logs;
1011
use Sentry\State\HubInterface;
1112
use Sentry\State\Scope;
1213
use Symfony\Component\Console\Event\ConsoleCommandEvent;
@@ -71,6 +72,7 @@ public function handleConsoleCommandEvent(ConsoleCommandEvent $event): void
7172
*/
7273
public function handleConsoleTerminateEvent(ConsoleTerminateEvent $event): void
7374
{
75+
Logs::getInstance()->flush();
7476
$this->hub->popScope();
7577
}
7678

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\EventListener;
6+
7+
use Sentry\Logs\Logs;
8+
use Symfony\Component\HttpKernel\Event\TerminateEvent;
9+
10+
/**
11+
* RequestListener for sentry log related events.
12+
*/
13+
class LogRequestListener
14+
{
15+
/**
16+
* Flushes the logs on kernel termination.
17+
*
18+
* @param TerminateEvent $event
19+
*
20+
* @return void
21+
*/
22+
public function handleKernelTerminateEvent(TerminateEvent $event)
23+
{
24+
Logs::getInstance()->flush();
25+
}
26+
}

src/Monolog/LogsHandler.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\Monolog;
6+
7+
use Monolog\Logger as MonologLogger;
8+
use Monolog\LogRecord;
9+
use Sentry\Monolog\CompatibilityLogLevelTrait;
10+
use Sentry\Monolog\LogsHandler as BaseLogsHandler;
11+
12+
/**
13+
* Extends the base LogsHandler so that it can be used with Monolog constants.
14+
* Sentry LogLevel objects are not easily usable with symfony config files, so this provides
15+
* a convenient way to instantiate the service in yaml/xml.
16+
*/
17+
class LogsHandler extends BaseLogsHandler
18+
{
19+
use CompatibilityLogLevelTrait;
20+
21+
public function __construct(int $level = MonologLogger::DEBUG, bool $bubble = true)
22+
{
23+
$logLevel = self::getSentryLogLevelFromMonologLevel($level);
24+
parent::__construct($logLevel, $bubble);
25+
}
26+
27+
/**
28+
* @param array<string, mixed>|LogRecord $record
29+
*/
30+
public function handle($record): bool
31+
{
32+
// Extra check required here because `isHandling` is not guaranteed to
33+
// be called, and we might accidentally capture log messages that should be filtered.
34+
if ($this->isHandling($record)) {
35+
return parent::handle($record);
36+
}
37+
38+
return false;
39+
}
40+
}

src/Resources/config/services.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
<tag name="kernel.event_listener" event="kernel.request" method="handleKernelRequestEvent" />
8686
</service>
8787

88+
<service id="Sentry\SentryBundle\EventListener\LogRequestListener" class="Sentry\SentryBundle\EventListener\LogRequestListener">
89+
<tag name="kernel.event_listener" event="kernel.terminate" method="handleKernelTerminateEvent" priority="10" />
90+
</service>
91+
8892
<service id="Sentry\SentryBundle\Command\SentryTestCommand" class="Sentry\SentryBundle\Command\SentryTestCommand">
8993
<argument type="service" id="Sentry\State\HubInterface" />
9094

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\Tests\End2End\App\Command;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\Console\Command\Command;
9+
use Symfony\Component\Console\Input\ArgvInput;
10+
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Output\OutputInterface;
12+
13+
class CrashCommand extends Command
14+
{
15+
/**
16+
* @var LoggerInterface
17+
*/
18+
private $logger;
19+
20+
/**
21+
* @var string
22+
*/
23+
private $subcommand;
24+
25+
public function __construct(LoggerInterface $logger, ?string $subcommand = null)
26+
{
27+
parent::__construct();
28+
$this->logger = $logger;
29+
$this->subcommand = $subcommand;
30+
}
31+
32+
protected function execute(InputInterface $input, OutputInterface $output): int
33+
{
34+
$this->logger->warning('Executing subcommand if exists');
35+
36+
if (null !== $this->subcommand) {
37+
$this->getApplication()->doRun(new ArgvInput(['bin/console', 'log:test']), $output);
38+
}
39+
40+
$this->logger->emergency('About to crash');
41+
42+
throw new \RuntimeException('Crash in command');
43+
}
44+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\Tests\End2End\App\Command;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\Console\Command\Command;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
12+
class LoggingCommand extends Command
13+
{
14+
/**
15+
* @var LoggerInterface
16+
*/
17+
private $logger;
18+
19+
public function __construct(LoggerInterface $logger)
20+
{
21+
parent::__construct();
22+
$this->logger = $logger;
23+
}
24+
25+
protected function execute(InputInterface $input, OutputInterface $output): int
26+
{
27+
$this->logger->debug('Debug Log');
28+
29+
$this->logger->info('Info Log');
30+
31+
$this->logger->warning('Warn Log');
32+
33+
$this->logger->error('Error Log');
34+
35+
return 0;
36+
}
37+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\Tests\End2End\App\Controller;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\HttpFoundation\Response;
9+
10+
class LoggingController
11+
{
12+
/**
13+
* @var LoggerInterface
14+
*/
15+
private $logger;
16+
17+
public function __construct(LoggerInterface $logger)
18+
{
19+
$this->logger = $logger;
20+
}
21+
22+
public function justLogging()
23+
{
24+
// LogLevel::fatal()
25+
$this->logger->emergency('Emergency Log');
26+
$this->logger->critical('Critical Log');
27+
// LogLevel::error()
28+
$this->logger->error('Error Log');
29+
// LogLevel::warn()
30+
$this->logger->warning('Warn Log');
31+
// LogLevel::info()
32+
$this->logger->info('Info Log');
33+
$this->logger->notice('Notice Log');
34+
// LogLevel::debug()
35+
$this->logger->debug('Debug Log');
36+
37+
return new Response();
38+
}
39+
40+
public function loggingWithError()
41+
{
42+
$this->logger->emergency('Something is not right');
43+
$this->logger->error('About to crash');
44+
throw new \RuntimeException('Crash');
45+
}
46+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\Tests\End2End\App;
6+
7+
use Symfony\Component\Config\Loader\LoaderInterface;
8+
9+
class KernelWithLogging extends Kernel
10+
{
11+
public function registerContainerConfiguration(LoaderInterface $loader): void
12+
{
13+
parent::registerContainerConfiguration($loader);
14+
15+
$loader->load(__DIR__ . '/logging.yml');
16+
}
17+
}

tests/End2End/App/logging.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
sentry:
2+
options:
3+
enable_logs: true
4+
5+
monolog:
6+
handlers:
7+
sentry_logs:
8+
type: service
9+
id: Sentry\SentryBundle\Monolog\LogsHandler
10+
11+
services:
12+
Sentry\SentryBundle\Monolog\LogsHandler:
13+
arguments:
14+
- !php/const Monolog\Logger::WARNING
15+
16+
Sentry\SentryBundle\Tests\End2End\App\Controller\LoggingController:
17+
autowire: true
18+
tags:
19+
- { name: controller.service_arguments }
20+
21+
Sentry\SentryBundle\Tests\End2End\App\Command\LoggingCommand:
22+
autowire: true
23+
tags: [ { name: 'console.command', command: 'log:test' } ]
24+
25+
Sentry\SentryBundle\Tests\End2End\App\Command\CrashCommand.nosub:
26+
autowire: true
27+
class: Sentry\SentryBundle\Tests\End2End\App\Command\CrashCommand
28+
arguments:
29+
$subcommand: null
30+
tags: [ { name: 'console.command', command: 'log:nosub' } ]
31+
32+
Sentry\SentryBundle\Tests\End2End\App\Command\CrashCommand.sub:
33+
autowire: true
34+
class: Sentry\SentryBundle\Tests\End2End\App\Command\CrashCommand
35+
arguments:
36+
$subcommand: 'log:test'
37+
tags: [ { name: 'console.command', command: 'log:sub' } ]

tests/End2End/App/routing.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ tracing_ignored_transaction:
4242
path: /tracing/ignored-transaction
4343
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\TracingController::ignoredTransaction' }
4444

45+
just_logging:
46+
path: /just-logging
47+
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\LoggingController::justLogging' }
48+
49+
logging_With_Error:
50+
path: /logging-with-error
51+
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\LoggingController::loggingWithError' }
52+
4553
buffer_flush:
4654
path: /buffer-flush
4755
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\BufferFlushController::testBufferFlush' }

0 commit comments

Comments
 (0)