diff --git a/includes/core.php b/includes/core.php index 70affbb..a0962b1 100644 --- a/includes/core.php +++ b/includes/core.php @@ -1,6 +1,9 @@ $sentry_dsn, - 'traces_sample_rate' => ( - isset($sentry_traces_sample_rate) ? - $sentry_traces_sample_rate : 0.2 - ), - ]); - - \Sentry\configureScope(function (\Sentry\State\Scope $scope): void { - global $auth_user; - $scope->setUser([ - 'id' => isset($auth_user) && $auth_user?$auth_user['uid']:null, - 'email' => isset($auth_user) && $auth_user?$auth_user['mail']:null, - 'segment' => isset($auth_user) && $auth_user?$auth_user['type']:null, - 'ip_address' => $_SERVER['REMOTE_ADDR'], - ]); - }); -} - -/** - * Log an exception or a message in Sentry - * @param string|Throwable $msg - * @return void - */ -function log_in_sentry($msg) { - global $sentry_dsn; - if (!isset($sentry_dsn) || !$sentry_dsn) { - Log :: trace('Sentry DSN not configured, do not log this error'); - return; - } - if (is_string($msg)) { - Log :: debug('Error logged in Sentry'); - \Sentry\captureMessage($msg); - } - elseif ($msg instanceof Exception) { - Log :: debug('Exception logged in Sentry'); - \Sentry\captureException($msg); - } -} - -/** - * Log a PHP error in Sentry - * @param int $errno The error number - * @param string $msg The error message - * @return void - */ -function log_php_error_in_sentry($errno, $msg) { - global $sentry_php_error_types; - if ( - isset($sentry_php_error_types) - && is_array($sentry_php_error_types) - && in_array($errno, $sentry_php_error_types) - ) - log_in_sentry($msg); -} - -/* - * Performance monitoring - */ - -class SentryTransaction { - - /** - * The Sentry transaction object - * @var \Sentry\Tracing\Transaction - */ - private $transaction; - - /** - * The Sentry transaction context object - * @var \Sentry\Tracing\TransactionContext - */ - private $context; - - /** - * Constructor: start a Sentry transaction - * @param string|null $op The operation name - * @param string|null $name The transaction name - * @return void - */ - public function __construct($op=null, $name=null) { - // Setup context for the full transaction - $this->context = new \Sentry\Tracing\TransactionContext(); - $this->context->setName( - $name?$name: - (php_sapi_name()=='cli'?'CLI execution':'HTTP request') - ); - $this->context->setOp( - $op?$op: - (php_sapi_name()=='cli'?'cli.command':'http.request') - ); - - // Start the transaction - $this->transaction = \Sentry\startTransaction($this->context); - - // Set the current transaction as the current span so we can retrieve it later - \Sentry\SentrySdk::getCurrentHub()->setSpan($this->transaction); - } - - /** - * Destructor: Stop the current Sentry transaction - * @return void - */ - public function __destruct() { - SentrySpan :: finishAll(); - $this->transaction->finish(); - } -} - -/** - * Internal Sentry Span object implementation - * This internal implementation principally permit to keep trace of new span parent - * and list of started spans. - */ -class SentrySpan { - - /** - * Keep trace of started Sentry spans - * @var array - */ - private static $_started_spans = array(); - - /** - * The unique ID of the Sentry span - * Note: internal ID used as key in self::$_started_spans - * @var int|null - */ - private $id = null; - - /** - * The parent of the Sentry span - * @var mixed - */ - private $parent = null; - - /** - * The context of the Sentry span - * @var null|\Sentry\Tracing\SpanContext - */ - private $context = null; - - /** - * The Sentry span object - * @var mixed - */ - private $span = null; - - /** - * Sentry span constructor - * @param string|null $op The operation name - * @param string|null $name The span name - * @return void - */ - public function __construct($op, $name) { - $this -> parent = \Sentry\SentrySdk::getCurrentHub()->getSpan(); - // Check if we have a parent span (this is the case if we started a transaction earlier) - if (is_null($this -> parent)) return; - - while (is_null($this -> id)) { - $this -> id = rand(); - if (isset(self :: $_started_spans[$this -> id])) - $this -> id = null; - } - $this -> context = new \Sentry\Tracing\SpanContext(); - $this -> context->setOp($op); - $this -> context->setDescription($name); - $this -> span = $this->parent->startChild($this -> context); - - // Set the current span to the span we just started - \Sentry\SentrySdk::getCurrentHub()->setSpan($this -> span); - - self :: $_started_spans[$this -> id] = $this; - } - - /** - * Finish the span (if started) - * @return void - */ - public function finish() { - if (!$this -> span) return; - $this -> span -> finish(); - unset(self::$_started_spans[$this -> id]); - \Sentry\SentrySdk::getCurrentHub()->setSpan($this -> parent); - } - - /** - * Finish all started spans - * @see SentryTransaction::__destruct() - * @return void - */ - public static function finishAll() { - foreach (array_reverse(self :: $_started_spans) as $id => $span) - $span -> finish(); - } -} - -# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab diff --git a/includes/smarty.php b/includes/smarty.php index 7a97fcd..020e1ec 100644 --- a/includes/smarty.php +++ b/includes/smarty.php @@ -1,6 +1,8 @@ 2) { $prefix = call_user_func_array( @@ -351,13 +351,10 @@ class Log { * @return false Return false to let the normal error handler continues. */ public static function on_php_error($errno, $errstr, $errfile, $errline) { - $msg = sprintf( + self :: error( "A PHP error occured : [%s] %s\nFile : %s (line : %d)", self :: errno2type($errno), $errstr, $errfile, $errline ); - self :: error($msg); - if (function_exists('log_php_error_in_sentry')) - log_php_error_in_sentry($errno, $msg); return False; } } diff --git a/src/SentryIntegration.php b/src/SentryIntegration.php new file mode 100644 index 0000000..4316701 --- /dev/null +++ b/src/SentryIntegration.php @@ -0,0 +1,114 @@ + + */ + protected static array $php_error_types = array( + E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, + E_RECOVERABLE_ERROR,E_DEPRECATED, + ); + + /** + * Initialization + * @param string|null $dsn Sentry DSN + * @param float|null $traces_sample_rate Sentry traces sample rate + * (optional, default: 0.2) + * @param array|null $php_error_types Types of PHP error to log in Sentry + * (optional, default: see self::$php_error_types) + * @return void + */ + public static function init($dsn=null, $traces_sample_rate=null, + $php_error_types=null) { + // Init Sentry (if its DSN is configured) + if (!$dsn) return; + + \Sentry\init([ + 'dsn' => $dsn, + 'traces_sample_rate' => ( + $traces_sample_rate? + $traces_sample_rate:0.2 + ), + ]); + + \Sentry\configureScope(function (\Sentry\State\Scope $scope): void { + global $auth_user; + $scope->setUser([ + 'id' => isset($auth_user) && $auth_user?$auth_user['uid']:null, + 'email' => isset($auth_user) && $auth_user?$auth_user['mail']:null, + 'segment' => isset($auth_user) && $auth_user?$auth_user['type']:null, + 'ip_address' => $_SERVER['REMOTE_ADDR'], + ]); + }); + + if (is_array($php_error_types)) + self :: $php_error_types = $php_error_types; + set_error_handler( + array('EesyPHP\\SentryIntegration', 'on_php_error'), + E_ALL + ); + } + + /** + * Log an exception or a message in Sentry + * @param string|Throwable $message + * @param array $extra_args Extra arguments to use to compute message using sprintf + * @return void + */ + public static function log($message, ...$extra_args) { + if (!self :: $dsn) { + Log :: trace('Sentry DSN not configured, do not log this error'); + return; + } + if (is_string($message)) { + // Extra arguments passed, format message using sprintf + if ($extra_args) { + $message = call_user_func_array( + 'sprintf', + array_merge(array($message), $extra_args) + ); + } + Log :: debug('Error logged in Sentry'); + \Sentry\captureMessage($message); + } + elseif ($message instanceof Exception) { + Log :: debug('Exception logged in Sentry'); + \Sentry\captureException($message); + } + } + + /** + * Log a PHP error in Sentry + * Note: method design to be used as callable by set_error_handler() + * @param int $errno The error number + * @param string $errstr The error message + * @param string $errfile The filename that the error was raised in + * @param int $errline The line number where the error was raised + * @return false Return false to let the normal error handler continues. + */ + public static function on_php_error($errno, $errstr, $errfile, $errline) { + if (in_array($errno, self :: $php_error_types)) + self :: log( + "A PHP error occured : [%s] %s\nFile : %s (line : %d)", + Log :: errno2type($errno), $errstr, $errfile, $errline + ); + return false; + } +} + +# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab diff --git a/src/SentrySpan.php b/src/SentrySpan.php new file mode 100644 index 0000000..a4c9570 --- /dev/null +++ b/src/SentrySpan.php @@ -0,0 +1,92 @@ + + */ + private static $_started_spans = array(); + + /** + * The unique ID of the Sentry span + * Note: internal ID used as key in self::$_started_spans + * @var int|null + */ + private $id = null; + + /** + * The parent of the Sentry span + * @var mixed + */ + private $parent = null; + + /** + * The context of the Sentry span + * @var null|\Sentry\Tracing\SpanContext + */ + private $context = null; + + /** + * The Sentry span object + * @var mixed + */ + private $span = null; + + /** + * Sentry span constructor + * @param string|null $op The operation name + * @param string|null $name The span name + * @return void + */ + public function __construct($op, $name) { + $this -> parent = \Sentry\SentrySdk::getCurrentHub()->getSpan(); + // Check if we have a parent span (this is the case if we started a transaction earlier) + if (is_null($this -> parent)) return; + + while (is_null($this -> id)) { + $this -> id = rand(); + if (isset(self :: $_started_spans[$this -> id])) + $this -> id = null; + } + $this -> context = new \Sentry\Tracing\SpanContext(); + $this -> context->setOp($op); + $this -> context->setDescription($name); + $this -> span = $this->parent->startChild($this -> context); + + // Set the current span to the span we just started + \Sentry\SentrySdk::getCurrentHub()->setSpan($this -> span); + + self :: $_started_spans[$this -> id] = $this; + } + + /** + * Finish the span (if started) + * @return void + */ + public function finish() { + if (!$this -> span) return; + $this -> span -> finish(); + unset(self::$_started_spans[$this -> id]); + \Sentry\SentrySdk::getCurrentHub()->setSpan($this -> parent); + } + + /** + * Finish all started spans + * @see SentryTransaction::__destruct() + * @return void + */ + public static function finishAll() { + foreach (array_reverse(self :: $_started_spans) as $id => $span) + $span -> finish(); + } +} + +# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab diff --git a/src/SentryTransaction.php b/src/SentryTransaction.php new file mode 100644 index 0000000..0e456d8 --- /dev/null +++ b/src/SentryTransaction.php @@ -0,0 +1,58 @@ +context = new \Sentry\Tracing\TransactionContext(); + $this->context->setName( + $name?$name: + (php_sapi_name()=='cli'?'CLI execution':'HTTP request') + ); + $this->context->setOp( + $op?$op: + (php_sapi_name()=='cli'?'cli.command':'http.request') + ); + + // Start the transaction + $this->transaction = \Sentry\startTransaction($this->context); + + // Set the current transaction as the current span so we can retrieve it later + \Sentry\SentrySdk::getCurrentHub()->setSpan($this->transaction); + } + + /** + * Destructor: Stop the current Sentry transaction + * @return void + */ + public function __destruct() { + SentrySpan :: finishAll(); + $this->transaction->finish(); + } +} + +# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab