Skip to content
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

Added telemetry support #160

Merged
merged 5 commits into from
Feb 19, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added initial telemetry support.
danielmorell committed Jan 17, 2025
commit 4745a1a2cb62352631ec520141e2f277d6e3e344
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"require": {
"php": "^8.1",
"illuminate/support": "^10.0|^11.0",
"rollbar/rollbar": "^4.0"
"rollbar/rollbar": "v4.1.0-rc"
},
"require-dev": {
"orchestra/testbench": "^8.0",
126 changes: 93 additions & 33 deletions src/RollbarServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php namespace Rollbar\Laravel;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Events\Dispatcher;
use Rollbar\Rollbar;
use Rollbar\RollbarLogger;
use Illuminate\Support\Arr;
@@ -10,6 +12,11 @@

class RollbarServiceProvider extends ServiceProvider
{
/**
* The telemetry event listener.
*/
protected TelemetryListener $telemetryListener;

/**
* Register the service provider.
*/
@@ -21,35 +28,11 @@ public function register(): void
}

$this->app->singleton(RollbarLogger::class, function (Application $app) {
$config = $this->getConfigs($app);

$defaults = [
'environment' => $app->environment(),
'root' => base_path(),
'handle_exception' => true,
'handle_error' => true,
'handle_fatal' => true,
];

$config = array_merge($defaults, $app['config']->get('logging.channels.rollbar', []));

$config['access_token'] = static::config('access_token');

if (empty($config['access_token'])) {
throw new InvalidArgumentException('Rollbar access token not configured');
}

$handleException = (bool) Arr::pull($config, 'handle_exception');
$handleError = (bool) Arr::pull($config, 'handle_error');
$handleFatal = (bool) Arr::pull($config, 'handle_fatal');

// Convert a request for the Rollbar agent to handle the logs to
// the format expected by `Rollbar::init`.
// @see https://github.com/rollbar/rollbar-php-laravel/issues/85
$handler = Arr::get($config, 'handler');
if ($handler === AgentHandler::class) {
$config['handler'] = 'agent';
}
$config['framework'] = 'laravel ' . app()->version();
$handleException = (bool)Arr::pull($config, 'handle_exception');
$handleError = (bool)Arr::pull($config, 'handle_error');
$handleFatal = (bool)Arr::pull($config, 'handle_fatal');
Rollbar::init($config, $handleException, $handleError, $handleFatal);

return Rollbar::logger();
@@ -58,20 +41,39 @@ public function register(): void
$this->app->singleton(MonologHandler::class, function (Application $app) {

$level = static::config('level', 'debug');

$handler = new MonologHandler($app[RollbarLogger::class], $level);
$handler->setApp($app);

return $handler;
});
}

/**
* Boot is called after all services are registered.
*
* This is where we can start listening for events.
*
* @param RollbarLogger $logger This parameter is injected by the service container, and is required to ensure that
* the Rollbar logger is initialized.
* @return void
*
* @since 8.1.0
*/
public function boot(RollbarLogger $logger): void
{
// Set up telemetry if it is enabled.
if (null !== Rollbar::getTelemeter()) {
$this->setupTelemetry($this->getConfigs($this->app));
}
}

/**
* Check if we should prevent the service from registering.
*
* @return boolean
*/
public function stop() : bool
public function stop(): bool
{
$level = static::config('level');

@@ -85,8 +87,8 @@ public function stop() : bool
/**
* Return a rollbar logging config.
*
* @param string $key The config key to lookup.
* @param mixed $default The default value to return if the config is not found.
* @param string $key The config key to lookup.
* @param mixed $default The default value to return if the config is not found.
*
* @return mixed
*/
@@ -98,8 +100,66 @@ protected static function config(string $key = '', mixed $default = null): mixed
$envKey = 'ROLLBAR_TOKEN';
}

$logKey = empty($key) ? 'logging.channels.rollbar' : "logging.channels.rollbar.$key";
$logKey = empty($key) ? 'logging.channels.rollbar' : 'logging.channels.rollbar.' . $key;

return getenv($envKey) ?: Config::get($logKey, $default);
}

/**
* Returns the Rollbar configuration.
*
* @param Application $app The Laravel application.
* @return array
*
* @since 8.1.0
*
* @throw InvalidArgumentException If the Rollbar access token is not configured.
*/
public function getConfigs(Application $app): array
{
$defaults = [
'environment' => $app->environment(),
'root' => base_path(),
'handle_exception' => true,
'handle_error' => true,
'handle_fatal' => true,
];

$config = array_merge($defaults, $app['config']->get('logging.channels.rollbar', []));
$config['access_token'] = static::config('access_token');

if (empty($config['access_token'])) {
throw new InvalidArgumentException('Rollbar access token not configured');
}
// Convert a request for the Rollbar agent to handle the logs to
// the format expected by `Rollbar::init`.
// @see https://github.com/rollbar/rollbar-php-laravel/issues/85
$handler = Arr::get($config, 'handler', MonologHandler::class);
if ($handler === AgentHandler::class) {
$config['handler'] = 'agent';
}
$config['framework'] = 'laravel ' . $app->version();
return $config;
}

/**
* Sets up the telemetry event listeners.
*
* @param array $config
* @return void
*
* @since 8.1.0
*/
protected function setupTelemetry(array $config): void
{
$this->telemetryListener = new TelemetryListener($this->app, $config);

try {
$dispatcher = $this->app->make(Dispatcher::class);
} catch (BindingResolutionException $e) {
return;
}

$this->telemetryListener->listen($dispatcher);
}
}
151 changes: 151 additions & 0 deletions src/TelemetryListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace Rollbar\Laravel;

use Exception;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Log\Events\MessageLogged;
use Illuminate\Routing\Events\RouteMatched;
use Rollbar\Rollbar;
use Rollbar\Telemetry\EventType;
use Rollbar\Telemetry\EventLevel;
use Rollbar\Telemetry\Telemeter;

/**
* This class handles Laravel events and maps them to Rollbar telemetry events.
*
* @since 8.1.0
*/
class TelemetryListener
{
const BASE_EVENTS = [
MessageLogged::class => 'logMessageHandler',
RouteMatched::class => 'routeMatchedHandler',
QueryExecuted::class => 'queryExecutedHandler',
];

private Container $container;

private array $config;

/**
* @var bool
*/
private bool $captureLogs;
private bool $captureRouting;
private bool $captureQueries;
private bool $captureDbParameters;

/**
* @param Container $container The Laravel application container.
* @param array $config
*/
public function __construct(Container $container, array $config)
{
$this->container = $container;
$this->config = $config;

$this->captureLogs = boolval($this->config['telemetry']['capture_logs'] ?? true);
$this->captureRouting = boolval($this->config['telemetry']['capture_routing'] ?? true);
$this->captureQueries = boolval($this->config['telemetry']['capture_db_queries'] ?? true);
// We do not want to capture query parameters by default, the developer must explicitly enable it.
$this->captureDbParameters = boolval($this->config['telemetry']['capture_db_query_parameters'] ?? false);
}

/**
* Register the event listeners for the application.
*
* @param Dispatcher $dispatcher
* @return void
*/
public function listen(Dispatcher $dispatcher): void
{
foreach (self::BASE_EVENTS as $event => $handler) {
$dispatcher->listen($event, [$this, $handler]);
}
}

/**
* Execute the event handler.
*
* This is used so that the handlers are not public methods.
*
* @param string $method The method to call.
* @param array $args The arguments to pass to the method.
* @return void
*/
public function __call(string $method, array $args): void
{
if (!method_exists($this, $method)) {
return;
}

try {
$this->{$method}(...$args);
} catch (Exception $e) {
// Do nothing.
}
}

/**
* Handler for log messages.
*
* @param MessageLogged $message
* @return void
*/
protected function logMessageHandler(MessageLogged $message): void
{
if (null === $message->message || !$this->captureLogs) {
return;
}

Rollbar::captureTelemetryEvent(
EventType::Log,
// Telemetry does not support all PSR-3 or RFC-5424 levels, so we need to convert them.
Telemeter::getLevelFromPsrLevel($message->level),
array_merge(
$message->context,
['message' => $message->message],
),
);
}

protected function routeMatchedHandler(RouteMatched $matchedRoute): void
{
if (!$this->captureRouting) {
return;
}
$routePath = $matchedRoute->route->uri();

Rollbar::captureTelemetryEvent(
EventType::Manual,
EventLevel::Info,
[
'message' => 'Route matched',
'route' => $routePath,
],
);
}

protected function queryExecutedHandler(QueryExecuted $query): void
{
if (!$this->captureQueries) {
return;
}

$meta = [
'message' => 'Query executed',
'query' => $query->sql,
'time' => $query->time,
'connection' => $query->connectionName,
];

if ($this->captureDbParameters) {
$meta['bindings'] = $query->bindings;
}

Rollbar::captureTelemetryEvent(EventType::Manual, EventLevel::Info, $meta);
}
}