fjffk简单的咖啡店科开发2方法2
环滁皆山也。其西南诸峰,林壑尤美,望之蔚然而深秀者,琅琊也。山行六七里,渐闻水声潺潺,而泻出于两峰之间者,酿泉也
???????????????????????????????
??????????????/?????????????????
............/...................
.........................../...
???????????????????????????????
>>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>
ÿØÿà JFIF ÿÛ „ ( %!1!%)+//.383,7(-.+
-%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ ¥2" ÿÄ ÿÄ J ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ ÿÄ * !1AQa‘"2q3±ð#b¡ÿÚ ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú
"SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁTÃÛ5
ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßwpŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e
ríV
?>
.........................................
.............................................................................
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
dsdfsentry/LICENSE 0000644 00000002104 15266563341 0007105 0 ustar 00 MIT License
Copyright (c) 2012 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. sentry/UPGRADE-4.0.md 0000644 00000006256 15266563341 0010024 0 ustar 00 # Upgrade 3.x to 4.0
- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry.
If you are using [sentry.io](https://sentry.io), no action is needed.
If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`.
- Added `ext-curl` as a composer requirement.
- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead.
```php
Sentry\init([
'ignore_exceptions' => [BadThingsHappenedException::class],
]);
```
This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check, so you can also ignore more generic exceptions.
- Removed support for `symfony/options-resolver: ^3.4.43`.
- The `RequestFetcher` now relies on `guzzlehttp/psr7: ^1.8.4|^2.1.1`.
- Added new methods to `ClientInterface`
```php
public function getCspReportUrl(): ?string;
public function getStacktraceBuilder(): StacktraceBuilder;
```
- Added new methods to `HubInterface`
```php
public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string;
```
- The new default value for the `trace_propagation_targets` option is now `null`. To not attach any headers to outgoing requests, using the `GuzzleTracingMiddleware`, set this option to `[]`.
- The `ignore_exceptions` option now performs a `is_a` check on the provided class strings.
- The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour.
- The `enable_compression` option was removed. Use `http_compression` instead.
- The `logger` option now accepts a `Psr\Log\LoggerInterface` instance instead of `string`.
- Removed `Options::getSendAttempts/setSendAttempts()`.
- Removed `Options::isCompressionEnabled/setEnableCompression()`. Use `Options::isHttpCompressionEnabled/setEnableHttpCompression()` instead.
- Removed `SpanContext::fromTraceparent()`. Use `Sentry\continueTrace()` instead.
- Removed `TransactionContext::fromSentryTrace()`. Use `Sentry\continueTrace()` instead.
- Removed `Sentry\Exception\InvalidArgumentException`. Use `\InvalidArgumentException` instead.
- Removed `Sentry\Exception/ExceptionInterface`.
- Removed `ClientBuilderInterface()`.
- Removed `ClientBuilder::setSerializer()`.
- Removed `ClientBuilder::setTransportFactory()`. You can set a custom transport via the `transport` option.
- Removed `Client::__construct()` parameter `SerializerInterface $serializer`.
- Removed `TransportFactoryInterface`.
- Removed `DefaultTransportFactory`.
- Removed `HttpClientFactoryInterface`.
- Removed `HttpClientFactory`.
- Removed `NullTransport`.
- Removed `Dsn::getSecretKey()`.
- Removed `Dsn::setSecretKey()`.
- Removed `Dsn::getStoreApiEndpointUrl()`.
- Removed `EventType::default()`.
- Removed adding the value of the `logger` option as a tag on the event. If you rely on this behaviour, add the tag manually.
- Added return type to `Dsn::getProjectId(): string`.
- Changed return type to `Options::getLogger(): ?LoggerInterface`.
- Changed parameter type of `Options::setLogger(LoggerInterface $logger)`.
sentry/README.md 0000644 00000012256 15266563341 0007370 0 ustar 00
_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_
# Official Sentry SDK for PHP
[](https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster)
[](https://codecov.io/gh/getsentry/sentry-php/branch/master)
[](https://packagist.org/packages/sentry/sentry)
[](https://packagist.org/packages/sentry/sentry)
[](https://packagist.org/packages/sentry/sentry)
[](https://packagist.org/packages/sentry/sentry)
[](https://discord.gg/cWnMQeA)
The Sentry PHP error reporter tracks errors and exceptions that happen during the
execution of your application and provides instant notification with detailed
information needed to prioritize, identify, reproduce and fix each issue.
## Getting started
### Install
Install the SDK using [Composer](https://getcomposer.org/).
```bash
composer require sentry/sentry
```
### Configuration
Initialize the SDK as early as possible in your application.
```php
\Sentry\init(['dsn' => '___PUBLIC_DSN___' ]);
```
### Usage
```php
try {
thisFunctionThrows(); // -> throw new \Exception('foo bar');
} catch (\Exception $exception) {
\Sentry\captureException($exception);
}
```
## Official integrations
The following integrations are fully supported and maintained by the Sentry team.
- [Symfony](https://github.com/getsentry/sentry-symfony)
- [Laravel](https://github.com/getsentry/sentry-laravel)
## 3rd party integrations using SDK 4.x
The following integrations are available and maintained by members of the Sentry community.
- [Drupal](https://www.drupal.org/project/raven)
- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/)
- Magento 2 by [JustBetter](https://github.com/justbetter/magento2-sentry) or by [Mygento](https://github.com/mygento/module-sentry)
- [Joomla!](https://github.com/AlterBrains/sentry-joomla)
- ... feel free to be famous, create a port to your favourite platform!
## 3rd party integrations using the old SDK 3.x
- [Neos Flow](https://github.com/flownative/flow-sentry)
- [ZendFramework](https://github.com/facile-it/sentry-module)
- [Yii2](https://github.com/notamedia/yii2-sentry)
- [Silverstripe](https://github.com/phptek/silverstripe-sentry)
- [CakePHP 3.0 - 4.3](https://github.com/Connehito/cake-sentry)
- [CakePHP 4.4+](https://github.com/lordsimal/cakephp-sentry)
- [October CMS](https://github.com/OFFLINE-GmbH/oc-sentry-plugin)
## 3rd party integrations using the old SDK 2.x
- [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient)
- [OXID eShop](https://github.com/OXIDprojects/sentry)
- [TYPO3](https://github.com/networkteam/sentry_client)
- [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x)
## 3rd party integrations using the old SDK 1.x
- [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient)
- [OpenCart](https://github.com/BurdaPraha/oc_sentry)
- [TYPO3](https://github.com/networkteam/sentry_client/tree/2.1.1)
## Community
- [Documentation](https://docs.sentry.io/error-reporting/quickstart/?platform=php)
- [Bug Tracker](http://github.com/getsentry/sentry-php/issues)
- [Code](http://github.com/getsentry/sentry-php)
## Contributing to the SDK
Please make sure to read the [CONTRIBUTING.md](CONTRIBUTING.md) before making a pull request.
Thanks to everyone who has contributed to this project so far.
## Getting help/support
If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/sentry). There is a ton of great people in our Discord community ready to help you!
## Resources
- [](https://docs.sentry.io/quickstart/)
- [](https://discord.gg/Ww9hbqr)
- [](http://stackoverflow.com/questions/tagged/sentry)
- [](https://twitter.com/intent/follow?screen_name=getsentry)
## License
Licensed under the MIT license, see [`LICENSE`](LICENSE)
sentry/AUTHORS 0000644 00000000236 15266563341 0007154 0 ustar 00 The Sentry PHP SDK was originally written by Michael van Tellingen
and is maintained by the Sentry Team.
http://github.com/getsentry/sentry-php/contributors
sentry/scripts/bump-version.sh 0000644 00000000756 15266563341 0012564 0 ustar 00 #!/bin/bash
set -eux
if [ "$(uname -s)" != "Linux" ]; then
echo "Please use the GitHub Action."
exit 1
fi
SCRIPT_DIR="$( dirname "$0" )"
cd $SCRIPT_DIR/..
OLD_VERSION="${1}"
NEW_VERSION="${2}"
echo "Current version: $OLD_VERSION"
echo "Bumping version: $NEW_VERSION"
function replace() {
! grep "$2" $3
perl -i -pe "s/$1/$2/g" $3
grep "$2" $3 # verify that replacement was successful
}
replace "SDK_VERSION = '[0-9.]+'" "SDK_VERSION = '$NEW_VERSION'" ./src/Client.php sentry/src/SentrySdk.php 0000644 00000002371 15266563341 0011334 0 ustar 00
*/
final class SentrySdk
{
/**
* @var HubInterface|null The current hub
*/
private static $currentHub;
/**
* Constructor.
*/
private function __construct()
{
}
/**
* Initializes the SDK by creating a new hub instance each time this method
* gets called.
*/
public static function init(): HubInterface
{
self::$currentHub = new Hub();
return self::$currentHub;
}
/**
* Gets the current hub. If it's not initialized then creates a new instance
* and sets it as current hub.
*/
public static function getCurrentHub(): HubInterface
{
if (self::$currentHub === null) {
self::$currentHub = new Hub();
}
return self::$currentHub;
}
/**
* Sets the current hub.
*
* @param HubInterface $hub The hub to set
*/
public static function setCurrentHub(HubInterface $hub): HubInterface
{
self::$currentHub = $hub;
return $hub;
}
}
sentry/src/Serializer/PayloadSerializerInterface.php 0000644 00000000714 15266563341 0016762 0 ustar 00 maxDepth = $maxDepth;
if ($mbDetectOrder != null) {
$this->mbDetectOrder = $mbDetectOrder;
}
$this->options = $options;
}
/**
* Serialize an object (recursively) into something safe for data
* sanitization and encoding.
*
* @param mixed $value
*
* @return string|bool|float|int|mixed[]|null
*/
protected function serializeRecursively($value, int $_depth = 0)
{
try {
if ($_depth >= $this->maxDepth) {
return $this->serializeValue($value);
}
try {
if (@\is_callable($value)) {
return $this->serializeCallable($value);
}
} catch (\Throwable $exception) {
// Do nothing on purpose
}
if (\is_array($value)) {
$serializedArray = [];
foreach ($value as $k => $v) {
$serializedArray[$k] = $this->serializeRecursively($v, $_depth + 1);
}
return $serializedArray;
}
if (\is_object($value)) {
$classSerializers = $this->resolveClassSerializers($value);
// Try each serializer until there is none left or the serializer returned data
foreach ($classSerializers as $classSerializer) {
try {
$serializedObjectData = $classSerializer($value);
if (\is_array($serializedObjectData)) {
return [
'class' => \get_class($value),
'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1),
];
}
} catch (\Throwable $e) {
// Ignore any exceptions generated by a class serializer
}
}
if ($value instanceof \DateTimeInterface) {
return $this->formatDate($value);
}
if ($this->serializeAllObjects || ($value instanceof \stdClass)) {
return $this->serializeObject($value, $_depth);
}
}
return $this->serializeValue($value);
} catch (\Throwable $error) {
if (\is_string($value)) {
return $value . ' {serialization error}';
}
return '{serialization error}';
}
}
/**
* Find class serializers for a object.
*
* Registered serializers with the `class_serializers` option take precedence over
* objects implementing the `SerializableInterface`.
*
* @param object $object
*
* @return array
*/
protected function resolveClassSerializers($object): array
{
$serializers = [];
foreach ($this->options->getClassSerializers() as $type => $serializer) {
if ($object instanceof $type) {
$serializers[] = $serializer;
}
}
if ($object instanceof SerializableInterface) {
$serializers[] = static function (SerializableInterface $object): ?array {
return $object->toSentry();
};
}
return $serializers;
}
/**
* @param object $object
* @param string[] $hashes
*
* @return mixed[]|string|bool|float|int|null
*/
protected function serializeObject($object, int $_depth = 0, array $hashes = [])
{
if ($_depth >= $this->maxDepth || \in_array(spl_object_hash($object), $hashes, true)) {
return $this->serializeValue($object);
}
$hashes[] = spl_object_hash($object);
$serializedObject = [];
foreach ($object as $key => &$value) {
if (\is_object($value)) {
$serializedObject[$key] = $this->serializeObject($value, $_depth + 1, $hashes);
} else {
$serializedObject[$key] = $this->serializeRecursively($value, $_depth + 1);
}
}
return $serializedObject;
}
/**
* Serializes the given value to a string.
*
* @param string $value The value to serialize
*/
protected function serializeString(string $value): string
{
if ($value === '') {
return '';
}
// we always guarantee this is coerced, even if we can't detect encoding
if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) {
$encoded = mb_convert_encoding($value, 'UTF-8', $currentEncoding) ?: '';
} else {
$encoded = mb_convert_encoding($value, 'UTF-8') ?: '';
}
if (mb_strlen($encoded) > $this->options->getMaxValueLength()) {
$encoded = mb_substr($encoded, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}';
}
return $encoded;
}
/**
* @param mixed $value
*
* @return string|bool|float|int|null
*/
protected function serializeValue($value)
{
if (($value === null) || \is_bool($value) || is_numeric($value)) {
return $value;
}
if ($value instanceof \UnitEnum) {
$reflection = new \ReflectionObject($value);
return 'Enum ' . $reflection->getName() . '::' . $value->name;
}
if (\is_object($value)) {
$reflection = new \ReflectionObject($value);
$objectId = null;
if ($reflection->hasProperty('id') && ($idProperty = $reflection->getProperty('id'))->isPublic()) {
$objectId = $idProperty->getValue($value);
} elseif ($reflection->hasMethod('getId') && ($getIdMethod = $reflection->getMethod('getId'))->isPublic()) {
try {
$objectId = $getIdMethod->invoke($value);
} catch (\Throwable $e) {
// Do nothing on purpose
}
}
return 'Object ' . $reflection->getName() . (\is_scalar($objectId) ? '(#' . $objectId . ')' : '');
}
if (\is_resource($value)) {
return 'Resource ' . get_resource_type($value);
}
try {
if (@\is_callable($value)) {
return $this->serializeCallable($value);
}
} catch (\Throwable $exception) {
// Do nothing on purpose
}
if (\is_array($value)) {
return 'Array of length ' . \count($value);
}
if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) {
return $this->serializeString((string) $value);
}
return null;
}
private function formatDate(\DateTimeInterface $date): string
{
$hasMicroseconds = $date->format('u') !== '000000';
$formatted = $hasMicroseconds ? $date->format('Y-m-d H:i:s.u') : $date->format('Y-m-d H:i:s');
$className = \get_class($date);
$timezone = $date->getTimezone();
if ($timezone && $timezone->getName() !== 'UTC') {
$formatted .= ' ' . $date->format('eP');
}
return "$className($formatted)";
}
/**
* @param callable|mixed $callable
*/
protected function serializeCallable($callable): string
{
if (\is_string($callable)) {
return $callable;
}
if (!\is_callable($callable)) {
throw new \InvalidArgumentException(\sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable)));
}
try {
if (\is_array($callable)) {
$reflection = new \ReflectionMethod($callable[0], $callable[1]);
$class = $reflection->getDeclaringClass();
} elseif ($callable instanceof \Closure) {
$reflection = new \ReflectionFunction($callable);
$class = null;
} elseif (\is_object($callable) && method_exists($callable, '__invoke')) {
$reflection = new \ReflectionMethod($callable, '__invoke');
$class = $reflection->getDeclaringClass();
} else {
throw new \InvalidArgumentException('Unrecognized type of callable');
}
} catch (\ReflectionException $exception) {
return '{unserializable callable, reflection error}';
}
$callableType = $reflection->isClosure() ? 'Lambda ' : 'Callable ';
$callableReturnType = $reflection->getReturnType();
if ($callableReturnType instanceof \ReflectionNamedType) {
$callableType .= $callableReturnType->getName() . ' ';
}
if ($class) {
$callableType .= $class->getName() . '::';
}
return $callableType . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection);
}
private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection): string
{
$params = [];
foreach ($reflection->getParameters() as &$param) {
$reflectionType = $param->getType();
if ($reflectionType instanceof \ReflectionNamedType) {
$paramType = $reflectionType->getName();
} else {
$paramType = 'mixed';
}
if ($param->allowsNull()) {
$paramType .= '|null';
}
$paramName = ($param->isPassedByReference() ? '&' : '') . $param->getName();
if ($param->isOptional()) {
$paramName = '[' . $paramName . ']';
}
$params[] = $paramType . ' ' . $paramName;
}
return '[' . implode('; ', $params) . ']';
}
public function getMbDetectOrder(): string
{
return $this->mbDetectOrder;
}
/**
* @return $this
*/
public function setMbDetectOrder(string $mbDetectOrder): self
{
$this->mbDetectOrder = $mbDetectOrder;
return $this;
}
public function setSerializeAllObjects(bool $value): void
{
$this->serializeAllObjects = $value;
}
public function getSerializeAllObjects(): bool
{
return $this->serializeAllObjects;
}
}
sentry/src/Serializer/Traits/StacktraceFrameSeralizerTrait.php 0000644 00000003231 15266563341 0020705 0 ustar 00
*
* @psalm-return array{
* filename: string,
* lineno: int,
* in_app: bool,
* abs_path?: string,
* function?: string,
* raw_function?: string,
* pre_context?: string[],
* context_line?: string,
* post_context?: string[],
* vars?: array
* }
*/
protected static function serializeStacktraceFrame(Frame $frame): array
{
$result = [
'filename' => $frame->getFile(),
'lineno' => $frame->getLine(),
'in_app' => $frame->isInApp(),
];
if ($frame->getAbsoluteFilePath() !== null) {
$result['abs_path'] = $frame->getAbsoluteFilePath();
}
if ($frame->getFunctionName() !== null) {
$result['function'] = $frame->getFunctionName();
}
if ($frame->getRawFunctionName() !== null) {
$result['raw_function'] = $frame->getRawFunctionName();
}
if (!empty($frame->getPreContext())) {
$result['pre_context'] = $frame->getPreContext();
}
if ($frame->getContextLine() !== null) {
$result['context_line'] = $frame->getContextLine();
}
if (!empty($frame->getPostContext())) {
$result['post_context'] = $frame->getPostContext();
}
if (!empty($frame->getVars())) {
$result['vars'] = $frame->getVars();
}
return $result;
}
}
sentry/src/Serializer/Traits/BreadcrumbSeralizerTrait.php 0000644 00000001763 15266563341 0017724 0 ustar 00
*
* @psalm-return array{
* type: string,
* category: string,
* level: string,
* timestamp: float,
* message?: string,
* data?: object
* }
*/
protected static function serializeBreadcrumb(Breadcrumb $breadcrumb): array
{
$result = [
'type' => $breadcrumb->getType(),
'category' => $breadcrumb->getCategory(),
'level' => $breadcrumb->getLevel(),
'timestamp' => $breadcrumb->getTimestamp(),
];
if ($breadcrumb->getMessage() !== null) {
$result['message'] = $breadcrumb->getMessage();
}
if (!empty($breadcrumb->getMetadata())) {
$result['data'] = (object) $breadcrumb->getMetadata();
}
return $result;
}
}
sentry/src/Serializer/RepresentationSerializer.php 0000644 00000002517 15266563341 0016555 0 ustar 00 serializeRecursively($value);
if (is_numeric($value)) {
return (string) $value;
}
if (\is_bool($value)) {
return $value ? 'true' : 'false';
}
return $value;
}
/**
* This method is overridden to return even basic types as strings.
*
* @param mixed $value The value that needs to be serialized
*
* @return string
*/
protected function serializeValue($value)
{
if ($value === null) {
return 'null';
}
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
if (\is_float($value) && (int) $value == $value) {
return $value . '.0';
}
if (is_numeric($value)) {
return (string) $value;
}
return (string) parent::serializeValue($value);
}
}
sentry/src/Serializer/RepresentationSerializerInterface.php 0000644 00000001305 15266563341 0020370 0 ustar 00 'profile',
'content_type' => 'application/json',
];
$profile = $event->getSdkMetadata('profile');
if (!$profile instanceof Profile) {
return null;
}
$payload = $profile->getFormattedData($event);
if ($payload === null) {
return null;
}
return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload));
}
}
sentry/src/Serializer/EnvelopItems/TransactionItem.php 0000644 00000013105 15266563341 0017232 0 ustar 00 ,
* }
*/
class TransactionItem implements EnvelopeItemInterface
{
use BreadcrumbSeralizerTrait;
public static function toEnvelopeItem(Event $event): string
{
$header = [
'type' => (string) EventType::transaction(),
'content_type' => 'application/json',
];
$payload = [
'timestamp' => $event->getTimestamp(),
'platform' => 'php',
'sdk' => $event->getSdkPayload(),
];
if ($event->getStartTimestamp() !== null) {
$payload['start_timestamp'] = $event->getStartTimestamp();
}
if ($event->getLevel() !== null) {
$payload['level'] = (string) $event->getLevel();
}
if ($event->getTransaction() !== null) {
$payload['transaction'] = $event->getTransaction();
}
if ($event->getServerName() !== null) {
$payload['server_name'] = $event->getServerName();
}
if ($event->getRelease() !== null) {
$payload['release'] = $event->getRelease();
}
if ($event->getEnvironment() !== null) {
$payload['environment'] = $event->getEnvironment();
}
if (!empty($event->getFingerprint())) {
$payload['fingerprint'] = $event->getFingerprint();
}
if (!empty($event->getModules())) {
$payload['modules'] = $event->getModules();
}
if (!empty($event->getExtra())) {
$payload['extra'] = $event->getExtra();
}
if (!empty($event->getTags())) {
$payload['tags'] = $event->getTags();
}
$user = $event->getUser();
if ($user !== null) {
$payload['user'] = array_merge($user->getMetadata(), [
'id' => $user->getId(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'ip_address' => $user->getIpAddress(),
'segment' => $user->getSegment(),
]);
}
$osContext = $event->getOsContext();
if ($osContext !== null) {
$payload['contexts']['os'] = [
'name' => $osContext->getName(),
'version' => $osContext->getVersion(),
'build' => $osContext->getBuild(),
'kernel_version' => $osContext->getKernelVersion(),
];
}
$runtimeContext = $event->getRuntimeContext();
if ($runtimeContext !== null) {
$payload['contexts']['runtime'] = [
'name' => $runtimeContext->getName(),
'sapi' => $runtimeContext->getSAPI(),
'version' => $runtimeContext->getVersion(),
];
}
if (!empty($event->getContexts())) {
$payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts());
}
if (!empty($event->getBreadcrumbs())) {
$payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs());
}
if (!empty($event->getRequest())) {
$payload['request'] = $event->getRequest();
}
$payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans()));
$transactionMetadata = $event->getSdkMetadata('transaction_metadata');
if ($transactionMetadata instanceof TransactionMetadata) {
$payload['transaction_info']['source'] = (string) $transactionMetadata->getSource();
}
return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload));
}
/**
* @return array
*
* @psalm-return array{
* span_id: string,
* trace_id: string,
* parent_span_id?: string,
* start_timestamp: float,
* timestamp?: float,
* status?: string,
* description?: string,
* op?: string,
* data?: array,
* tags?: array
* _metrics_summary?: array
* }
*/
protected static function serializeSpan(Span $span): array
{
$result = [
'span_id' => (string) $span->getSpanId(),
'trace_id' => (string) $span->getTraceId(),
'start_timestamp' => $span->getStartTimestamp(),
'origin' => $span->getOrigin() ?? 'manual',
];
if ($span->getParentSpanId() !== null) {
$result['parent_span_id'] = (string) $span->getParentSpanId();
}
if ($span->getEndTimestamp() !== null) {
$result['timestamp'] = $span->getEndTimestamp();
}
if ($span->getStatus() !== null) {
$result['status'] = (string) $span->getStatus();
}
if ($span->getDescription() !== null) {
$result['description'] = $span->getDescription();
}
if ($span->getOp() !== null) {
$result['op'] = $span->getOp();
}
if (!empty($span->getData())) {
$result['data'] = $span->getData();
}
if (!empty($span->getTags())) {
$result['tags'] = $span->getTags();
}
return $result;
}
}
sentry/src/Serializer/EnvelopItems/EnvelopeItemInterface.php 0000644 00000000332 15266563341 0020341 0 ustar 00 (string) $event->getType(),
'content_type' => 'application/json',
];
$payload = [
'timestamp' => $event->getTimestamp(),
'platform' => 'php',
'sdk' => $event->getSdkPayload(),
];
if ($event->getStartTimestamp() !== null) {
$payload['start_timestamp'] = $event->getStartTimestamp();
}
if ($event->getLevel() !== null) {
$payload['level'] = (string) $event->getLevel();
}
if ($event->getLogger() !== null) {
$payload['logger'] = $event->getLogger();
}
if ($event->getTransaction() !== null) {
$payload['transaction'] = $event->getTransaction();
}
if ($event->getServerName() !== null) {
$payload['server_name'] = $event->getServerName();
}
if ($event->getRelease() !== null) {
$payload['release'] = $event->getRelease();
}
if ($event->getEnvironment() !== null) {
$payload['environment'] = $event->getEnvironment();
}
if (!empty($event->getFingerprint())) {
$payload['fingerprint'] = $event->getFingerprint();
}
if (!empty($event->getModules())) {
$payload['modules'] = $event->getModules();
}
if (!empty($event->getExtra())) {
$payload['extra'] = $event->getExtra();
}
if (!empty($event->getTags())) {
$payload['tags'] = $event->getTags();
}
$user = $event->getUser();
if ($user !== null) {
$payload['user'] = array_merge($user->getMetadata(), [
'id' => $user->getId(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'ip_address' => $user->getIpAddress(),
'segment' => $user->getSegment(),
]);
}
$osContext = $event->getOsContext();
if ($osContext !== null) {
$payload['contexts']['os'] = [
'name' => $osContext->getName(),
'version' => $osContext->getVersion(),
'build' => $osContext->getBuild(),
'kernel_version' => $osContext->getKernelVersion(),
];
}
$runtimeContext = $event->getRuntimeContext();
if ($runtimeContext !== null) {
$payload['contexts']['runtime'] = [
'name' => $runtimeContext->getName(),
'sapi' => $runtimeContext->getSAPI(),
'version' => $runtimeContext->getVersion(),
];
}
if (!empty($event->getContexts())) {
$payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts());
}
if (!empty($event->getBreadcrumbs())) {
$payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs());
}
if (!empty($event->getRequest())) {
$payload['request'] = $event->getRequest();
}
if ($event->getMessage() !== null) {
if (empty($event->getMessageParams())) {
$payload['message'] = $event->getMessage();
} else {
$payload['message'] = [
'message' => $event->getMessage(),
'params' => $event->getMessageParams(),
'formatted' => $event->getMessageFormatted() ?? Str::vsprintfOrNull($event->getMessage(), $event->getMessageParams()) ?? $event->getMessage(),
];
}
}
$exceptions = $event->getExceptions();
for ($i = \count($exceptions) - 1; $i >= 0; --$i) {
$payload['exception']['values'][] = self::serializeException($exceptions[$i]);
}
$stacktrace = $event->getStacktrace();
if ($stacktrace !== null) {
$payload['stacktrace'] = [
'frames' => array_map([self::class, 'serializeStacktraceFrame'], $stacktrace->getFrames()),
];
}
return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload));
}
/**
* @return array
*
* @psalm-return array{
* type: string,
* value: string,
* stacktrace?: array{
* frames: array>
* },
* mechanism?: array{
* type: string,
* handled: boolean,
* data?: array
* }
* }
*/
protected static function serializeException(ExceptionDataBag $exception): array
{
$exceptionMechanism = $exception->getMechanism();
$exceptionStacktrace = $exception->getStacktrace();
$result = [
'type' => $exception->getType(),
'value' => $exception->getValue(),
];
if ($exceptionStacktrace !== null) {
$result['stacktrace'] = [
'frames' => array_map([self::class, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()),
];
}
if ($exceptionMechanism !== null) {
$result['mechanism'] = [
'type' => $exceptionMechanism->getType(),
'handled' => $exceptionMechanism->isHandled(),
];
if ($exceptionMechanism->getData() !== []) {
$result['mechanism']['data'] = $exceptionMechanism->getData();
}
}
return $result;
}
}
sentry/src/Serializer/EnvelopItems/CheckInItem.php 0000644 00000002406 15266563341 0016253 0 ustar 00 (string) $event->getType(),
'content_type' => 'application/json',
];
$payload = [];
$checkIn = $event->getCheckIn();
if ($checkIn !== null) {
$payload = [
'check_in_id' => $checkIn->getId(),
'monitor_slug' => $checkIn->getMonitorSlug(),
'status' => (string) $checkIn->getStatus(),
'duration' => $checkIn->getDuration(),
'release' => $checkIn->getRelease(),
'environment' => $checkIn->getEnvironment(),
];
if ($checkIn->getMonitorConfig() !== null) {
$payload['monitor_config'] = $checkIn->getMonitorConfig()->toArray();
}
if (!empty($event->getContexts()['trace'])) {
$payload['contexts']['trace'] = $event->getContexts()['trace'];
}
}
return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload));
}
}
sentry/src/Serializer/EnvelopItems/LogsItem.php 0000644 00000002646 15266563341 0015661 0 ustar 00 getLogs();
$header = [
'type' => (string) EventType::logs(),
'item_count' => \count($logs),
'content_type' => 'application/vnd.sentry.items.log+json',
];
return \sprintf(
"%s\n%s",
JSON::encode($header),
JSON::encode([
'items' => array_map(static function (Log $log): array {
return [
'timestamp' => $log->getTimestamp(),
'trace_id' => $log->getTraceId(),
'level' => (string) $log->getLevel(),
'body' => $log->getBody(),
'attributes' => array_map(static function (Attribute $attribute): array {
return [
'type' => $attribute->getType(),
'value' => $attribute->getValue(),
];
}, $log->attributes()->all()),
];
}, $logs),
])
);
}
}
sentry/src/Serializer/SerializerInterface.php 0000644 00000000577 15266563341 0015457 0 ustar 00 serializeRecursively($value);
}
}
sentry/src/Serializer/PayloadSerializer.php 0000644 00000004612 15266563341 0015142 0 ustar 00 options = $options;
}
/**
* {@inheritdoc}
*/
public function serialize(Event $event): string
{
// @see https://develop.sentry.dev/sdk/envelopes/#envelope-headers
$envelopeHeader = [
'event_id' => (string) $event->getId(),
'sent_at' => gmdate('Y-m-d\TH:i:s\Z'),
'dsn' => (string) $this->options->getDsn(),
'sdk' => $event->getSdkPayload(),
];
$dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context');
if ($dynamicSamplingContext instanceof DynamicSamplingContext) {
$entries = $dynamicSamplingContext->getEntries();
if (!empty($entries)) {
$envelopeHeader['trace'] = $entries;
}
}
$items = [];
switch ($event->getType()) {
case EventType::event():
$items[] = EventItem::toEnvelopeItem($event);
break;
case EventType::transaction():
$items[] = TransactionItem::toEnvelopeItem($event);
if ($event->getSdkMetadata('profile') !== null) {
$items[] = ProfileItem::toEnvelopeItem($event);
}
break;
case EventType::checkIn():
$items[] = CheckInItem::toEnvelopeItem($event);
break;
case EventType::logs():
$items[] = LogsItem::toEnvelopeItem($event);
break;
}
return \sprintf("%s\n%s", JSON::encode($envelopeHeader), implode("\n", array_filter($items)));
}
}
sentry/src/Exception/JsonException.php 0000644 00000000157 15266563341 0014134 0 ustar 00
*/
final class FatalErrorException extends \ErrorException
{
}
sentry/src/Metrics/Metrics.php 0000644 00000005474 15266563341 0012431 0 ustar 00 $tags
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function increment(
string $key,
float $value,
?MetricsUnit $unit = null,
array $tags = [],
?int $timestamp = null,
int $stackLevel = 0
): void {
}
/**
* @param array $tags
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function distribution(
string $key,
float $value,
?MetricsUnit $unit = null,
array $tags = [],
?int $timestamp = null,
int $stackLevel = 0
): void {
}
/**
* @param array $tags
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function gauge(
string $key,
float $value,
?MetricsUnit $unit = null,
array $tags = [],
?int $timestamp = null,
int $stackLevel = 0
): void {
}
/**
* @param int|string $value
* @param array $tags
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function set(
string $key,
$value,
?MetricsUnit $unit = null,
array $tags = [],
?int $timestamp = null,
int $stackLevel = 0
): void {
}
/**
* @template T
*
* @param callable(): T $callback
* @param array $tags
*
* @return T
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function timing(
string $key,
callable $callback,
array $tags = [],
int $stackLevel = 0
) {
return trace(
function () use ($callback) {
return $callback();
},
SpanContext::make()
->setOp('metric.timing')
->setOrigin('auto.measure.metrics.timing')
->setDescription($key)
);
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function flush(): ?EventId
{
return null;
}
}
sentry/src/Metrics/MetricsUnit.php 0000644 00000006567 15266563341 0013275 0 ustar 00 A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
public static function nanosecond(): self
{
return self::getInstance('nanosecond');
}
public static function microsecond(): self
{
return self::getInstance('microsecond');
}
public static function millisecond(): self
{
return self::getInstance('millisecond');
}
public static function second(): self
{
return self::getInstance('second');
}
public static function minute(): self
{
return self::getInstance('minute');
}
public static function hour(): self
{
return self::getInstance('hour');
}
public static function day(): self
{
return self::getInstance('day');
}
public static function week(): self
{
return self::getInstance('week');
}
public static function bit(): self
{
return self::getInstance('bit');
}
public static function byte(): self
{
return self::getInstance('byte');
}
public static function kilobyte(): self
{
return self::getInstance('kilobyte');
}
public static function kibibyte(): self
{
return self::getInstance('kibibyte');
}
public static function megabyte(): self
{
return self::getInstance('megabyte');
}
public static function mebibyte(): self
{
return self::getInstance('mebibyte');
}
public static function gigabyte(): self
{
return self::getInstance('gigabyte');
}
public static function gibibyte(): self
{
return self::getInstance('gibibyte');
}
public static function terabyte(): self
{
return self::getInstance('terabyte');
}
public static function tebibyte(): self
{
return self::getInstance('tebibyte');
}
public static function petabyte(): self
{
return self::getInstance('petabyte');
}
public static function pebibyte(): self
{
return self::getInstance('pebibyte');
}
public static function exabyte(): self
{
return self::getInstance('exabyte');
}
public static function exbibyte(): self
{
return self::getInstance('exbibyte');
}
public static function ratio(): self
{
return self::getInstance('ratio');
}
public static function percent(): self
{
return self::getInstance('percent');
}
public static function none(): self
{
return self::getInstance('none');
}
public static function custom(string $unit): self
{
return new self($unit);
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/Monolog/LogsHandler.php 0000644 00000006622 15266563341 0013225 0 ustar 00 logLevel = $logLevel ?? LogLevel::debug();
$this->bubble = $bubble;
}
/**
* @param array|LogRecord $record
*/
public function isHandling($record): bool
{
return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority();
}
/**
* @param array|LogRecord $record
*/
public function handle($record): bool
{
if (!$this->isHandling($record)) {
return false;
}
// Do not collect logs for exceptions, they should be handled seperately by the `Handler` or `captureException`
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
return false;
}
Logs::getInstance()->aggregator()->add(
self::getSentryLogLevelFromMonologLevel($record['level']),
$record['message'],
[],
array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.log.monolog'])
);
return $this->bubble === false;
}
/**
* @param array|LogRecord> $records
*/
public function handleBatch(array $records): void
{
foreach ($records as $record) {
$this->handle($record);
}
}
public function close(): void
{
Logs::getInstance()->flush();
}
/**
* @param callable $callback
*/
public function pushProcessor($callback): void
{
// noop, this handler does not support processors
}
/**
* @return callable
*/
public function popProcessor()
{
// Since we do not support processors, we throw an exception if this method is called
throw new \LogicException('You tried to pop from an empty processor stack.');
}
public function setFormatter(FormatterInterface $formatter): void
{
// noop, this handler does not support formatters
}
public function getFormatter(): FormatterInterface
{
// To adhere to the interface we need to return a formatter so we return a default one
return new LineFormatter();
}
public function __destruct()
{
try {
$this->close();
} catch (\Throwable $e) {
// Just in case so that the destructor can never fail.
}
}
}
sentry/src/Monolog/BreadcrumbHandler.php 0000644 00000005446 15266563341 0014372 0 ustar 00 hub = $hub;
parent::__construct($level, $bubble);
}
/**
* @psalm-suppress MoreSpecificImplementedParamType
*
* @param LogRecord|array{
* level: int,
* channel: string,
* datetime: \DateTimeImmutable,
* message: string,
* extra?: array
* } $record {@see https://github.com/Seldaek/monolog/blob/main/doc/message-structure.md}
*/
protected function write($record): void
{
$breadcrumb = new Breadcrumb(
$this->getBreadcrumbLevel($record['level']),
$this->getBreadcrumbType($record['level']),
$record['channel'],
$record['message'],
($record['context'] ?? []) + ($record['extra'] ?? []),
$record['datetime']->getTimestamp()
);
$this->hub->addBreadcrumb($breadcrumb);
}
/**
* @param Level|int $level
*/
private function getBreadcrumbLevel($level): string
{
if ($level instanceof Level) {
$level = $level->value;
}
switch ($level) {
case Logger::DEBUG:
return Breadcrumb::LEVEL_DEBUG;
case Logger::INFO:
case Logger::NOTICE:
return Breadcrumb::LEVEL_INFO;
case Logger::WARNING:
return Breadcrumb::LEVEL_WARNING;
case Logger::ERROR:
return Breadcrumb::LEVEL_ERROR;
default:
return Breadcrumb::LEVEL_FATAL;
}
}
private function getBreadcrumbType(int $level): string
{
if ($level >= Logger::ERROR) {
return Breadcrumb::TYPE_ERROR;
}
return Breadcrumb::TYPE_DEFAULT;
}
}
sentry/src/Monolog/Handler.php 0000644 00000006362 15266563341 0012401 0 ustar 00
*/
final class Handler extends AbstractProcessingHandler
{
use CompatibilityProcessingHandlerTrait;
private const CONTEXT_EXCEPTION_KEY = 'exception';
/**
* @var HubInterface
*/
private $hub;
/**
* @var bool
*/
private $fillExtraContext;
/**
* {@inheritdoc}
*
* @param HubInterface $hub The hub to which errors are reported
*/
public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true, bool $fillExtraContext = false)
{
parent::__construct($level, $bubble);
$this->hub = $hub;
$this->fillExtraContext = $fillExtraContext;
}
/**
* @param array|LogRecord $record
*/
protected function doWrite($record): void
{
$event = Event::createEvent();
$event->setLevel(self::getSeverityFromLevel($record['level']));
$event->setMessage($record['message']);
$event->setLogger(\sprintf('monolog.%s', $record['channel']));
$hint = new EventHint();
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
$hint->exception = $record['context']['exception'];
}
$this->hub->withScope(function (Scope $scope) use ($record, $event, $hint): void {
$scope->setExtra('monolog.channel', $record['channel']);
$scope->setExtra('monolog.level', $record['level_name']);
$monologContextData = $this->getMonologContextData($record['context']);
if ($monologContextData !== []) {
$scope->setExtra('monolog.context', $monologContextData);
}
$monologExtraData = $this->getMonologExtraData($record['extra']);
if ($monologExtraData !== []) {
$scope->setExtra('monolog.extra', $monologExtraData);
}
$this->hub->captureEvent($event, $hint);
});
}
/**
* @param mixed[] $context
*
* @return mixed[]
*/
private function getMonologContextData(array $context): array
{
if (!$this->fillExtraContext) {
return [];
}
$contextData = [];
foreach ($context as $key => $value) {
// We skip the `exception` field because it goes in its own context
if ($key === self::CONTEXT_EXCEPTION_KEY) {
continue;
}
$contextData[$key] = $value;
}
return $contextData;
}
/**
* @param mixed[] $context
*
* @return mixed[]
*/
private function getMonologExtraData(array $context): array
{
if (!$this->fillExtraContext) {
return [];
}
$extraData = [];
foreach ($context as $key => $value) {
$extraData[$key] = $value;
}
return $extraData;
}
}
sentry/src/Monolog/CompatibilityProcessingHandlerTrait.php 0000644 00000005474 15266563341 0020177 0 ustar 00 = 3) {
/**
* Logic which is used if monolog >= 3 is installed.
*
* @internal
*/
trait CompatibilityProcessingHandlerTrait
{
/**
* @param array|LogRecord $record
*/
abstract protected function doWrite($record): void;
/**
* {@inheritdoc}
*/
protected function write(LogRecord $record): void
{
$this->doWrite($record);
}
/**
* Translates the Monolog level into the Sentry severity.
*/
private static function getSeverityFromLevel(int $level): Severity
{
$level = Level::from($level);
switch ($level) {
case Level::Debug:
return Severity::debug();
case Level::Warning:
return Severity::warning();
case Level::Error:
return Severity::error();
case Level::Critical:
case Level::Alert:
case Level::Emergency:
return Severity::fatal();
case Level::Info:
case Level::Notice:
default:
return Severity::info();
}
}
}
} else {
/**
* Logic which is used if monolog < 3 is installed.
*
* @internal
*/
trait CompatibilityProcessingHandlerTrait
{
/**
* @param array|LogRecord $record
*/
abstract protected function doWrite($record): void;
/**
* {@inheritdoc}
*/
protected function write(array $record): void
{
$this->doWrite($record);
}
/**
* Translates the Monolog level into the Sentry severity.
*
* @param Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY $level The Monolog log level
*/
private static function getSeverityFromLevel(int $level): Severity
{
switch ($level) {
case Logger::DEBUG:
return Severity::debug();
case Logger::WARNING:
return Severity::warning();
case Logger::ERROR:
return Severity::error();
case Logger::CRITICAL:
case Logger::ALERT:
case Logger::EMERGENCY:
return Severity::fatal();
case Logger::INFO:
case Logger::NOTICE:
default:
return Severity::info();
}
}
}
}
sentry/src/Monolog/CompatibilityLogLevelTrait.php 0000644 00000004312 15266563341 0016264 0 ustar 00 = 3) {
/**
* Logic which is used if monolog >= 3 is installed.
*
* @internal
*/
trait CompatibilityLogLevelTrait
{
/**
* Translates the Monolog level into the Sentry LogLevel.
*/
private static function getSentryLogLevelFromMonologLevel(int $level): LogLevel
{
$level = Level::from($level);
switch ($level) {
case Level::Debug:
return LogLevel::debug();
case Level::Warning:
return LogLevel::warn();
case Level::Error:
return LogLevel::error();
case Level::Critical:
case Level::Alert:
case Level::Emergency:
return LogLevel::fatal();
case Level::Info:
case Level::Notice:
default:
return LogLevel::info();
}
}
}
} else {
/**
* Logic which is used if monolog < 3 is installed.
*
* @internal
*/
trait CompatibilityLogLevelTrait
{
/**
* Translates the Monolog level into the Sentry LogLevel.
*
* @param Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY $level The Monolog log level
*/
private static function getSentryLogLevelFromMonologLevel(int $level): LogLevel
{
switch ($level) {
case Logger::DEBUG:
return LogLevel::debug();
case Logger::WARNING:
return LogLevel::warn();
case Logger::ERROR:
return LogLevel::error();
case Logger::CRITICAL:
case Logger::ALERT:
case Logger::EMERGENCY:
return LogLevel::fatal();
case Logger::INFO:
case Logger::NOTICE:
default:
return LogLevel::info();
}
}
}
}
sentry/src/StacktraceBuilder.php 0000644 00000005223 15266563341 0013000 0 ustar 00 frameBuilder = new FrameBuilder($options, $representationSerializer);
if (PHPConfiguration::isBooleanIniOptionEnabled('zend.exception_ignore_args')) {
$options->getLoggerOrNullLogger()->warning('The "zend.exception_ignore_args" PHP setting is enabled which results in missing stack trace arguments, see: https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces.');
}
}
/**
* Builds a {@see Stacktrace} object from the given exception.
*
* @param \Throwable $exception The exception object
*/
public function buildFromException(\Throwable $exception): Stacktrace
{
return $this->buildFromBacktrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
}
/**
* Builds a {@see Stacktrace} object from the given backtrace.
*
* @param array> $backtrace The backtrace
* @param string $file The file where the backtrace originated from
* @param int $line The line from which the backtrace originated from
*
* @psalm-param list $backtrace
*/
public function buildFromBacktrace(array $backtrace, string $file, int $line): Stacktrace
{
$frames = [];
foreach ($backtrace as $backtraceFrame) {
array_unshift($frames, $this->frameBuilder->buildFromBacktraceFrame($file, $line, $backtraceFrame));
$file = $backtraceFrame['file'] ?? Frame::INTERNAL_FRAME_FILENAME;
$line = $backtraceFrame['line'] ?? 0;
}
// Add a final stackframe for the first method ever of this stacktrace
array_unshift($frames, $this->frameBuilder->buildFromBacktraceFrame($file, $line, []));
return new Stacktrace($frames);
}
}
sentry/src/Transport/ResultStatus.php 0000644 00000005770 15266563341 0014072 0 ustar 00
*/
private static $instances = [];
/**
* Constructor.
*
* @param string $value The value of the enum instance
*/
private function __construct(string $value)
{
$this->value = $value;
}
/**
* Returns an instance of this enum representing the fact that the event
* failed to be sent due to unknown reasons.
*/
public static function unknown(): self
{
return self::getInstance('UNKNOWN');
}
/**
* Returns an instance of this enum representing the fact that event was
* skipped from being sent.
*/
public static function skipped(): self
{
return self::getInstance('SKIPPED');
}
/**
* Returns an instance of this enum representing the fact that the event
* was sent successfully.
*/
public static function success(): self
{
return self::getInstance('SUCCESS');
}
/**
* Returns an instance of this enum representing the fact that the event
* failed to be sent because of API rate limiting.
*/
public static function rateLimit(): self
{
return self::getInstance('RATE_LIMIT');
}
/**
* Returns an instance of this enum representing the fact that the event
* failed to be sent because the server was not able to process the request.
*/
public static function invalid(): self
{
return self::getInstance('INVALID');
}
/**
* Returns an instance of this enum representing the fact that the event
* failed to be sent because the server returned a server error.
*/
public static function failed(): self
{
return self::getInstance('FAILED');
}
/**
* Returns an instance of this enum according to the given HTTP status code.
*
* @param int $statusCode The HTTP status code
*/
public static function createFromHttpStatusCode(int $statusCode): self
{
switch (true) {
case $statusCode >= 200 && $statusCode < 300:
return self::success();
case $statusCode === 429:
return self::rateLimit();
case $statusCode >= 400 && $statusCode < 500:
return self::invalid();
case $statusCode >= 500:
return self::failed();
default:
return self::unknown();
}
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/Transport/Result.php 0000644 00000002010 15266563341 0012646 0 ustar 00 status = $status;
$this->event = $event;
}
/**
* Gets the status of the sending operation of the event.
*/
public function getStatus(): ResultStatus
{
return $this->status;
}
/**
* Gets the instance of the event being sent, or null if it was not available yet.
*/
public function getEvent(): ?Event
{
return $this->event;
}
}
sentry/src/Transport/RateLimiter.php 0000644 00000010236 15266563341 0013622 0 ustar 00 The map of time instants for each event category after
* which an HTTP request can be retried
*/
private $rateLimits = [];
/**
* @var LoggerInterface A PSR-3 logger
*/
private $logger;
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?? new NullLogger();
}
public function handleResponse(Response $response): bool
{
$now = time();
if ($response->hasHeader(self::RATE_LIMITS_HEADER)) {
foreach (explode(',', $response->getHeaderLine(self::RATE_LIMITS_HEADER)) as $limit) {
/**
* $parameters[0] - retry_after
* $parameters[1] - categories
* $parameters[2] - scope (not used)
* $parameters[3] - reason_code (not used)
* $parameters[4] - namespaces (only returned if categories contains "metric_bucket").
*/
$parameters = explode(':', $limit, 5);
$retryAfter = $now + (ctype_digit($parameters[0]) ? (int) $parameters[0] : self::DEFAULT_RETRY_AFTER_SECONDS);
foreach (explode(';', $parameters[1]) as $category) {
$this->rateLimits[$category ?: 'all'] = $retryAfter;
$this->logger->warning(
\sprintf('Rate limited exceeded for category "%s", backing off until "%s".', $category, gmdate(\DATE_ATOM, $retryAfter))
);
}
}
return $this->rateLimits !== [];
}
if ($response->hasHeader(self::RETRY_AFTER_HEADER)) {
$retryAfter = $now + $this->parseRetryAfterHeader($now, $response->getHeaderLine(self::RETRY_AFTER_HEADER));
$this->rateLimits['all'] = $retryAfter;
$this->logger->warning(
\sprintf('Rate limited exceeded for all categories, backing off until "%s".', gmdate(\DATE_ATOM, $retryAfter))
);
return true;
}
return false;
}
/**
* @param string|EventType $eventType
*/
public function isRateLimited($eventType): bool
{
$disabledUntil = $this->getDisabledUntil($eventType);
return $disabledUntil > time();
}
/**
* @param string|EventType $eventType
*/
public function getDisabledUntil($eventType): int
{
$eventType = $eventType instanceof EventType ? (string) $eventType : $eventType;
if ($eventType === 'event') {
$eventType = self::DATA_CATEGORY_ERROR;
} elseif ($eventType === 'log') {
$eventType = self::DATA_CATEGORY_LOG_ITEM;
}
return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$eventType] ?? 0);
}
private function parseRetryAfterHeader(int $currentTime, string $header): int
{
if (preg_match('/^\d+$/', $header) === 1) {
return (int) $header;
}
$headerDate = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC1123, $header);
if ($headerDate !== false && $headerDate->getTimestamp() >= $currentTime) {
return $headerDate->getTimestamp() - $currentTime;
}
return self::DEFAULT_RETRY_AFTER_SECONDS;
}
}
sentry/src/Transport/HttpTransport.php 0000644 00000012652 15266563341 0014241 0 ustar 00 options = $options;
$this->httpClient = $httpClient;
$this->payloadSerializer = $payloadSerializer;
$this->logger = $logger ?? new NullLogger();
$this->rateLimiter = new RateLimiter($this->logger);
}
/**
* {@inheritdoc}
*/
public function send(Event $event): Result
{
$this->sendRequestToSpotlight($event);
$eventDescription = \sprintf(
'%s%s [%s]',
$event->getLevel() !== null ? $event->getLevel() . ' ' : '',
(string) $event->getType(),
(string) $event->getId()
);
if ($this->options->getDsn() === null) {
$this->logger->info(\sprintf('Skipping %s, because no DSN is set.', $eventDescription), ['event' => $event]);
return new Result(ResultStatus::skipped(), $event);
}
$targetDescription = \sprintf(
'%s [project:%s]',
$this->options->getDsn()->getHost(),
$this->options->getDsn()->getProjectId()
);
$this->logger->info(\sprintf('Sending %s to %s.', $eventDescription, $targetDescription), ['event' => $event]);
$eventType = $event->getType();
if ($this->rateLimiter->isRateLimited((string) $eventType)) {
$this->logger->warning(
\sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType),
['event' => $event]
);
return new Result(ResultStatus::rateLimit());
}
$request = new Request();
$request->setStringBody($this->payloadSerializer->serialize($event));
try {
$response = $this->httpClient->sendRequest($request, $this->options);
} catch (\Throwable $exception) {
$this->logger->error(
\sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $exception->getMessage()),
['exception' => $exception, 'event' => $event]
);
return new Result(ResultStatus::failed());
}
if ($response->hasError()) {
$this->logger->error(
\sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $response->getError()),
['event' => $event]
);
return new Result(ResultStatus::unknown());
}
$this->rateLimiter->handleResponse($response);
$resultStatus = ResultStatus::createFromHttpStatusCode($response->getStatusCode());
$this->logger->info(
\sprintf('Sent %s to %s. Result: "%s" (status: %s).', $eventDescription, $targetDescription, strtolower((string) $resultStatus), $response->getStatusCode()),
['response' => $response, 'event' => $event]
);
return new Result($resultStatus, $event);
}
/**
* {@inheritdoc}
*/
public function close(?int $timeout = null): Result
{
return new Result(ResultStatus::success());
}
/**
* @internal
*/
public function getHttpClient(): HttpClientInterface
{
return $this->httpClient;
}
private function sendRequestToSpotlight(Event $event): void
{
if (!$this->options->isSpotlightEnabled()) {
return;
}
$request = new Request();
$request->setStringBody($this->payloadSerializer->serialize($event));
try {
$spotLightResponse = SpotlightClient::sendRequest(
$request,
$this->options->getSpotlightUrl() . '/stream'
);
if ($spotLightResponse->hasError()) {
$this->logger->info(
\sprintf('Failed to send the event to Spotlight. Reason: "%s".', $spotLightResponse->getError()),
['event' => $event]
);
}
} catch (\Throwable $exception) {
$this->logger->info(
\sprintf('Failed to send the event to Spotlight. Reason: "%s".', $exception->getMessage()),
['exception' => $exception, 'event' => $event]
);
}
}
}
sentry/src/Transport/TransportInterface.php 0000644 00000000334 15266563341 0015214 0 ustar 00 A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
public static function minute(): self
{
return self::getInstance('minute');
}
public static function hour(): self
{
return self::getInstance('hour');
}
public static function day(): self
{
return self::getInstance('day');
}
public static function week(): self
{
return self::getInstance('week');
}
public static function month(): self
{
return self::getInstance('month');
}
public static function year(): self
{
return self::getInstance('year');
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/functions.php 0000644 00000031073 15266563341 0011417 0 ustar 00 ,
* ignore_transactions?: array,
* in_app_exclude?: array,
* in_app_include?: array,
* integrations?: IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[],
* logger?: LoggerInterface|null,
* max_breadcrumbs?: int,
* max_request_body_size?: "none"|"never"|"small"|"medium"|"always",
* max_value_length?: int,
* org_id?: int|null,
* prefixes?: array,
* profiles_sample_rate?: int|float|null,
* release?: string|null,
* sample_rate?: float|int,
* send_attempts?: int,
* send_default_pii?: bool,
* server_name?: string,
* spotlight?: bool,
* spotlight_url?: string,
* strict_trace_propagation?: bool,
* tags?: array,
* trace_propagation_targets?: array|null,
* traces_sample_rate?: float|int|null,
* traces_sampler?: callable|null,
* transport?: callable,
* } $options The client options
*/
function init(array $options = []): void
{
$client = ClientBuilder::create($options)->getClient();
SentrySdk::init()->bindClient($client);
}
/**
* Captures a message event and sends it to Sentry.
*
* @param string $message The message
* @param Severity|null $level The severity level of the message
* @param EventHint|null $hint Object that can contain additional information about the event
*/
function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint);
}
/**
* Captures an exception event and sends it to Sentry.
*
* @param \Throwable $exception The exception
* @param EventHint|null $hint Object that can contain additional information about the event
*/
function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureException($exception, $hint);
}
/**
* Captures a new event using the provided data.
*
* @param Event $event The event being captured
* @param EventHint|null $hint May contain additional information about the event
*/
function captureEvent(Event $event, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureEvent($event, $hint);
}
/**
* Logs the most recent error (obtained with {@see error_get_last()}).
*
* @param EventHint|null $hint Object that can contain additional information about the event
*/
function captureLastError(?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureLastError($hint);
}
/**
* Captures a check-in and sends it to Sentry.
*
* @param string $slug Identifier of the Monitor
* @param CheckInStatus $status The status of the check-in
* @param int|float|null $duration The duration of the check-in
* @param MonitorConfig|null $monitorConfig Configuration of the Monitor
* @param string|null $checkInId A check-in ID from the previous check-in
*/
function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string
{
return SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId);
}
/**
* Execute the given callable while wrapping it in a monitor check-in.
*
* @param string $slug Identifier of the Monitor
* @param callable $callback The callable that is going to be monitored
* @param MonitorConfig|null $monitorConfig Configuration of the Monitor
*
* @return mixed
*/
function withMonitor(string $slug, callable $callback, ?MonitorConfig $monitorConfig = null)
{
$checkInId = SentrySdk::getCurrentHub()->captureCheckIn($slug, CheckInStatus::inProgress(), null, $monitorConfig);
$status = CheckInStatus::ok();
$duration = 0;
try {
$start = microtime(true);
$result = $callback();
$duration = microtime(true) - $start;
return $result;
} catch (\Throwable $e) {
$status = CheckInStatus::error();
throw $e;
} finally {
SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId);
}
}
/**
* Records a new breadcrumb which will be attached to future events. They
* will be added to subsequent events to provide more context on user's
* actions prior to an error or crash.
*
* @param Breadcrumb|string $category The category of the breadcrumb, can be a Breadcrumb instance as well (in which case the other parameters are ignored)
* @param string|null $message Breadcrumb message
* @param array $metadata Additional information about the breadcrumb
* @param string $level The error level of the breadcrumb
* @param string $type The type of the breadcrumb
* @param float|null $timestamp Optional timestamp of the breadcrumb
*/
function addBreadcrumb($category, ?string $message = null, array $metadata = [], string $level = Breadcrumb::LEVEL_INFO, string $type = Breadcrumb::TYPE_DEFAULT, ?float $timestamp = null): void
{
SentrySdk::getCurrentHub()->addBreadcrumb(
$category instanceof Breadcrumb
? $category
: new Breadcrumb($level, $type, $category, $message, $metadata, $timestamp)
);
}
/**
* Calls the given callback passing to it the current scope so that any
* operation can be run within its context.
*
* @param callable $callback The callback to be executed
*/
function configureScope(callable $callback): void
{
SentrySdk::getCurrentHub()->configureScope($callback);
}
/**
* Creates a new scope with and executes the given operation within. The scope
* is automatically removed once the operation finishes or throws.
*
* @param callable $callback The callback to be executed
*
* @psalm-template T
*
* @psalm-param callable(Scope): T $callback
*
* @return mixed|void The callback's return value, upon successful execution
*
* @psalm-return T
*/
function withScope(callable $callback)
{
return SentrySdk::getCurrentHub()->withScope($callback);
}
/**
* Starts a new `Transaction` and returns it. This is the entry point to manual
* tracing instrumentation.
*
* A tree structure can be built by adding child spans to the transaction, and
* child spans to other spans. To start a new child span within the transaction
* or any span, call the respective `startChild()` method.
*
* Every child span must be finished before the transaction is finished,
* otherwise the unfinished spans are discarded.
*
* The transaction must be finished with a call to its `finish()` method, at
* which point the transaction with all its finished child spans will be sent to
* Sentry.
*
* @param TransactionContext $context Properties of the new transaction
* @param array $customSamplingContext Additional context that will be passed to the {@see Tracing\SamplingContext}
*/
function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction
{
return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext);
}
/**
* Execute the given callable while wrapping it in a span added as a child to the current transaction and active span.
* If there is no transaction active this is a no-op and the scope passed to the trace callable will be unused.
*
* @template T
*
* @param callable(Scope): T $trace The callable that is going to be traced
* @param SpanContext $context The context of the span to be created
*
* @return T
*/
function trace(callable $trace, SpanContext $context)
{
return SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($context, $trace) {
$parentSpan = $scope->getSpan();
// If there is a span set on the scope and it's sampled there is an active transaction.
// If that is the case we create the child span and set it on the scope.
// Otherwise we only execute the callable without creating a span.
if ($parentSpan !== null && $parentSpan->getSampled()) {
$span = $parentSpan->startChild($context);
$scope->setSpan($span);
}
try {
return $trace($scope);
} finally {
if (isset($span)) {
$span->finish();
$scope->setSpan($parentSpan);
}
}
});
}
/**
* Creates the current Sentry traceparent string, to be used as a HTTP header value
* or HTML meta tag value.
* This function is context aware, as in it either returns the traceparent based
* on the current span, or the scope's propagation context.
*/
function getTraceparent(): string
{
$hub = SentrySdk::getCurrentHub();
$client = $hub->getClient();
if ($client !== null) {
$options = $client->getOptions();
if ($options !== null && $options->isTracingEnabled()) {
$span = SentrySdk::getCurrentHub()->getSpan();
if ($span !== null) {
return $span->toTraceparent();
}
}
}
$traceParent = '';
$hub->configureScope(function (Scope $scope) use (&$traceParent) {
$traceParent = $scope->getPropagationContext()->toTraceparent();
});
return $traceParent;
}
/**
* Creates the current W3C traceparent string, to be used as a HTTP header value
* or HTML meta tag value.
* This function is context aware, as in it either returns the traceparent based
* on the current span, or the scope's propagation context.
*
* @deprecated since version 4.12. To be removed in version 5.0.
*/
function getW3CTraceparent(): string
{
return '';
}
/**
* Creates the baggage content string, to be used as a HTTP header value
* or HTML meta tag value.
* This function is context aware, as in it either returns the baggage based
* on the current span or the scope's propagation context.
*/
function getBaggage(): string
{
$hub = SentrySdk::getCurrentHub();
$client = $hub->getClient();
if ($client !== null) {
$options = $client->getOptions();
if ($options !== null && $options->isTracingEnabled()) {
$span = SentrySdk::getCurrentHub()->getSpan();
if ($span !== null) {
return $span->toBaggage();
}
}
}
$baggage = '';
$hub->configureScope(function (Scope $scope) use (&$baggage) {
$baggage = $scope->getPropagationContext()->toBaggage();
});
return $baggage;
}
/**
* Continue a trace based on HTTP header values.
* If the SDK is configured with enabled tracing,
* this function returns a populated TransactionContext.
* In any other cases, it populates the propagation context on the scope.
*/
function continueTrace(string $sentryTrace, string $baggage): TransactionContext
{
$hub = SentrySdk::getCurrentHub();
$hub->configureScope(function (Scope $scope) use ($sentryTrace, $baggage) {
$propagationContext = PropagationContext::fromHeaders($sentryTrace, $baggage);
$scope->setPropagationContext($propagationContext);
});
return TransactionContext::fromHeaders($sentryTrace, $baggage);
}
/**
* Get the Sentry Logs client.
*/
function logger(): Logs
{
return Logs::getInstance();
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
function metrics(): Metrics
{
return Metrics::getInstance();
}
sentry/src/MonitorConfig.php 0000644 00000006243 15266563341 0012165 0 ustar 00 schedule = $schedule;
$this->checkinMargin = $checkinMargin;
$this->maxRuntime = $maxRuntime;
$this->timezone = $timezone;
$this->failureIssueThreshold = $failureIssueThreshold;
$this->recoveryThreshold = $recoveryThreshold;
}
public function getSchedule(): MonitorSchedule
{
return $this->schedule;
}
public function setSchedule(MonitorSchedule $schedule): self
{
$this->schedule = $schedule;
return $this;
}
public function getCheckinMargin(): ?int
{
return $this->checkinMargin;
}
public function setCheckinMargin(?int $checkinMargin): self
{
$this->checkinMargin = $checkinMargin;
return $this;
}
public function getMaxRuntime(): ?int
{
return $this->maxRuntime;
}
public function setMaxRuntime(?int $maxRuntime): self
{
$this->maxRuntime = $maxRuntime;
return $this;
}
public function getTimezone(): ?string
{
return $this->timezone;
}
public function setTimezone(?string $timezone): self
{
$this->timezone = $timezone;
return $this;
}
public function getFailureRecoveryThreshold(): ?int
{
return $this->failureIssueThreshold;
}
public function setFailureRecoveryThreshold(?int $failureIssueThreshold): self
{
$this->failureIssueThreshold = $failureIssueThreshold;
return $this;
}
public function getRecoveryThreshold(): ?int
{
return $this->recoveryThreshold;
}
public function setRecoveryThreshold(?int $recoveryThreshold): self
{
$this->recoveryThreshold = $recoveryThreshold;
return $this;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'schedule' => $this->schedule->toArray(),
'checkin_margin' => $this->checkinMargin,
'max_runtime' => $this->maxRuntime,
'timezone' => $this->timezone,
'failure_issue_threshold' => $this->failureIssueThreshold,
'recovery_threshold' => $this->recoveryThreshold,
];
}
}
sentry/src/Context/RuntimeContext.php 0000644 00000004210 15266563341 0014014 0 ustar 00
*/
final class RuntimeContext
{
/**
* @var string The name of the runtime
*/
private $name;
/**
* @var string|null The version of the runtime
*/
private $version;
/**
* @var string|null The SAPI (Server API) name
*/
private $sapi;
/**
* Constructor.
*
* @param string $name The name of the runtime
* @param string|null $version The version of the runtime
* @param string|null $sapi The SAPI name of the runtime
*/
public function __construct(string $name, ?string $version = null, ?string $sapi = null)
{
if (trim($name) === '') {
throw new \InvalidArgumentException('The $name argument cannot be an empty string.');
}
$this->name = $name;
$this->version = $version;
$this->sapi = $sapi;
}
/**
* Gets the name of the runtime.
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets the name of the runtime.
*
* @param string $name The name
*/
public function setName(string $name): void
{
if (trim($name) === '') {
throw new \InvalidArgumentException('The $name argument cannot be an empty string.');
}
$this->name = $name;
}
/**
* Gets the version of the runtime.
*/
public function getVersion(): ?string
{
return $this->version;
}
/**
* Sets the version of the runtime.
*
* @param string|null $version The version
*/
public function setVersion(?string $version): void
{
$this->version = $version;
}
/**
* Gets the SAPI of the runtime.
*/
public function getSAPI(): ?string
{
return $this->sapi;
}
/**
* Sets the SAPI of the runtime.
*
* @param string|null $sapi The SAPI name
*/
public function setSAPI(?string $sapi): void
{
$this->sapi = $sapi;
}
}
sentry/src/Context/OsContext.php 0000644 00000007013 15266563341 0012756 0 ustar 00
*/
final class OsContext
{
/**
* @var string The name of the operating system
*/
private $name;
/**
* @var string|null The version of the operating system
*/
private $version;
/**
* @var string|null The internal build revision of the operating system
*/
private $build;
/**
* @var string|null An independent kernel version string
*/
private $kernelVersion;
/**
* @var string|null
*/
private $machineType;
/**
* Constructor.
*
* @param string $name The name of the operating system
* @param string|null $version The version of the operating system
* @param string|null $build The internal build revision of the operating system
* @param string|null $kernelVersion An independent kernel version string
* @param string|null $machineType The machine type
*/
public function __construct(
string $name,
?string $version = null,
?string $build = null,
?string $kernelVersion = null,
?string $machineType = null
) {
if (trim($name) === '') {
throw new \InvalidArgumentException('The $name argument cannot be an empty string.');
}
$this->name = $name;
$this->version = $version;
$this->build = $build;
$this->kernelVersion = $kernelVersion;
$this->machineType = $machineType;
}
/**
* Gets the name of the operating system.
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets the name of the operating system.
*
* @param string $name The name
*/
public function setName(string $name): void
{
if (trim($name) === '') {
throw new \InvalidArgumentException('The $name argument cannot be an empty string.');
}
$this->name = $name;
}
/**
* Gets the version of the operating system.
*/
public function getVersion(): ?string
{
return $this->version;
}
/**
* Sets the version of the operating system.
*
* @param string|null $version The version
*/
public function setVersion(?string $version): void
{
$this->version = $version;
}
/**
* Gets the build of the operating system.
*/
public function getBuild(): ?string
{
return $this->build;
}
/**
* Sets the build of the operating system.
*
* @param string|null $build The build
*/
public function setBuild(?string $build): void
{
$this->build = $build;
}
/**
* Gets the version of the kernel of the operating system.
*/
public function getKernelVersion(): ?string
{
return $this->kernelVersion;
}
/**
* Sets the version of the kernel of the operating system.
*
* @param string|null $kernelVersion The kernel version
*/
public function setKernelVersion(?string $kernelVersion): void
{
$this->kernelVersion = $kernelVersion;
}
public function getMachineType(): ?string
{
return $this->machineType;
}
/**
* @param string|null $machineType The machine type
*/
public function setMachineType(?string $machineType): void
{
$this->machineType = $machineType;
}
}
sentry/src/State/HubAdapter.php 0000644 00000012213 15266563341 0012501 0 ustar 00 getClient();
}
/**
* {@inheritdoc}
*/
public function getLastEventId(): ?EventId
{
return SentrySdk::getCurrentHub()->getLastEventId();
}
/**
* {@inheritdoc}
*/
public function pushScope(): Scope
{
return SentrySdk::getCurrentHub()->pushScope();
}
/**
* {@inheritdoc}
*/
public function popScope(): bool
{
return SentrySdk::getCurrentHub()->popScope();
}
/**
* {@inheritdoc}
*/
public function withScope(callable $callback)
{
return SentrySdk::getCurrentHub()->withScope($callback);
}
/**
* {@inheritdoc}
*/
public function configureScope(callable $callback): void
{
SentrySdk::getCurrentHub()->configureScope($callback);
}
/**
* {@inheritdoc}
*/
public function bindClient(ClientInterface $client): void
{
SentrySdk::getCurrentHub()->bindClient($client);
}
/**
* {@inheritdoc}
*/
public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint);
}
/**
* {@inheritdoc}
*/
public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureException($exception, $hint);
}
/**
* {@inheritdoc}
*/
public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureEvent($event, $hint);
}
/**
* {@inheritdoc}
*/
public function captureLastError(?EventHint $hint = null): ?EventId
{
return SentrySdk::getCurrentHub()->captureLastError($hint);
}
/**
* {@inheritdoc}
*
* @param int|float|null $duration
*/
public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string
{
return SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId);
}
/**
* {@inheritdoc}
*/
public function addBreadcrumb(Breadcrumb $breadcrumb): bool
{
return SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb);
}
/**
* {@inheritdoc}
*/
public function getIntegration(string $className): ?IntegrationInterface
{
return SentrySdk::getCurrentHub()->getIntegration($className);
}
/**
* {@inheritdoc}
*/
public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction
{
return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext);
}
/**
* {@inheritdoc}
*/
public function getTransaction(): ?Transaction
{
return SentrySdk::getCurrentHub()->getTransaction();
}
/**
* {@inheritdoc}
*/
public function getSpan(): ?Span
{
return SentrySdk::getCurrentHub()->getSpan();
}
/**
* {@inheritdoc}
*/
public function setSpan(?Span $span): HubInterface
{
return SentrySdk::getCurrentHub()->setSpan($span);
}
/**
* @see https://www.php.net/manual/en/language.oop5.cloning.php#object.clone
*/
public function __clone()
{
throw new \BadMethodCallException('Cloning is forbidden.');
}
/**
* @see https://www.php.net/manual/en/language.oop5.magic.php#object.wakeup
*/
public function __wakeup()
{
throw new \BadMethodCallException('Unserializing instances of this class is forbidden.');
}
/**
* @see https://www.php.net/manual/en/language.oop5.magic.php#object.sleep
*/
public function __sleep()
{
throw new \BadMethodCallException('Serializing instances of this class is forbidden.');
}
}
sentry/src/State/HubInterface.php 0000644 00000011662 15266563341 0013030 0 ustar 00 $className
*
* @psalm-return T|null
*/
public function getIntegration(string $className): ?IntegrationInterface;
/**
* Starts a new `Transaction` and returns it. This is the entry point to manual
* tracing instrumentation.
*
* A tree structure can be built by adding child spans to the transaction, and
* child spans to other spans. To start a new child span within the transaction
* or any span, call the respective `startChild()` method.
*
* Every child span must be finished before the transaction is finished,
* otherwise the unfinished spans are discarded.
*
* The transaction must be finished with a call to its `finish()` method, at
* which point the transaction with all its finished child spans will be sent to
* Sentry.
*
* @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext}
*/
public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction;
/**
* Returns the transaction that is on the Hub.
*/
public function getTransaction(): ?Transaction;
/**
* Returns the span that is on the Hub.
*/
public function getSpan(): ?Span;
/**
* Sets the span on the Hub.
*/
public function setSpan(?Span $span): HubInterface;
}
sentry/src/State/Layer.php 0000644 00000003072 15266563341 0011541 0 ustar 00 client = $client;
$this->scope = $scope;
}
/**
* Gets the client held by this layer.
*/
public function getClient(): ?ClientInterface
{
return $this->client;
}
/**
* Sets the client held by this layer.
*
* @param ClientInterface|null $client The client instance
*
* @return $this
*/
public function setClient(?ClientInterface $client): self
{
$this->client = $client;
return $this;
}
/**
* Gets the scope held by this layer.
*/
public function getScope(): Scope
{
return $this->scope;
}
/**
* Sets the scope held by this layer.
*
* @param Scope $scope The scope instance
*
* @return $this
*/
public function setScope(Scope $scope): self
{
$this->scope = $scope;
return $this;
}
}
sentry/src/State/Scope.php 0000644 00000030411 15266563341 0011533 0 ustar 00 > The list of contexts associated to this scope
*/
private $contexts = [];
/**
* @var array The list of tags associated to this scope
*/
private $tags = [];
/**
* @var array A set of extra data associated to this scope
*/
private $extra = [];
/**
* @var string[] List of fingerprints used to group events together in
* Sentry
*/
private $fingerprint = [];
/**
* @var Severity|null The severity to associate to the events captured in
* this scope
*/
private $level;
/**
* @var callable[] List of event processors
*
* @psalm-var array
*/
private $eventProcessors = [];
/**
* @var Span|null Set a Span on the Scope
*/
private $span;
/**
* @var callable[] List of event processors
*
* @psalm-var array
*/
private static $globalEventProcessors = [];
public function __construct(?PropagationContext $propagationContext = null)
{
$this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults();
}
/**
* Sets a new tag in the tags context.
*
* @param string $key The key that uniquely identifies the tag
* @param string $value The value
*
* @return $this
*/
public function setTag(string $key, string $value): self
{
$this->tags[$key] = $value;
return $this;
}
/**
* Merges the given tags into the current tags context.
*
* @param array $tags The tags to merge into the current context
*
* @return $this
*/
public function setTags(array $tags): self
{
$this->tags = array_merge($this->tags, $tags);
return $this;
}
/**
* Removes a given tag from the tags context.
*
* @param string $key The key that uniquely identifies the tag
*
* @return $this
*/
public function removeTag(string $key): self
{
unset($this->tags[$key]);
return $this;
}
/**
* Sets data to the context by a given name.
*
* @param string $name The name that uniquely identifies the context
* @param array $value The value
*
* @return $this
*/
public function setContext(string $name, array $value): self
{
if (!empty($value)) {
$this->contexts[$name] = $value;
}
return $this;
}
/**
* Removes the context from the scope.
*
* @param string $name The name that uniquely identifies the context
*
* @return $this
*/
public function removeContext(string $name): self
{
unset($this->contexts[$name]);
return $this;
}
/**
* Sets a new information in the extra context.
*
* @param string $key The key that uniquely identifies the information
* @param mixed $value The value
*
* @return $this
*/
public function setExtra(string $key, $value): self
{
$this->extra[$key] = $value;
return $this;
}
/**
* Merges the given data into the current extras context.
*
* @param array $extras Data to merge into the current context
*
* @return $this
*/
public function setExtras(array $extras): self
{
$this->extra = array_merge($this->extra, $extras);
return $this;
}
/**
* Get the user context.
*/
public function getUser(): ?UserDataBag
{
return $this->user;
}
/**
* Merges the given data in the user context.
*
* @param array|UserDataBag $user The user data
*
* @return $this
*/
public function setUser($user): self
{
if (!\is_array($user) && !$user instanceof UserDataBag) {
throw new \TypeError(\sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, get_debug_type($user)));
}
if (\is_array($user)) {
$user = UserDataBag::createFromArray($user);
}
if ($this->user === null) {
$this->user = $user;
} else {
$this->user = $this->user->merge($user);
}
return $this;
}
/**
* Removes all data of the user context.
*
* @return $this
*/
public function removeUser(): self
{
$this->user = null;
return $this;
}
/**
* Sets the list of strings used to dictate the deduplication of this event.
*
* @param string[] $fingerprint The fingerprint values
*
* @return $this
*/
public function setFingerprint(array $fingerprint): self
{
$this->fingerprint = $fingerprint;
return $this;
}
/**
* Sets the severity to apply to all events captured in this scope.
*
* @param Severity|null $level The severity
*
* @return $this
*/
public function setLevel(?Severity $level): self
{
$this->level = $level;
return $this;
}
/**
* Add the given breadcrumb to the scope.
*
* @param Breadcrumb $breadcrumb The breadcrumb to add
* @param int $maxBreadcrumbs The maximum number of breadcrumbs to record
*
* @return $this
*/
public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100): self
{
$this->breadcrumbs[] = $breadcrumb;
$this->breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs);
return $this;
}
/**
* Clears all the breadcrumbs.
*
* @return $this
*/
public function clearBreadcrumbs(): self
{
$this->breadcrumbs = [];
return $this;
}
/**
* Adds a new event processor that will be called after {@see Scope::applyToEvent}
* finished its work.
*
* @param callable $eventProcessor The event processor
*
* @return $this
*/
public function addEventProcessor(callable $eventProcessor): self
{
$this->eventProcessors[] = $eventProcessor;
return $this;
}
/**
* Adds a new event processor that will be called after {@see Scope::applyToEvent}
* finished its work.
*
* @param callable $eventProcessor The event processor
*/
public static function addGlobalEventProcessor(callable $eventProcessor): void
{
self::$globalEventProcessors[] = $eventProcessor;
}
/**
* Clears the scope and resets any data it contains.
*
* @return $this
*/
public function clear(): self
{
$this->user = null;
$this->level = null;
$this->span = null;
$this->fingerprint = [];
$this->breadcrumbs = [];
$this->tags = [];
$this->extra = [];
$this->contexts = [];
return $this;
}
/**
* Applies the current context and fingerprint to the event. If the event has
* already some breadcrumbs on it, the ones from this scope won't get merged.
*
* @param Event $event The event object that will be enriched with scope data
*/
public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $options = null): ?Event
{
$event->setFingerprint(array_merge($event->getFingerprint(), $this->fingerprint));
if (empty($event->getBreadcrumbs())) {
$event->setBreadcrumb($this->breadcrumbs);
}
if ($this->level !== null) {
$event->setLevel($this->level);
}
if (!empty($this->tags)) {
$event->setTags(array_merge($this->tags, $event->getTags()));
}
if (!empty($this->extra)) {
$event->setExtra(array_merge($this->extra, $event->getExtra()));
}
if ($this->user !== null) {
$user = $event->getUser();
if ($user === null) {
$user = $this->user;
} else {
$user = $this->user->merge($user);
}
$event->setUser($user);
}
/**
* Apply the trace context to errors if there is a Span on the Scope.
* Else fallback to the propagation context.
* But do not override a trace context already present.
*/
if ($this->span !== null) {
if (!\array_key_exists('trace', $event->getContexts())) {
$event->setContext('trace', $this->span->getTraceContext());
}
// Apply the dynamic sampling context to errors if there is a Transaction on the Scope
$transaction = $this->span->getTransaction();
if ($transaction !== null) {
$event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext());
}
} else {
if (!\array_key_exists('trace', $event->getContexts())) {
$event->setContext('trace', $this->propagationContext->getTraceContext());
}
$dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext();
if ($dynamicSamplingContext === null && $options !== null) {
$dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this);
}
$event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext);
}
foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) {
$event->setContext($name, $data);
}
// We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one
if ($hint === null) {
$hint = new EventHint();
}
foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) {
$event = $processor($event, $hint);
if ($event === null) {
return null;
}
if (!$event instanceof Event) {
throw new \InvalidArgumentException(\sprintf('The event processor must return null or an instance of the %s class', Event::class));
}
}
return $event;
}
/**
* Returns the span that is on the scope.
*/
public function getSpan(): ?Span
{
return $this->span;
}
/**
* Sets the span on the scope.
*
* @param Span|null $span The span
*
* @return $this
*/
public function setSpan(?Span $span): self
{
$this->span = $span;
return $this;
}
/**
* Returns the transaction attached to the scope (if there is one).
*/
public function getTransaction(): ?Transaction
{
if ($this->span !== null) {
return $this->span->getTransaction();
}
return null;
}
public function getPropagationContext(): PropagationContext
{
return $this->propagationContext;
}
public function setPropagationContext(PropagationContext $propagationContext): self
{
$this->propagationContext = $propagationContext;
return $this;
}
public function __clone()
{
if ($this->user !== null) {
$this->user = clone $this->user;
}
if ($this->propagationContext !== null) {
$this->propagationContext = clone $this->propagationContext;
}
}
}
sentry/src/State/Hub.php 0000644 00000027121 15266563341 0011204 0 ustar 00 stack[] = new Layer($client, $scope ?? new Scope());
}
/**
* {@inheritdoc}
*/
public function getClient(): ?ClientInterface
{
return $this->getStackTop()->getClient();
}
/**
* {@inheritdoc}
*/
public function getLastEventId(): ?EventId
{
return $this->lastEventId;
}
/**
* {@inheritdoc}
*/
public function pushScope(): Scope
{
$clonedScope = clone $this->getScope();
$this->stack[] = new Layer($this->getClient(), $clonedScope);
return $clonedScope;
}
/**
* {@inheritdoc}
*/
public function popScope(): bool
{
if (\count($this->stack) === 1) {
return false;
}
return array_pop($this->stack) !== null;
}
/**
* {@inheritdoc}
*/
public function withScope(callable $callback)
{
$scope = $this->pushScope();
try {
return $callback($scope);
} finally {
$this->popScope();
}
}
/**
* {@inheritdoc}
*/
public function configureScope(callable $callback): void
{
$callback($this->getScope());
}
/**
* {@inheritdoc}
*/
public function bindClient(ClientInterface $client): void
{
$layer = $this->getStackTop();
$layer->setClient($client);
}
/**
* {@inheritdoc}
*/
public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId
{
$client = $this->getClient();
if ($client !== null) {
return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint);
}
return null;
}
/**
* {@inheritdoc}
*/
public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId
{
$client = $this->getClient();
if ($client !== null) {
return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint);
}
return null;
}
/**
* {@inheritdoc}
*/
public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId
{
$client = $this->getClient();
if ($client !== null) {
return $this->lastEventId = $client->captureEvent($event, $hint, $this->getScope());
}
return null;
}
/**
* {@inheritdoc}
*/
public function captureLastError(?EventHint $hint = null): ?EventId
{
$client = $this->getClient();
if ($client !== null) {
return $this->lastEventId = $client->captureLastError($this->getScope(), $hint);
}
return null;
}
/**
* {@inheritdoc}
*
* @param int|float|null $duration
*/
public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string
{
$client = $this->getClient();
if ($client === null) {
return null;
}
$options = $client->getOptions();
$event = Event::createCheckIn();
$checkIn = new CheckIn(
$slug,
$status,
$checkInId,
$options->getRelease(),
$options->getEnvironment(),
$duration,
$monitorConfig
);
$event->setCheckIn($checkIn);
$this->captureEvent($event);
return $checkIn->getId();
}
/**
* {@inheritdoc}
*/
public function addBreadcrumb(Breadcrumb $breadcrumb): bool
{
$client = $this->getClient();
if ($client === null) {
return false;
}
$options = $client->getOptions();
$beforeBreadcrumbCallback = $options->getBeforeBreadcrumbCallback();
$maxBreadcrumbs = $options->getMaxBreadcrumbs();
if ($maxBreadcrumbs <= 0) {
return false;
}
$breadcrumb = $beforeBreadcrumbCallback($breadcrumb);
if ($breadcrumb !== null) {
$this->getScope()->addBreadcrumb($breadcrumb, $maxBreadcrumbs);
}
return $breadcrumb !== null;
}
/**
* {@inheritdoc}
*/
public function getIntegration(string $className): ?IntegrationInterface
{
$client = $this->getClient();
if ($client !== null) {
return $client->getIntegration($className);
}
return null;
}
/**
* {@inheritdoc}
*
* @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext}
*/
public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction
{
$transaction = new Transaction($context, $this);
$client = $this->getClient();
$options = $client !== null ? $client->getOptions() : null;
$logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
if ($options === null || !$options->isTracingEnabled()) {
$transaction->setSampled(false);
$logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]);
return $transaction;
}
$samplingContext = SamplingContext::getDefault($context);
$samplingContext->setAdditionalContext($customSamplingContext);
$sampleSource = 'context';
$sampleRand = $context->getMetadata()->getSampleRand();
if ($transaction->getSampled() === null) {
$tracesSampler = $options->getTracesSampler();
if ($tracesSampler !== null) {
$sampleRate = $tracesSampler($samplingContext);
$sampleSource = 'config:traces_sampler';
} else {
$parentSampleRate = $context->getMetadata()->getParentSamplingRate();
if ($parentSampleRate !== null) {
$sampleRate = $parentSampleRate;
$sampleSource = 'parent:sample_rate';
} else {
$sampleRate = $this->getSampleRate(
$samplingContext->getParentSampled(),
$options->getTracesSampleRate() ?? 0
);
$sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate';
}
}
if (!$this->isValidSampleRate($sampleRate)) {
$transaction->setSampled(false);
$logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);
return $transaction;
}
$transaction->getMetadata()->setSamplingRate($sampleRate);
// Always overwrite the sample_rate in the DSC
$dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext();
if ($dynamicSamplingContext !== null) {
$dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true);
}
if ($sampleRate === 0.0) {
$transaction->setSampled(false);
$logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]);
return $transaction;
}
$transaction->setSampled($sampleRand < $sampleRate);
}
if (!$transaction->getSampled()) {
$logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);
return $transaction;
}
$logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);
$transaction->initSpanRecorder();
$profilesSampleRate = $options->getProfilesSampleRate();
if ($profilesSampleRate === null) {
$logger->info(\sprintf('Transaction [%s] is not profiling because `profiles_sample_rate` option is not set.', (string) $transaction->getTraceId()));
} elseif ($this->sample($profilesSampleRate)) {
$logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId()));
$transaction->initProfiler()->start();
} else {
$logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId()));
}
return $transaction;
}
/**
* {@inheritdoc}
*/
public function getTransaction(): ?Transaction
{
return $this->getScope()->getTransaction();
}
/**
* {@inheritdoc}
*/
public function setSpan(?Span $span): HubInterface
{
$this->getScope()->setSpan($span);
return $this;
}
/**
* {@inheritdoc}
*/
public function getSpan(): ?Span
{
return $this->getScope()->getSpan();
}
/**
* Gets the scope bound to the top of the stack.
*/
private function getScope(): Scope
{
return $this->getStackTop()->getScope();
}
/**
* Gets the topmost client/layer pair in the stack.
*/
private function getStackTop(): Layer
{
return $this->stack[\count($this->stack) - 1];
}
private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float
{
if ($hasParentBeenSampled === true) {
return 1.0;
}
if ($hasParentBeenSampled === false) {
return 0.0;
}
return $fallbackSampleRate;
}
/**
* @param mixed $sampleRate
*/
private function sample($sampleRate): bool
{
if ($sampleRate === 0.0 || $sampleRate === null) {
return false;
}
if ($sampleRate === 1.0) {
return true;
}
return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate;
}
/**
* @param mixed $sampleRate
*/
private function isValidSampleRate($sampleRate): bool
{
if (!\is_float($sampleRate) && !\is_int($sampleRate)) {
return false;
}
if ($sampleRate < 0 || $sampleRate > 1) {
return false;
}
return true;
}
}
sentry/src/Stacktrace.php 0000644 00000004705 15266563341 0011475 0 ustar 00
*/
final class Stacktrace
{
/**
* @var Frame[] The frames that compose the stacktrace
*/
private $frames = [];
/**
* Constructor.
*
* @param Frame[] $frames A non-empty list of stack frames. The list must be
* ordered from caller to callee. The last frame is the
* one creating the exception
*/
public function __construct(array $frames)
{
if (empty($frames)) {
throw new \InvalidArgumentException('Expected a non empty list of frames.');
}
foreach ($frames as $frame) {
if (!$frame instanceof Frame) {
throw new \UnexpectedValueException(\sprintf('Expected an instance of the "%s" class. Got: "%s".', Frame::class, get_debug_type($frame)));
}
}
$this->frames = $frames;
}
/**
* Gets the stacktrace frames.
*
* @return Frame[]
*/
public function getFrames(): array
{
return $this->frames;
}
/**
* Gets the frame at the given index.
*
* @param int $index The index from which the frame should be get
*
* @throws \OutOfBoundsException
*/
public function getFrame(int $index): Frame
{
if ($index < 0 || $index >= \count($this->frames)) {
throw new \OutOfBoundsException();
}
return $this->frames[$index];
}
/**
* Adds a new frame to the stacktrace.
*
* @param Frame $frame The frame
*/
public function addFrame(Frame $frame): self
{
array_unshift($this->frames, $frame);
return $this;
}
/**
* Removes the frame at the given index from the stacktrace.
*
* @param int $index The index of the frame
*
* @throws \OutOfBoundsException If the index is out of range
*/
public function removeFrame(int $index): self
{
if (!isset($this->frames[$index])) {
throw new \OutOfBoundsException(\sprintf('Cannot remove the frame at index %d.', $index));
}
if (\count($this->frames) === 1) {
throw new \RuntimeException('Cannot remove all frames from the stacktrace.');
}
array_splice($this->frames, $index, 1);
return $this;
}
}
sentry/src/FrameBuilder.php 0000644 00000021674 15266563341 0011756 0 ustar 00 options = $options;
$this->representationSerializer = $representationSerializer;
}
/**
* Builds a {@see Frame} object from the given backtrace's raw frame.
*
* @param string $file The file where the frame originated
* @param int $line The line at which the frame originated
* @param array $backtraceFrame The raw frame
*
* @psalm-param StacktraceFrame $backtraceFrame
*/
public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame
{
// The filename can be in any of these formats:
// -
// - () : eval()'d code
// - () : runtime-created function
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) {
$file = $matches[1];
$line = (int) $matches[2];
}
$functionName = null;
$rawFunctionName = null;
$strippedFilePath = $this->stripPrefixFromFilePath($this->options, $file);
if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) {
$functionName = $backtraceFrame['class'];
// Skip if no prefixes are set
if ($this->options->getPrefixes()) {
$prefixStrippedFunctionName = preg_replace_callback('/@anonymous\\x00([^:]+)(:.*)?/', function (array $matches) {
return "@anonymous\x00" . $this->stripPrefixFromFilePath($this->options, $matches[1]) . ($matches[2] ?? '');
}, $functionName);
if ($prefixStrippedFunctionName) {
$functionName = $prefixStrippedFunctionName;
}
}
$rawFunctionName = \sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']);
$functionName = \sprintf('%s::%s', preg_replace('/(?::\d+\$|0x)[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']);
} elseif (isset($backtraceFrame['function'])) {
$functionName = $backtraceFrame['function'];
}
// Starting with PHP 8.4 a closure function call is reported as "{closure:filename:line}" instead of just "{closure}", properly strip the prefixes from that format
if (\PHP_VERSION_ID >= 80400 && $functionName !== null && $this->options->getPrefixes()) {
$prefixStrippedFunctionName = preg_replace_callback('/^\{closure:(.*?):(\d+)}$/', function (array $matches) {
return '{closure:' . $this->stripPrefixFromFilePath($this->options, $matches[1]) . ':' . $matches[2] . '}';
}, $functionName);
if ($prefixStrippedFunctionName) {
$functionName = $prefixStrippedFunctionName;
}
}
return new Frame(
$functionName,
$strippedFilePath,
$line,
$rawFunctionName,
$file !== Frame::INTERNAL_FRAME_FILENAME ? $file : null,
$this->getFunctionArguments($backtraceFrame),
$this->isFrameInApp($file, $functionName)
);
}
/**
* Checks whether a certain frame should be marked as "in app" or not.
*
* @param string $file The file to check
* @param string|null $functionName The name of the function
*/
private function isFrameInApp(string $file, ?string $functionName): bool
{
if ($file === Frame::INTERNAL_FRAME_FILENAME) {
return false;
}
if ($functionName !== null && substr($functionName, 0, \strlen('Sentry\\')) === 'Sentry\\') {
return false;
}
$excludedAppPaths = $this->options->getInAppExcludedPaths();
$includedAppPaths = $this->options->getInAppIncludedPaths();
$absoluteFilePath = @realpath($file) ?: $file;
$isInApp = true;
foreach ($excludedAppPaths as $excludedAppPath) {
if (mb_substr($absoluteFilePath, 0, mb_strlen($excludedAppPath)) === $excludedAppPath) {
$isInApp = false;
break;
}
}
foreach ($includedAppPaths as $includedAppPath) {
if (mb_substr($absoluteFilePath, 0, mb_strlen($includedAppPath)) === $includedAppPath) {
$isInApp = true;
break;
}
}
return $isInApp;
}
/**
* Gets the arguments of the function called in the given frame.
*
* @param array $backtraceFrame The frame data
*
* @psalm-param StacktraceFrame $backtraceFrame
*
* @return array
*/
private function getFunctionArguments(array $backtraceFrame): array
{
if (!isset($backtraceFrame['function'], $backtraceFrame['args'])) {
return [];
}
$reflectionFunction = null;
try {
if (isset($backtraceFrame['class'])) {
if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']);
} elseif (isset($backtraceFrame['type']) && $backtraceFrame['type'] === '::') {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic');
} else {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call');
}
} elseif ($backtraceFrame['function'] !== '__lambda_func' && !str_starts_with($backtraceFrame['function'], '{closure') && \function_exists($backtraceFrame['function'])) {
$reflectionFunction = new \ReflectionFunction($backtraceFrame['function']);
}
} catch (\ReflectionException $e) {
// Reflection failed, we do nothing instead
}
$argumentValues = [];
if ($reflectionFunction !== null) {
$argumentValues = $this->getFunctionArgumentValues($reflectionFunction, $backtraceFrame['args']);
} else {
foreach ($backtraceFrame['args'] as $parameterPosition => $parameterValue) {
$argumentValues['param' . $parameterPosition] = $parameterValue;
}
}
foreach ($argumentValues as $argumentName => $argumentValue) {
$argumentValues[$argumentName] = $this->representationSerializer->representationSerialize($argumentValue);
}
return $argumentValues;
}
/**
* Gets an hashmap indexed by argument name containing all the arguments
* passed to the function called in the given frame of the stacktrace.
*
* @param \ReflectionFunctionAbstract $reflectionFunction A reflection object
* @param mixed[] $backtraceFrameArgs The arguments of the frame
*
* @return array
*/
private function getFunctionArgumentValues(\ReflectionFunctionAbstract $reflectionFunction, array $backtraceFrameArgs): array
{
$argumentValues = [];
foreach ($reflectionFunction->getParameters() as $reflectionParameter) {
$parameterPosition = $reflectionParameter->getPosition();
if ($reflectionParameter->isVariadic()) {
// For variadic parameters, collect all remaining arguments into an array
$variadicArgs = \array_slice($backtraceFrameArgs, $parameterPosition);
$argumentValues[$reflectionParameter->getName()] = array_values($variadicArgs);
// Variadic parameter is always the last one, so we can break
break;
}
if (!\array_key_exists($parameterPosition, $backtraceFrameArgs)) {
continue;
}
$argumentValues[$reflectionParameter->getName()] = $backtraceFrameArgs[$parameterPosition];
}
return $argumentValues;
}
}
sentry/src/Logs/LogsAggregator.php 0000644 00000013124 15266563341 0013217 0 ustar 00 $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function add(
LogLevel $level,
string $message,
array $values = [],
array $attributes = []
): void {
$timestamp = microtime(true);
$hub = SentrySdk::getCurrentHub();
$client = $hub->getClient();
// There is no need to continue if there is no client
if ($client === null) {
return;
}
$options = $client->getOptions();
$sdkLogger = $options->getLogger();
if (!$options->getEnableLogs()) {
if ($sdkLogger !== null) {
$sdkLogger->info(
'Log will be discarded because "enable_logs" is "false".'
);
}
return;
}
$formattedMessage = Str::vsprintfOrNull($message, $values);
if ($formattedMessage === null) {
// If formatting fails we don't format the message and log the error
if ($sdkLogger !== null) {
$sdkLogger->warning('Failed to format log message with values.', [
'message' => $message,
'values' => $values,
]);
}
$formattedMessage = $message;
}
$log = (new Log($timestamp, $this->getTraceId($hub), $level, $formattedMessage))
->setAttribute('sentry.release', $options->getRelease())
->setAttribute('sentry.environment', $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT)
->setAttribute('sentry.server.address', $options->getServerName())
->setAttribute('sentry.trace.parent_span_id', $hub->getSpan() ? $hub->getSpan()->getSpanId() : null);
if ($client instanceof Client) {
$log->setAttribute('sentry.sdk.name', $client->getSdkIdentifier());
$log->setAttribute('sentry.sdk.version', $client->getSdkVersion());
}
$hub->configureScope(function (Scope $scope) use ($log) {
$user = $scope->getUser();
if ($user !== null) {
if ($user->getId() !== null) {
$log->setAttribute('user.id', $user->getId());
}
if ($user->getEmail() !== null) {
$log->setAttribute('user.email', $user->getEmail());
}
if ($user->getUsername() !== null) {
$log->setAttribute('user.name', $user->getUsername());
}
}
});
if (\count($values)) {
$log->setAttribute('sentry.message.template', $message);
foreach ($values as $key => $value) {
$log->setAttribute("sentry.message.parameter.{$key}", $value);
}
}
$attributes = Arr::simpleDot($attributes);
foreach ($attributes as $key => $value) {
$attribute = Attribute::tryFromValue($value);
if (!\is_string($key)) {
if ($sdkLogger !== null) {
$sdkLogger->info(
\sprintf("Dropping log attribute with non-string key '%s' and value of type '%s'.", $key, \gettype($value))
);
}
continue;
}
if ($attribute === null) {
if ($sdkLogger !== null) {
$sdkLogger->info(
\sprintf("Dropping log attribute {$key} with value of type '%s' because it is not serializable or an unsupported type.", \gettype($value))
);
}
continue;
}
$log->setAttribute($key, $attribute);
}
$log = ($options->getBeforeSendLogCallback())($log);
if ($log === null) {
if ($sdkLogger !== null) {
$sdkLogger->info(
'Log will be discarded because the "before_send_log" callback returned "null".',
['log' => $log]
);
}
return;
}
if ($sdkLogger !== null) {
$sdkLogger->log($log->getPsrLevel(), "Logs item: {$log->getBody()}", $log->attributes()->toSimpleArray());
}
$this->logs[] = $log;
}
public function flush(): ?EventId
{
if (empty($this->logs)) {
return null;
}
$hub = SentrySdk::getCurrentHub();
$event = Event::createLogs()->setLogs($this->logs);
$this->logs = [];
return $hub->captureEvent($event);
}
/**
* @return Log[]
*/
public function all(): array
{
return $this->logs;
}
private function getTraceId(HubInterface $hub): string
{
$span = $hub->getSpan();
if ($span !== null) {
return (string) $span->getTraceId();
}
$traceId = '';
$hub->configureScope(function (Scope $scope) use (&$traceId) {
$traceId = (string) $scope->getPropagationContext()->getTraceId();
});
return $traceId;
}
}
sentry/src/Logs/Logs.php 0000644 00000007307 15266563341 0011222 0 ustar 00 aggregator = new LogsAggregator();
}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function trace(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::trace(), $message, $values, $attributes);
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function debug(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::debug(), $message, $values, $attributes);
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function info(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::info(), $message, $values, $attributes);
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function warn(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::warn(), $message, $values, $attributes);
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function error(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::error(), $message, $values, $attributes);
}
/**
* @param string $message see sprintf for a description of format
* @param array $values see sprintf for a description of values
* @param array $attributes additional attributes to add to the log
*/
public function fatal(string $message, array $values = [], array $attributes = []): void
{
$this->aggregator->add(LogLevel::fatal(), $message, $values, $attributes);
}
/**
* Flush the captured logs and send them to Sentry.
*/
public function flush(): ?EventId
{
return $this->aggregator->flush();
}
/**
* Get the logs aggregator.
*
* @internal
*/
public function aggregator(): LogsAggregator
{
return $this->aggregator;
}
}
sentry/src/Logs/Log.php 0000644 00000003614 15266563341 0011034 0 ustar 00 timestamp = $timestamp;
$this->traceId = $traceId;
$this->level = $level;
$this->body = $body;
$this->attributes = new AttributeBag();
}
public function getTimestamp(): float
{
return $this->timestamp;
}
public function setTimestamp(float $timestamp): self
{
$this->timestamp = $timestamp;
return $this;
}
public function getTraceId(): string
{
return $this->traceId;
}
public function setTraceId(string $traceId): self
{
$this->traceId = $traceId;
return $this;
}
public function getLevel(): LogLevel
{
return $this->level;
}
public function setLevel(LogLevel $level): self
{
$this->level = $level;
return $this;
}
public function getPsrLevel(): string
{
return $this->level->toPsrLevel();
}
public function getBody(): string
{
return $this->body;
}
public function setBody(string $body): self
{
$this->body = $body;
return $this;
}
public function attributes(): AttributeBag
{
return $this->attributes;
}
/**
* @param mixed $value
*/
public function setAttribute(string $key, $value): self
{
$this->attributes->set($key, $value);
return $this;
}
}
sentry/src/Logs/LogLevel.php 0000644 00000004154 15266563341 0012024 0 ustar 00 A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value, int $priority)
{
$this->value = $value;
$this->priority = $priority;
}
public static function trace(): self
{
return self::getInstance('trace', 10);
}
public static function debug(): self
{
return self::getInstance('debug', 20);
}
public static function info(): self
{
return self::getInstance('info', 30);
}
public static function warn(): self
{
return self::getInstance('warn', 40);
}
public static function error(): self
{
return self::getInstance('error', 50);
}
public static function fatal(): self
{
return self::getInstance('fatal', 60);
}
public function __toString(): string
{
return $this->value;
}
public function getPriority(): int
{
return $this->priority;
}
public function toPsrLevel(): string
{
switch ($this->value) {
case 'trace':
case 'debug':
return \Psr\Log\LogLevel::DEBUG;
case 'warn':
return \Psr\Log\LogLevel::WARNING;
case 'error':
return \Psr\Log\LogLevel::ERROR;
case 'fatal':
return \Psr\Log\LogLevel::CRITICAL;
case 'info':
default:
return \Psr\Log\LogLevel::INFO;
}
}
private static function getInstance(string $value, int $priority): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value, $priority);
}
return self::$instances[$value];
}
}
sentry/src/Severity.php 0000644 00000007710 15266563341 0011222 0 ustar 00
*/
final class Severity implements \Stringable
{
/**
* This constant represents the "debug" severity level.
*
* @internal
*/
public const DEBUG = 'debug';
/**
* This constant represents the "info" severity level.
*
* @internal
*/
public const INFO = 'info';
/**
* This constant represents the "warning" severity level.
*
* @internal
*/
public const WARNING = 'warning';
/**
* This constant represents the "error" severity level.
*
* @internal
*/
public const ERROR = 'error';
/**
* This constant represents the "fatal" severity level.
*
* @internal
*/
public const FATAL = 'fatal';
/**
* This constant contains the list of allowed enum values.
*
* @internal
*/
public const ALLOWED_SEVERITIES = [
self::DEBUG,
self::INFO,
self::WARNING,
self::ERROR,
self::FATAL,
];
/**
* @var string The value of this enum instance
*/
private $value;
/**
* Constructor.
*
* @param string $value The value this instance represents
*/
public function __construct(string $value = self::INFO)
{
if (!\in_array($value, self::ALLOWED_SEVERITIES, true)) {
throw new \InvalidArgumentException(\sprintf('The "%s" is not a valid enum value.', $value));
}
$this->value = $value;
}
/**
* Translate a PHP Error constant into a Sentry log level group.
*
* @param int $severity PHP E_* error constant
*/
public static function fromError(int $severity): self
{
switch ($severity) {
case \E_DEPRECATED:
case \E_USER_DEPRECATED:
case \E_WARNING:
case \E_USER_WARNING:
return self::warning();
case \E_ERROR:
case \E_PARSE:
case \E_CORE_ERROR:
case \E_CORE_WARNING:
case \E_COMPILE_ERROR:
case \E_COMPILE_WARNING:
return self::fatal();
case \E_RECOVERABLE_ERROR:
case \E_USER_ERROR:
return self::error();
case \E_NOTICE:
case \E_USER_NOTICE:
case 2048: // This is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices
return self::info();
default:
return self::error();
}
}
/**
* Creates a new instance of this enum for the "debug" value.
*/
public static function debug(): self
{
return new self(self::DEBUG);
}
/**
* Creates a new instance of this enum for the "info" value.
*/
public static function info(): self
{
return new self(self::INFO);
}
/**
* Creates a new instance of this enum for the "warning" value.
*/
public static function warning(): self
{
return new self(self::WARNING);
}
/**
* Creates a new instance of this enum for the "error" value.
*/
public static function error(): self
{
return new self(self::ERROR);
}
/**
* Creates a new instance of this enum for the "fatal" value.
*/
public static function fatal(): self
{
return new self(self::FATAL);
}
/**
* Returns whether two object instances of this class are equal.
*
* @param self $other The object to compare
*/
public function isEqualTo(self $other): bool
{
return $this->value === (string) $other;
}
/**
* @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*/
public function __toString(): string
{
return $this->value;
}
}
sentry/src/Logger/DebugFileLogger.php 0000644 00000000611 15266563341 0013606 0 ustar 00 filePath = $filePath;
}
public function write(string $message): void
{
file_put_contents($this->filePath, $message, \FILE_APPEND);
}
}
sentry/src/Logger/DebugStdOutLogger.php 0000644 00000000335 15266563341 0014154 0 ustar 00 write(
\sprintf("sentry/sentry: [%s] %s\n", $level, $formattedMessageAndContext)
);
}
abstract public function write(string $message): void;
}
sentry/src/ClientInterface.php 0000644 00000005625 15266563341 0012452 0 ustar 00 $className
*
* @psalm-return T|null
*/
public function getIntegration(string $className): ?IntegrationInterface;
/**
* Flushes the queue of events pending to be sent. If a timeout is provided
* and the queue takes longer to drain, the promise resolves with `false`.
*
* @param int|null $timeout Maximum time in seconds the client should wait
*/
public function flush(?int $timeout = null): Result;
/**
* Returns the stacktrace builder of the client.
*/
public function getStacktraceBuilder(): StacktraceBuilder;
}
sentry/src/Attributes/AttributeBag.php 0000644 00000002451 15266563341 0014110 0 ustar 00
*/
private $attributes = [];
/**
* @param mixed $value
*/
public function set(string $key, $value): self
{
$attribute = $value instanceof Attribute
? $value
: Attribute::tryFromValue($value);
if ($attribute !== null) {
$this->attributes[$key] = $attribute;
}
return $this;
}
public function get(string $key): ?Attribute
{
return $this->attributes[$key] ?? null;
}
public function forget(string $key): self
{
unset($this->attributes[$key]);
return $this;
}
/**
* @return array
*/
public function all(): array
{
return $this->attributes;
}
/**
* Get a simplified representation of the attributes as a key-value array, main purpose is for logging output.
*
* @return array
*/
public function toSimpleArray(): array
{
return array_map(static function (Attribute $attribute) {
return $attribute->getValue();
}, $this->attributes);
}
}
sentry/src/Attributes/Attribute.php 0000644 00000004115 15266563341 0013475 0 ustar 00 value = $value;
$this->type = $type;
}
/**
* @return AttributeType
*/
public function getType(): string
{
return $this->type;
}
/**
* @return AttributeValue
*/
public function getValue()
{
return $this->value;
}
/**
* @param mixed $value
*
* @throws \InvalidArgumentException thrown when the value cannot be serialized as an attribute
*/
public static function fromValue($value): self
{
$attribute = self::tryFromValue($value);
if ($attribute === null) {
throw new \InvalidArgumentException(\sprintf('Invalid attribute value, %s cannot be serialized', \gettype($value)));
}
return $attribute;
}
/**
* @param mixed $value
*/
public static function tryFromValue($value): ?self
{
if ($value === null) {
return null;
}
if (\is_bool($value)) {
return new self($value, 'boolean');
}
if (\is_int($value)) {
return new self($value, 'integer');
}
if (\is_float($value)) {
return new self($value, 'double');
}
if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) {
$stringValue = (string) $value;
if (empty($stringValue)) {
return null;
}
return new self($stringValue, 'string');
}
return null;
}
public function __toString(): string
{
return "{$this->value} ({$this->type})";
}
}
sentry/src/EventType.php 0000644 00000003403 15266563341 0011326 0 ustar 00
*/
final class EventType implements \Stringable
{
/**
* @var string The value of the enum instance
*/
private $value;
/**
* @var array A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
public static function event(): self
{
return self::getInstance('event');
}
public static function transaction(): self
{
return self::getInstance('transaction');
}
public static function checkIn(): self
{
return self::getInstance('check_in');
}
public static function logs(): self
{
return self::getInstance('log');
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public static function metrics(): self
{
return self::getInstance('metrics');
}
/**
* List of all cases on the enum.
*
* @return self[]
*/
public static function cases(): array
{
return [
self::event(),
self::transaction(),
self::checkIn(),
self::logs(),
self::metrics(),
];
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/EventId.php 0000644 00000001736 15266563341 0010750 0 ustar 00
*/
final class EventId implements \Stringable
{
/**
* @var string The ID
*/
private $value;
/**
* Class constructor.
*
* @param string $value The ID
*/
public function __construct(string $value)
{
if (!preg_match('/^[a-f0-9]{32}$/i', $value)) {
throw new \InvalidArgumentException('The $value argument must be a 32 characters long hexadecimal string.');
}
$this->value = $value;
}
/**
* Generates a new event ID.
*
* @copyright Matt Farina MIT License https://github.com/lootils/uuid/blob/master/LICENSE
*/
public static function generate(): self
{
return new self(SentryUid::generate());
}
public function __toString(): string
{
return $this->value;
}
}
sentry/src/Dsn.php 0000644 00000014232 15266563341 0010131 0 ustar 00
*/
final class Dsn implements \Stringable
{
/**
* @var string Regex to match the organization ID in the host.
* This only applies to Sentry SaaS DSNs that contain the organization ID.
*/
private const SENTRY_ORG_ID_REGEX = '/^o(\d+)\./';
/**
* @var string The protocol to be used to access the resource
*/
private $scheme;
/**
* @var string The host that holds the resource
*/
private $host;
/**
* @var int The port on which the resource is exposed
*/
private $port;
/**
* @var string The public key to authenticate the SDK
*/
private $publicKey;
/**
* @var string The ID of the resource to access
*/
private $projectId;
/**
* @var string The specific resource that the web client wants to access
*/
private $path;
/**
* @var int|null
*/
private $orgId;
/**
* Class constructor.
*
* @param string $scheme The protocol to be used to access the resource
* @param string $host The host that holds the resource
* @param int $port The port on which the resource is exposed
* @param string $projectId The ID of the resource to access
* @param string $path The specific resource that the web client wants to access
* @param string $publicKey The public key to authenticate the SDK
* @param ?int $orgId The org ID
*/
private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey, ?int $orgId = null)
{
$this->scheme = $scheme;
$this->host = $host;
$this->port = $port;
$this->projectId = $projectId;
$this->path = $path;
$this->publicKey = $publicKey;
$this->orgId = $orgId;
}
/**
* Creates an instance of this class by parsing the given string.
*
* @param string $value The string to parse
*/
public static function createFromString(string $value): self
{
$parsedDsn = parse_url($value);
if ($parsedDsn === false) {
throw new \InvalidArgumentException(\sprintf('The "%s" DSN is invalid.', $value));
}
foreach (['scheme', 'host', 'path', 'user'] as $component) {
if (!isset($parsedDsn[$component]) || (isset($parsedDsn[$component]) && empty($parsedDsn[$component]))) {
throw new \InvalidArgumentException(\sprintf('The "%s" DSN must contain a scheme, a host, a user and a path component.', $value));
}
}
if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) {
throw new \InvalidArgumentException(\sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value));
}
$segmentPaths = explode('/', $parsedDsn['path']);
$projectId = array_pop($segmentPaths);
$lastSlashPosition = strrpos($parsedDsn['path'], '/');
$path = $parsedDsn['path'];
if ($lastSlashPosition !== false) {
$path = substr($parsedDsn['path'], 0, $lastSlashPosition);
}
$orgId = null;
if (preg_match(self::SENTRY_ORG_ID_REGEX, $parsedDsn['host'], $matches) == 1) {
$orgId = (int) $matches[1];
}
return new self(
$parsedDsn['scheme'],
$parsedDsn['host'],
$parsedDsn['port'] ?? ($parsedDsn['scheme'] === 'http' ? 80 : 443),
$projectId,
$path,
$parsedDsn['user'],
$orgId
);
}
/**
* Gets the protocol to be used to access the resource.
*/
public function getScheme(): string
{
return $this->scheme;
}
/**
* Gets the host that holds the resource.
*/
public function getHost(): string
{
return $this->host;
}
/**
* Gets the port on which the resource is exposed.
*/
public function getPort(): int
{
return $this->port;
}
/**
* Gets the specific resource that the web client wants to access.
*/
public function getPath(): string
{
return $this->path;
}
/**
* Gets the ID of the resource to access.
*/
public function getProjectId(): string
{
return $this->projectId;
}
/**
* Gets the public key to authenticate the SDK.
*/
public function getPublicKey(): string
{
return $this->publicKey;
}
public function getOrgId(): ?int
{
return $this->orgId;
}
/**
* Returns the URL of the API for the envelope endpoint.
*/
public function getEnvelopeApiEndpointUrl(): string
{
return $this->getBaseEndpointUrl() . '/envelope/';
}
/**
* Returns the URL of the API for the CSP report endpoint.
*/
public function getCspReportEndpointUrl(): string
{
return $this->getBaseEndpointUrl() . '/security/?sentry_key=' . $this->publicKey;
}
/**
* @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*/
public function __toString(): string
{
$url = $this->scheme . '://' . $this->publicKey;
$url .= '@' . $this->host;
if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) {
$url .= ':' . $this->port;
}
if ($this->path !== null) {
$url .= $this->path;
}
$url .= '/' . $this->projectId;
return $url;
}
/**
* Returns the base url to Sentry from the DSN.
*/
private function getBaseEndpointUrl(): string
{
$url = $this->scheme . '://' . $this->host;
if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) {
$url .= ':' . $this->port;
}
if ($this->path !== null) {
$url .= $this->path;
}
$url .= '/api/' . $this->projectId;
return $url;
}
}
sentry/src/CheckIn.php 0000644 00000006007 15266563341 0010712 0 ustar 00 setMonitorSlug($monitorSlug);
$this->setStatus($status);
$this->setId($id ?? SentryUid::generate());
$this->setRelease($release ?? '');
$this->setEnvironment($environment ?? Event::DEFAULT_ENVIRONMENT);
$this->setDuration($duration);
$this->setMonitorConfig($monitorConfig);
}
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getMonitorSlug(): string
{
return $this->monitorSlug;
}
public function setMonitorSlug(string $monitorSlug): self
{
$this->monitorSlug = $monitorSlug;
return $this;
}
public function getStatus(): CheckInStatus
{
return $this->status;
}
public function setStatus(CheckInStatus $status): self
{
$this->status = $status;
return $this;
}
public function getRelease(): ?string
{
return $this->release;
}
public function setRelease(string $release): self
{
$this->release = $release;
return $this;
}
public function getEnvironment(): ?string
{
return $this->environment;
}
public function setEnvironment(string $environment): self
{
$this->environment = $environment;
return $this;
}
/**
* @return int|float|null
*/
public function getDuration()
{
return $this->duration;
}
/**
* @param int|float|null $duration The duration of the check-in in seconds
*/
public function setDuration($duration): self
{
$this->duration = $duration;
return $this;
}
public function getMonitorConfig(): ?MonitorConfig
{
return $this->monitorConfig;
}
public function setMonitorConfig(?MonitorConfig $monitorConfig): self
{
$this->monitorConfig = $monitorConfig;
return $this;
}
}
sentry/src/EventHint.php 0000644 00000004743 15266563341 0011317 0 ustar 00
*/
public $extra = [];
/**
* Create a EventHint instance from an array of values.
*
* @psalm-param array{
* exception?: \Throwable|null,
* mechanism?: ExceptionMechanism|null,
* stacktrace?: Stacktrace|null,
* extra?: array
* } $hintData
*/
public static function fromArray(array $hintData): self
{
$hint = new self();
$exception = $hintData['exception'] ?? null;
$mechanism = $hintData['mechanism'] ?? null;
$stacktrace = $hintData['stacktrace'] ?? null;
$extra = $hintData['extra'] ?? [];
if ($exception !== null && !$exception instanceof \Throwable) {
throw new \InvalidArgumentException(\sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception)));
}
if ($mechanism !== null && !$mechanism instanceof ExceptionMechanism) {
throw new \InvalidArgumentException(\sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism)));
}
if ($stacktrace !== null && !$stacktrace instanceof Stacktrace) {
throw new \InvalidArgumentException(\sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace)));
}
if (!\is_array($extra)) {
throw new \InvalidArgumentException(\sprintf('The value of the "extra" field must be an array. Got: "%s".', get_debug_type($extra)));
}
$hint->exception = $exception;
$hint->mechanism = $mechanism;
$hint->stacktrace = $stacktrace;
$hint->extra = $extra;
return $hint;
}
}
sentry/src/MonitorSchedule.php 0000644 00000004061 15266563341 0012510 0 ustar 00 type = $type;
$this->value = $value;
$this->unit = $unit;
}
public static function crontab(string $value): self
{
return new self(self::TYPE_CRONTAB, $value);
}
public static function interval(int $value, MonitorScheduleUnit $unit): self
{
return new self(self::TYPE_INTERVAL, $value, $unit);
}
public function getType(): string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* @return string|int
*/
public function getValue()
{
return $this->value;
}
/**
* @param string|int $value
*/
public function setValue($value): self
{
$this->value = $value;
return $this;
}
public function getUnit(): ?MonitorScheduleUnit
{
return $this->unit;
}
public function setUnit(?MonitorScheduleUnit $unit): self
{
$this->unit = $unit;
return $this;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'type' => $this->type,
'value' => $this->value,
'unit' => (string) $this->unit,
];
}
}
sentry/src/Client.php 0000644 00000041740 15266563341 0010627 0 ustar 00 The stack of integrations
*
* @psalm-var array, IntegrationInterface>
*/
private $integrations;
/**
* @var StacktraceBuilder
*/
private $stacktraceBuilder;
/**
* @var string The Sentry SDK identifier
*/
private $sdkIdentifier;
/**
* @var string The SDK version of the Client
*/
private $sdkVersion;
/**
* Constructor.
*
* @param Options $options The client configuration
* @param TransportInterface $transport The transport
* @param string|null $sdkIdentifier The Sentry SDK identifier
* @param string|null $sdkVersion The Sentry SDK version
* @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments
* @param LoggerInterface|null $logger The PSR-3 logger
*/
public function __construct(
Options $options,
TransportInterface $transport,
?string $sdkIdentifier = null,
?string $sdkVersion = null,
?RepresentationSerializerInterface $representationSerializer = null,
?LoggerInterface $logger = null
) {
$this->options = $options;
$this->transport = $transport;
$this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER;
$this->sdkVersion = $sdkVersion ?? self::SDK_VERSION;
$this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options));
$this->logger = $logger ?? new NullLogger();
$this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger);
}
/**
* {@inheritdoc}
*/
public function getOptions(): Options
{
return $this->options;
}
/**
* {@inheritdoc}
*/
public function getCspReportUrl(): ?string
{
$dsn = $this->options->getDsn();
if ($dsn === null) {
return null;
}
$endpoint = $dsn->getCspReportEndpointUrl();
$query = array_filter([
'sentry_release' => $this->options->getRelease(),
'sentry_environment' => $this->options->getEnvironment(),
]);
if (!empty($query)) {
$endpoint .= '&' . http_build_query($query, '', '&');
}
return $endpoint;
}
/**
* {@inheritdoc}
*/
public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId
{
$event = Event::createEvent();
$event->setMessage($message);
$event->setLevel($level);
return $this->captureEvent($event, $hint, $scope);
}
/**
* {@inheritdoc}
*/
public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId
{
$className = \get_class($exception);
if ($this->shouldIgnoreException($className)) {
$this->logger->info(
'The exception will be discarded because it matches an entry in "ignore_exceptions".',
['className' => $className]
);
return null; // short circuit to avoid unnecessary processing
}
$hint = $hint ?? new EventHint();
if ($hint->exception === null) {
$hint->exception = $exception;
}
return $this->captureEvent(Event::createEvent(), $hint, $scope);
}
/**
* {@inheritdoc}
*/
public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId
{
$event = $this->prepareEvent($event, $hint, $scope);
if ($event === null) {
return null;
}
try {
/** @var Result $result */
$result = $this->transport->send($event);
$event = $result->getEvent();
if ($event !== null) {
return $event->getId();
}
} catch (\Throwable $exception) {
$this->logger->error(
\sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()),
['exception' => $exception, 'event' => $event]
);
}
return null;
}
/**
* {@inheritdoc}
*/
public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId
{
$error = error_get_last();
if ($error === null || !isset($error['message'][0])) {
return null;
}
$exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']);
return $this->captureException($exception, $scope, $hint);
}
/**
* {@inheritdoc}
*
* @psalm-template T of IntegrationInterface
*/
public function getIntegration(string $className): ?IntegrationInterface
{
/** @psalm-var T|null */
return $this->integrations[$className] ?? null;
}
/**
* {@inheritdoc}
*/
public function flush(?int $timeout = null): Result
{
return $this->transport->close($timeout);
}
/**
* {@inheritdoc}
*/
public function getStacktraceBuilder(): StacktraceBuilder
{
return $this->stacktraceBuilder;
}
/**
* @internal
*/
public function getLogger(): LoggerInterface
{
return $this->logger;
}
/**
* @internal
*/
public function getTransport(): TransportInterface
{
return $this->transport;
}
public function getSdkIdentifier(): string
{
return $this->sdkIdentifier;
}
public function getSdkVersion(): string
{
return $this->sdkVersion;
}
/**
* Assembles an event and prepares it to be sent of to Sentry.
*
* @param Event $event The payload that will be converted to an Event
* @param EventHint|null $hint May contain additional information about the event
* @param Scope|null $scope Optional scope which enriches the Event
*
* @return Event|null The prepared event object or null if it must be discarded
*/
private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event
{
if ($hint !== null) {
if ($hint->exception !== null && empty($event->getExceptions())) {
$this->addThrowableToEvent($event, $hint->exception, $hint);
}
if ($hint->stacktrace !== null && $event->getStacktrace() === null) {
$event->setStacktrace($hint->stacktrace);
}
}
$this->addMissingStacktraceToEvent($event);
$event->setSdkIdentifier($this->sdkIdentifier);
$event->setSdkVersion($this->sdkVersion);
$event->setTags(array_merge($this->options->getTags(), $event->getTags()));
if ($event->getServerName() === null) {
$event->setServerName($this->options->getServerName());
}
if ($event->getRelease() === null) {
$event->setRelease($this->options->getRelease());
}
if ($event->getEnvironment() === null) {
$event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT);
}
$eventDescription = \sprintf(
'%s%s [%s]',
$event->getLevel() !== null ? $event->getLevel() . ' ' : '',
(string) $event->getType(),
(string) $event->getId()
);
$isEvent = EventType::event() === $event->getType();
$sampleRate = $this->options->getSampleRate();
// only sample with the `sample_rate` on errors/messages
if ($isEvent && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) {
$this->logger->info(\sprintf('The %s will be discarded because it has been sampled.', $eventDescription), ['event' => $event]);
return null;
}
$event = $this->applyIgnoreOptions($event, $eventDescription);
if ($event === null) {
return null;
}
if ($scope !== null) {
$beforeEventProcessors = $event;
$event = $scope->applyToEvent($event, $hint, $this->options);
if ($event === null) {
$this->logger->info(
\sprintf('The %s will be discarded because one of the event processors returned "null".', $eventDescription),
['event' => $beforeEventProcessors]
);
return null;
}
}
$beforeSendCallback = $event;
$event = $this->applyBeforeSendCallback($event, $hint);
if ($event === null) {
$this->logger->info(
\sprintf(
'The %s will be discarded because the "%s" callback returned "null".',
$eventDescription,
$this->getBeforeSendCallbackName($beforeSendCallback)
),
['event' => $beforeSendCallback]
);
}
return $event;
}
/**
* Checks if an exception should be ignored based on configured patterns.
* Supports both class hierarchy matching and regex patterns.
* Patterns starting and ending with '/' are treated as regex patterns.
*/
private function shouldIgnoreException(string $className): bool
{
foreach ($this->options->getIgnoreExceptions() as $pattern) {
// Check for regex pattern (starts with / and ends with / optionally followed by flags)
if (preg_match(self::REGEX_PATTERN_DETECTION, $pattern)) {
try {
if (preg_match($pattern, $className)) {
return true;
}
} catch (\Throwable $e) {
// Invalid regex pattern, log and skip
$this->logger->warning(
\sprintf('Invalid regex pattern in ignore_exceptions: "%s". Error: %s', $pattern, $e->getMessage())
);
continue;
}
} else {
// Class hierarchy check
if (is_a($className, $pattern, true)) {
return true;
}
}
}
return false;
}
/**
* Checks if a transaction should be ignored based on configured patterns.
* Supports both exact string matching and regex patterns.
* Patterns starting and ending with '/' are treated as regex patterns.
*/
private function shouldIgnoreTransaction(string $transactionName): bool
{
foreach ($this->options->getIgnoreTransactions() as $pattern) {
// Check for regex pattern (starts with / and ends with / optionally followed by flags)
if (preg_match(self::REGEX_PATTERN_DETECTION, $pattern)) {
try {
if (preg_match($pattern, $transactionName)) {
return true;
}
} catch (\Throwable $e) {
// Invalid regex pattern, log and skip
$this->logger->warning(
\sprintf('Invalid regex pattern in ignore_transactions: "%s". Error: %s', $pattern, $e->getMessage())
);
continue;
}
} else {
// Exact string match
if ($transactionName === $pattern) {
return true;
}
}
}
return false;
}
private function applyIgnoreOptions(Event $event, string $eventDescription): ?Event
{
if ($event->getType() === EventType::event()) {
$exceptions = $event->getExceptions();
if (empty($exceptions)) {
return $event;
}
foreach ($exceptions as $exception) {
if ($this->shouldIgnoreException($exception->getType())) {
$this->logger->info(
\sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription),
['event' => $event]
);
return null;
}
}
}
if ($event->getType() === EventType::transaction()) {
$transactionName = $event->getTransaction();
if ($transactionName === null) {
return $event;
}
if ($this->shouldIgnoreTransaction($transactionName)) {
$this->logger->info(
\sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription),
['event' => $event]
);
return null;
}
}
return $event;
}
private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event
{
switch ($event->getType()) {
case EventType::event():
return ($this->options->getBeforeSendCallback())($event, $hint);
case EventType::transaction():
return ($this->options->getBeforeSendTransactionCallback())($event, $hint);
case EventType::checkIn():
return ($this->options->getBeforeSendCheckInCallback())($event, $hint);
default:
return $event;
}
}
private function getBeforeSendCallbackName(Event $event): string
{
switch ($event->getType()) {
case EventType::transaction():
return 'before_send_transaction';
case EventType::checkIn():
return 'before_send_check_in';
default:
return 'before_send';
}
}
/**
* Optionally adds a missing stacktrace to the Event if the client is configured to do so.
*
* @param Event $event The Event to add the missing stacktrace to
*/
private function addMissingStacktraceToEvent(Event $event): void
{
if (!$this->options->shouldAttachStacktrace()) {
return;
}
// We should not add a stacktrace when the event already has one or contains exceptions
if ($event->getStacktrace() !== null || !empty($event->getExceptions())) {
return;
}
$event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace(
debug_backtrace(0),
__FILE__,
__LINE__ - 3
));
}
/**
* Stores the given exception in the passed event.
*
* @param Event $event The event that will be enriched with the exception
* @param \Throwable $exception The exception that will be processed and added to the event
* @param EventHint $hint Contains additional information about the event
*/
private function addThrowableToEvent(Event $event, \Throwable $exception, EventHint $hint): void
{
if ($exception instanceof \ErrorException && $event->getLevel() === null) {
$event->setLevel(Severity::fromError($exception->getSeverity()));
}
$exceptions = [];
do {
$exceptions[] = new ExceptionDataBag(
$exception,
$this->stacktraceBuilder->buildFromException($exception),
$hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => $exception->getCode()])
);
} while ($exception = $exception->getPrevious());
$event->setExceptions($exceptions);
}
}
sentry/src/Tracing/SpanContext.php 0000644 00000010670 15266563341 0013244 0 ustar 00 A List of tags associated to this Span
*/
private $tags = [];
/**
* @var array An arbitrary mapping of additional metadata
*/
private $data = [];
/**
* @var float|null Timestamp in seconds (epoch time) indicating when the span started
*/
private $startTimestamp;
/**
* @var float|null Timestamp in seconds (epoch time) indicating when the span ended
*/
private $endTimestamp;
/**
* @var string|null the trace origin of the span
*/
private $origin;
/**
* @return self
*/
public static function make()
{
return new self();
}
public function getDescription(): ?string
{
return $this->description;
}
/**
* @return $this
*/
public function setDescription(?string $description)
{
$this->description = $description;
return $this;
}
public function getOp(): ?string
{
return $this->op;
}
/**
* @return $this
*/
public function setOp(?string $op)
{
$this->op = $op;
return $this;
}
public function getStatus(): ?SpanStatus
{
return $this->status;
}
/**
* @return $this
*/
public function setStatus(?SpanStatus $status)
{
$this->status = $status;
return $this;
}
public function getParentSpanId(): ?SpanId
{
return $this->parentSpanId;
}
/**
* @return $this
*/
public function setParentSpanId(?SpanId $parentSpanId)
{
$this->parentSpanId = $parentSpanId;
return $this;
}
public function getSampled(): ?bool
{
return $this->sampled;
}
/**
* @return $this
*/
public function setSampled(?bool $sampled)
{
$this->sampled = $sampled;
return $this;
}
public function getSpanId(): ?SpanId
{
return $this->spanId;
}
/**
* @return $this
*/
public function setSpanId(?SpanId $spanId)
{
$this->spanId = $spanId;
return $this;
}
public function getTraceId(): ?TraceId
{
return $this->traceId;
}
/**
* @return $this
*/
public function setTraceId(?TraceId $traceId)
{
$this->traceId = $traceId;
return $this;
}
/**
* @return array
*/
public function getTags(): array
{
return $this->tags;
}
/**
* @param array $tags
*
* @return $this
*/
public function setTags(array $tags)
{
$this->tags = $tags;
return $this;
}
/**
* @return array
*/
public function getData(): array
{
return $this->data;
}
/**
* @param array $data
*
* @return $this
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
public function getStartTimestamp(): ?float
{
return $this->startTimestamp;
}
/**
* @return $this
*/
public function setStartTimestamp(?float $startTimestamp)
{
$this->startTimestamp = $startTimestamp;
return $this;
}
public function getEndTimestamp(): ?float
{
return $this->endTimestamp;
}
/**
* @return $this
*/
public function setEndTimestamp(?float $endTimestamp)
{
$this->endTimestamp = $endTimestamp;
return $this;
}
public function getOrigin(): ?string
{
return $this->origin;
}
/**
* @return $this
*/
public function setOrigin(?string $origin)
{
$this->origin = $origin;
return $this;
}
}
sentry/src/Tracing/GuzzleTracingMiddleware.php 0000644 00000013636 15266563341 0015571 0 ustar 00 getClient();
$parentSpan = $hub->getSpan();
$partialUri = Uri::fromParts([
'scheme' => $request->getUri()->getScheme(),
'host' => $request->getUri()->getHost(),
'port' => $request->getUri()->getPort(),
'path' => $request->getUri()->getPath(),
]);
$spanAndBreadcrumbData = [
'http.request.method' => $request->getMethod(),
'http.request.body.size' => $request->getBody()->getSize(),
];
if ($request->getUri()->getQuery() !== '') {
$spanAndBreadcrumbData['http.query'] = $request->getUri()->getQuery();
}
if ($request->getUri()->getFragment() !== '') {
$spanAndBreadcrumbData['http.fragment'] = $request->getUri()->getFragment();
}
$childSpan = null;
if ($parentSpan !== null && $parentSpan->getSampled()) {
$spanContext = new SpanContext();
$spanContext->setOp('http.client');
$spanContext->setData($spanAndBreadcrumbData);
$spanContext->setOrigin('auto.http.guzzle');
$spanContext->setDescription($request->getMethod() . ' ' . $partialUri);
$childSpan = $parentSpan->startChild($spanContext);
$hub->setSpan($childSpan);
}
if (self::shouldAttachTracingHeaders($client, $request)) {
$request = $request
->withHeader('sentry-trace', getTraceparent())
->withHeader('baggage', getBaggage());
}
$handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $parentSpan, $partialUri) {
if ($childSpan !== null) {
// We finish the span (which means setting the span end timestamp) first to ensure the measured time
// the span spans is as close to only the HTTP request time and do the data collection afterwards
$childSpan->finish();
$hub->setSpan($parentSpan);
}
$response = null;
/** @psalm-suppress UndefinedClass */
if ($responseOrException instanceof ResponseInterface) {
$response = $responseOrException;
} elseif ($responseOrException instanceof GuzzleRequestException) {
$response = $responseOrException->getResponse();
}
$breadcrumbLevel = Breadcrumb::LEVEL_INFO;
if ($response !== null) {
$spanAndBreadcrumbData['http.response.body.size'] = $response->getBody()->getSize();
$spanAndBreadcrumbData['http.response.status_code'] = $response->getStatusCode();
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
$breadcrumbLevel = Breadcrumb::LEVEL_WARNING;
} elseif ($response->getStatusCode() >= 500) {
$breadcrumbLevel = Breadcrumb::LEVEL_ERROR;
}
}
if ($childSpan !== null) {
if ($response !== null) {
$childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode()));
$childSpan->setData($spanAndBreadcrumbData);
} else {
$childSpan->setStatus(SpanStatus::internalError());
}
}
$hub->addBreadcrumb(new Breadcrumb(
$breadcrumbLevel,
Breadcrumb::TYPE_HTTP,
'http',
null,
array_merge([
'url' => (string) $partialUri,
], $spanAndBreadcrumbData)
));
if ($responseOrException instanceof \Throwable) {
throw $responseOrException;
}
return $responseOrException;
};
return $handler($request, $options)->then($handlerPromiseCallback, $handlerPromiseCallback);
};
};
}
private static function shouldAttachTracingHeaders(?ClientInterface $client, RequestInterface $request): bool
{
if ($client === null) {
return false;
}
$sdkOptions = $client->getOptions();
// Check if the request destination is allow listed in the trace_propagation_targets option.
return $sdkOptions->getTracePropagationTargets() === null
|| \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets());
}
}
sentry/src/Tracing/TransactionSource.php 0000644 00000003512 15266563341 0014441 0 ustar 00 A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
/**
* User-defined name.
*/
public static function custom(): self
{
return self::getInstance('custom');
}
/**
* Raw URL, potentially containing identifiers.
*/
public static function url(): self
{
return self::getInstance('url');
}
/**
* Parametrized URL / route.
*/
public static function route(): self
{
return self::getInstance('route');
}
/**
* Name of the view handling the request.
*/
public static function view(): self
{
return self::getInstance('view');
}
/**
* Named after a software component, such as a function or class name.
*/
public static function component(): self
{
return self::getInstance('component');
}
/**
* Name of a background task (e.g. a Celery task).
*/
public static function task(): self
{
return self::getInstance('task');
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/Tracing/TransactionMetadata.php 0000644 00000005622 15266563341 0014725 0 ustar 00 samplingRate = $samplingRate;
$this->dynamicSamplingContext = $dynamicSamplingContext;
$this->source = $source ?? TransactionSource::custom();
$this->parentSamplingRate = $parentSamplingRate;
$this->sampleRand = $sampleRand ?? round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
}
/**
* @return float|int|null
*/
public function getSamplingRate()
{
return $this->samplingRate;
}
/**
* @param float|int|null $samplingRate
*/
public function setSamplingRate($samplingRate): self
{
$this->samplingRate = $samplingRate;
return $this;
}
public function getParentSamplingRate(): ?float
{
return $this->parentSamplingRate;
}
public function setParentSamplingRate(?float $parentSamplingRate): self
{
$this->parentSamplingRate = $parentSamplingRate;
return $this;
}
public function getSampleRand(): ?float
{
return $this->sampleRand;
}
public function setSampleRand(?float $sampleRand): self
{
$this->sampleRand = $sampleRand;
return $this;
}
public function getDynamicSamplingContext(): ?DynamicSamplingContext
{
return $this->dynamicSamplingContext;
}
public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): self
{
$this->dynamicSamplingContext = $dynamicSamplingContext;
return $this;
}
public function getSource(): ?TransactionSource
{
return $this->source;
}
public function setSource(?TransactionSource $source): self
{
$this->source = $source;
return $this;
}
}
sentry/src/Tracing/TraceId.php 0000644 00000001513 15266563341 0012305 0 ustar 00 value = $value;
}
/**
* Generates a new trace ID.
*/
public static function generate(): self
{
return new self(SentryUid::generate());
}
public function __toString(): string
{
return $this->value;
}
}
sentry/src/Tracing/SpanId.php 0000644 00000002075 15266563341 0012154 0 ustar 00 value = $value;
}
/**
* Generates a new span ID.
*/
public static function generate(): self
{
return new self(substr(SentryUid::generate(), 0, 16));
}
/**
* Compares whether two objects are equals.
*
* @param SpanId $other The object to compare
*/
public function isEqualTo(self $other): bool
{
return $this->value === $other->value;
}
public function __toString(): string
{
return $this->value;
}
}
sentry/src/Tracing/Span.php 0000644 00000033232 15266563341 0011676 0 ustar 00 ,
* }
*/
class Span
{
/**
* @var SpanId Span ID
*/
protected $spanId;
/**
* @var TraceId Trace ID
*/
protected $traceId;
/**
* @var string|null Description of the span
*/
protected $description;
/**
* @var string|null Operation of the span
*/
protected $op;
/**
* @var SpanStatus|null Completion status of the span
*/
protected $status;
/**
* @var SpanId|null ID of the parent span
*/
protected $parentSpanId;
/**
* @var bool|null Has the sample decision been made?
*/
protected $sampled;
/**
* @var array A List of tags associated to this span
*/
protected $tags = [];
/**
* @var array An arbitrary mapping of additional metadata
*/
protected $data = [];
/**
* @var float Timestamp in seconds (epoch time) indicating when the span started
*/
protected $startTimestamp;
/**
* @var float|null Timestamp in seconds (epoch time) indicating when the span ended
*/
protected $endTimestamp;
/**
* @var SpanRecorder|null Reference instance to the {@see SpanRecorder}
*/
protected $spanRecorder;
/**
* @var Transaction|null The transaction containing this span
*/
protected $transaction;
/**
* @var string|null The trace origin of the span. If no origin is set, the span is considered to be "manual".
*
* @see https://develop.sentry.dev/sdk/performance/trace-origin/
*/
protected $origin;
/**
* Constructor.
*
* @param SpanContext|null $context The context to create the span with
*
* @internal
*/
public function __construct(?SpanContext $context = null)
{
if ($context === null) {
$this->traceId = TraceId::generate();
$this->spanId = SpanId::generate();
$this->startTimestamp = microtime(true);
return;
}
$this->traceId = $context->getTraceId() ?? TraceId::generate();
$this->spanId = $context->getSpanId() ?? SpanId::generate();
$this->startTimestamp = $context->getStartTimestamp() ?? microtime(true);
$this->parentSpanId = $context->getParentSpanId();
$this->description = $context->getDescription();
$this->op = $context->getOp();
$this->status = $context->getStatus();
$this->sampled = $context->getSampled();
$this->tags = $context->getTags();
$this->data = $context->getData();
$this->endTimestamp = $context->getEndTimestamp();
$this->origin = $context->getOrigin();
}
/**
* Sets the ID of the span.
*
* @param SpanId $spanId The ID
*/
public function setSpanId(SpanId $spanId): self
{
$this->spanId = $spanId;
return $this;
}
/**
* Gets the ID that determines which trace the span belongs to.
*/
public function getTraceId(): TraceId
{
return $this->traceId;
}
/**
* Sets the ID that determines which trace the span belongs to.
*
* @param TraceId $traceId The ID
*
* @return $this
*/
public function setTraceId(TraceId $traceId)
{
$this->traceId = $traceId;
return $this;
}
/**
* Gets the ID that determines which span is the parent of the current one.
*/
public function getParentSpanId(): ?SpanId
{
return $this->parentSpanId;
}
/**
* Sets the ID that determines which span is the parent of the current one.
*
* @param SpanId|null $parentSpanId The ID
*
* @return $this
*/
public function setParentSpanId(?SpanId $parentSpanId)
{
$this->parentSpanId = $parentSpanId;
return $this;
}
/**
* Gets the timestamp representing when the measuring started.
*/
public function getStartTimestamp(): float
{
return $this->startTimestamp;
}
/**
* Sets the timestamp representing when the measuring started.
*
* @param float $startTimestamp The timestamp
*
* @return $this
*/
public function setStartTimestamp(float $startTimestamp)
{
$this->startTimestamp = $startTimestamp;
return $this;
}
/**
* Gets the timestamp representing when the measuring finished.
*/
public function getEndTimestamp(): ?float
{
return $this->endTimestamp;
}
/**
* Gets a description of the span's operation, which uniquely identifies
* the span but is consistent across instances of the span.
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* Sets a description of the span's operation, which uniquely identifies
* the span but is consistent across instances of the span.
*
* @param string|null $description The description
*
* @return $this
*/
public function setDescription(?string $description)
{
$this->description = $description;
return $this;
}
/**
* Gets a short code identifying the type of operation the span is measuring.
*/
public function getOp(): ?string
{
return $this->op;
}
/**
* Sets a short code identifying the type of operation the span is measuring.
*
* @param string|null $op The short code
*
* @return $this
*/
public function setOp(?string $op)
{
$this->op = $op;
return $this;
}
/**
* Gets the status of the span/transaction.
*/
public function getStatus(): ?SpanStatus
{
return $this->status;
}
/**
* Sets the status of the span/transaction.
*
* @param SpanStatus|null $status The status
*
* @return $this
*/
public function setStatus(?SpanStatus $status)
{
$this->status = $status;
return $this;
}
/**
* Sets the HTTP status code and the status of the span/transaction.
*
* @param int $statusCode The HTTP status code
*
* @return $this
*/
public function setHttpStatus(int $statusCode)
{
SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($statusCode) {
$scope->setContext('response', [
'status_code' => $statusCode,
]);
});
$status = SpanStatus::createFromHttpStatusCode($statusCode);
if ($status !== SpanStatus::unknownError()) {
$this->status = $status;
}
return $this;
}
/**
* Gets a map of tags for this event.
*
* @return array
*/
public function getTags(): array
{
return $this->tags;
}
/**
* Sets a map of tags for this event. This method will merge the given tags with
* the existing ones.
*
* @param array $tags The tags
*
* @return $this
*/
public function setTags(array $tags)
{
$this->tags = array_merge($this->tags, $tags);
return $this;
}
/**
* Gets the ID of the span.
*/
public function getSpanId(): SpanId
{
return $this->spanId;
}
/**
* Gets the flag determining whether this span should be sampled or not.
*/
public function getSampled(): ?bool
{
return $this->sampled;
}
/**
* Sets the flag determining whether this span should be sampled or not.
*
* @param bool $sampled Whether to sample or not this span
*
* @return $this
*/
public function setSampled(?bool $sampled)
{
$this->sampled = $sampled;
return $this;
}
/**
* Gets a map of arbitrary data or a specific key from the map of data attached to this span.
*
* @param string|null $key Select a specific key from the data to return the value of
* @param mixed $default When the $key is not found, return this value
*
* @return ($key is null ? array : mixed|null)
*/
public function getData(?string $key = null, $default = null)
{
if ($key === null) {
return $this->data;
}
return $this->data[$key] ?? $default;
}
/**
* Sets a map of arbitrary data. This method will merge the given data with the existing one.
*
* @param array $data The data
*
* @return $this
*/
public function setData(array $data)
{
$this->data = array_merge($this->data, $data);
return $this;
}
/**
* Gets the data in a format suitable for storage in the "trace" context.
*
* @return array
*
* @psalm-return array{
* data?: array,
* description?: string,
* op?: string,
* parent_span_id?: string,
* span_id: string,
* status?: string,
* tags?: array,
* trace_id: string,
* origin: string,
* }
*/
public function getTraceContext(): array
{
$result = [
'span_id' => (string) $this->spanId,
'trace_id' => (string) $this->traceId,
'origin' => $this->origin ?? 'manual',
];
if ($this->parentSpanId !== null) {
$result['parent_span_id'] = (string) $this->parentSpanId;
}
if ($this->description !== null) {
$result['description'] = $this->description;
}
if ($this->op !== null) {
$result['op'] = $this->op;
}
if ($this->status !== null) {
$result['status'] = (string) $this->status;
}
if (!empty($this->data)) {
$result['data'] = $this->data;
}
if (!empty($this->tags)) {
$result['tags'] = $this->tags;
}
return $result;
}
/**
* Sets the finish timestamp on the current span.
*
* @param float|null $endTimestamp Takes an endTimestamp if the end should not be the time when you call this function
*
* @return EventId|null Finish for a span always returns null
*/
public function finish(?float $endTimestamp = null): ?EventId
{
$this->endTimestamp = $endTimestamp ?? microtime(true);
return null;
}
/**
* Creates a new {@see Span} while setting the current ID as `parentSpanId`.
* Also the `sampled` decision will be inherited.
*
* @param SpanContext $context The context of the child span
*/
public function startChild(SpanContext $context): self
{
$context = clone $context;
$context->setSampled($this->sampled);
$context->setParentSpanId($this->spanId);
$context->setTraceId($this->traceId);
$span = new self($context);
$span->transaction = $this->transaction;
$span->spanRecorder = $this->spanRecorder;
if ($span->spanRecorder !== null) {
$span->spanRecorder->add($span);
}
return $span;
}
/**
* Gets the span recorder attached to this span.
*
* @internal
*/
public function getSpanRecorder(): ?SpanRecorder
{
return $this->spanRecorder;
}
/**
* Detaches the span recorder from this instance.
*
* @return $this
*/
public function detachSpanRecorder()
{
$this->spanRecorder = null;
return $this;
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function getMetricsSummary(): array
{
return [];
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function setMetricsSummary(
string $type,
string $key,
$value,
MetricsUnit $unit,
array $tags
): void {
}
/**
* Sets the trace origin for this span.
*/
public function getOrigin(): ?string
{
return $this->origin;
}
/**
* Sets the trace origin of the span.
*
* @return $this
*/
public function setOrigin(?string $origin)
{
$this->origin = $origin;
return $this;
}
/**
* Returns the transaction containing this span.
*/
public function getTransaction(): ?Transaction
{
return $this->transaction;
}
/**
* Returns a string that can be used for the `sentry-trace` header & meta tag.
*/
public function toTraceparent(): string
{
$sampled = '';
if ($this->sampled !== null) {
$sampled = $this->sampled ? '-1' : '-0';
}
return \sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled);
}
/**
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
*
* @deprecated since version 4.12. To be removed in version 5.0.
*/
public function toW3CTraceparent(): string
{
return '';
}
/**
* Returns a string that can be used for the `baggage` header & meta tag.
*/
public function toBaggage(): string
{
$transaction = $this->getTransaction();
if ($transaction !== null) {
return (string) $transaction->getDynamicSamplingContext();
}
return '';
}
}
sentry/src/Tracing/Traits/TraceHeaderParserTrait.php 0000644 00000007660 15266563341 0016601 0 ustar 00 [0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i';
/**
* Parses the sentry-trace and baggage headers and returns the extracted data.
*
* @param string $sentryTrace The sentry-trace header value
* @param string $baggage The baggage header value
*
* @return array{
* traceId: TraceId|null,
* parentSpanId: SpanId|null,
* parentSampled: bool|null,
* dynamicSamplingContext: DynamicSamplingContext|null,
* sampleRand: float|null,
* parentSamplingRate: float|null
* }
*/
protected static function parseTraceAndBaggageHeaders(string $sentryTrace, string $baggage): array
{
$result = [
'traceId' => null,
'parentSpanId' => null,
'parentSampled' => null,
'dynamicSamplingContext' => null,
'sampleRand' => null,
'parentSamplingRate' => null,
];
$hasSentryTrace = false;
if (preg_match(self::$sentryTraceparentHeaderRegex, $sentryTrace, $matches)) {
if (!empty($matches['trace_id'])) {
$result['traceId'] = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}
if (!empty($matches['span_id'])) {
$result['parentSpanId'] = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}
if (isset($matches['sampled'])) {
$result['parentSampled'] = $matches['sampled'] === '1';
$hasSentryTrace = true;
}
}
$samplingContext = DynamicSamplingContext::fromHeader($baggage);
if ($hasSentryTrace && !$samplingContext->hasEntries()) {
// The request comes from an old SDK which does not support Dynamic Sampling.
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
$samplingContext->freeze();
$result['dynamicSamplingContext'] = $samplingContext;
}
if ($hasSentryTrace && $samplingContext->hasEntries()) {
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
// Propagate this Dynamic Sampling Context.
$result['dynamicSamplingContext'] = $samplingContext;
}
// Store the propagated traces sample rate
if ($samplingContext->has('sample_rate')) {
$result['parentSamplingRate'] = (float) $samplingContext->get('sample_rate');
}
// Store the propagated trace sample rand or generate a new one
if ($samplingContext->has('sample_rand')) {
$result['sampleRand'] = (float) $samplingContext->get('sample_rand');
} else {
if ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) {
if ($result['parentSampled'] === true) {
// [0, rate)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
} else {
// [rate, 1)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6);
}
} elseif ($result['parentSampled'] !== null) {
// [0, 1)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
}
}
return $result;
}
}
sentry/src/Tracing/SpanRecorder.php 0000644 00000002167 15266563341 0013367 0 ustar 00 maxSpans = $maxSpans;
}
/**
* Adds a span to the list of recorded spans or detaches the recorder if the
* maximum number of spans to store has been reached.
*/
public function add(Span $span): self
{
if (\count($this->spans) > $this->maxSpans) {
$span->detachSpanRecorder();
} else {
$this->spans[] = $span;
}
return $this;
}
/**
* Gets all the spans managed by this recorder.
*
* @return Span[]
*/
public function getSpans(): array
{
return $this->spans;
}
}
sentry/src/Tracing/TransactionContext.php 0000644 00000011172 15266563341 0014626 0 ustar 00 ';
/**
* @var string Name of the transaction
*/
private $name;
/**
* @var bool|null The parent's sampling decision
*/
private $parentSampled;
/**
* @var TransactionMetadata The transaction metadata
*/
private $metadata;
/**
* Constructor.
*
* @param string $name The name of the transaction
* @param bool|null $parentSampled The parent's sampling decision
* @param TransactionMetadata|null $metadata The transaction metadata
*/
public function __construct(
string $name = self::DEFAULT_NAME,
?bool $parentSampled = null,
?TransactionMetadata $metadata = null
) {
$this->name = $name;
$this->parentSampled = $parentSampled;
$this->metadata = $metadata ?? new TransactionMetadata();
}
/**
* @return self
*/
public static function make()
{
return new self();
}
/**
* Gets the name of the transaction.
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets the name of the transaction.
*
* @param string $name The name
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Gets the parent's sampling decision.
*/
public function getParentSampled(): ?bool
{
return $this->parentSampled;
}
/**
* Sets the parent's sampling decision.
*
* @param bool|null $parentSampled The decision
*/
public function setParentSampled(?bool $parentSampled): self
{
$this->parentSampled = $parentSampled;
return $this;
}
/**
* Gets the transaction metadata.
*/
public function getMetadata(): TransactionMetadata
{
return $this->metadata;
}
/**
* Sets the transaction metadata.
*
* @param TransactionMetadata $metadata The transaction metadata
*/
public function setMetadata(TransactionMetadata $metadata): self
{
$this->metadata = $metadata;
return $this;
}
/**
* Sets the transaction source.
*
* @param TransactionSource $transactionSource The transaction source
*/
public function setSource(TransactionSource $transactionSource): self
{
$this->metadata->setSource($transactionSource);
return $this;
}
/**
* Returns a context populated with the data of the given environment variables.
*
* @param string $sentryTrace The sentry-trace value from the environment
* @param string $baggage The baggage header value from the environment
*/
public static function fromEnvironment(string $sentryTrace, string $baggage): self
{
return self::parseTraceAndBaggage($sentryTrace, $baggage);
}
/**
* Returns a context populated with the data of the given headers.
*
* @param string $sentryTraceHeader The sentry-trace header from an incoming request
* @param string $baggageHeader The baggage header from an incoming request
*/
public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self
{
return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader);
}
private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self
{
$context = new self();
$parsedData = self::parseTraceAndBaggageHeaders($sentryTrace, $baggage);
if ($parsedData['traceId'] !== null) {
$context->traceId = $parsedData['traceId'];
}
if ($parsedData['parentSpanId'] !== null) {
$context->parentSpanId = $parsedData['parentSpanId'];
}
if ($parsedData['parentSampled'] !== null) {
$context->parentSampled = $parsedData['parentSampled'];
}
if ($parsedData['dynamicSamplingContext'] !== null) {
$context->getMetadata()->setDynamicSamplingContext($parsedData['dynamicSamplingContext']);
}
if ($parsedData['parentSamplingRate'] !== null) {
$context->getMetadata()->setParentSamplingRate($parsedData['parentSamplingRate']);
}
if ($parsedData['sampleRand'] !== null) {
$context->getMetadata()->setSampleRand($parsedData['sampleRand']);
}
return $context;
}
}
sentry/src/Tracing/SamplingContext.php 0000644 00000004365 15266563341 0014121 0 ustar 00 |null Additional context, depending on where the SDK runs
*/
private $additionalContext;
/**
* Returns an instance populated with the data of the transaction context.
*/
public static function getDefault(TransactionContext $transactionContext): self
{
$context = new self();
$context->transactionContext = $transactionContext;
$context->parentSampled = $transactionContext->getParentSampled();
$context->sampleRand = $transactionContext->getMetadata()->getSampleRand();
return $context;
}
public function getTransactionContext(): ?TransactionContext
{
return $this->transactionContext;
}
/**
* Gets the sampling decision from the parent transaction, if any.
*/
public function getParentSampled(): ?bool
{
return $this->parentSampled;
}
public function getSampleRand(): ?float
{
return $this->sampleRand;
}
/**
* Sets the sampling decision from the parent transaction, if any.
*/
public function setParentSampled(?bool $parentSampled): self
{
$this->parentSampled = $parentSampled;
return $this;
}
/**
* Sets additional data that will be provided as a second argument to {@link \Sentry\startTransaction()}.
*
* @param array|null $additionalContext
*/
public function setAdditionalContext(?array $additionalContext): self
{
$this->additionalContext = $additionalContext;
return $this;
}
/**
* Gets the additional data that will be provided as a second argument to {@link \Sentry\startTransaction()}.
*
* @return array|null
*/
public function getAdditionalContext(): ?array
{
return $this->additionalContext;
}
}
sentry/src/Tracing/DynamicSamplingContext.php 0000644 00000015672 15266563341 0015431 0 ustar 00 The dsc entries
*/
private $entries = [];
/**
* @var bool Indicates if the dsc is mutable or immutable
*/
private $isFrozen = false;
/**
* Construct a new dsc object.
*/
private function __construct()
{
}
/**
* Set a new key value pair on the dsc.
*
* @param string $key the list member key
* @param string $value the list member value
*/
public function set(string $key, string $value, bool $forceOverwrite = false): self
{
if ($this->isFrozen && !$forceOverwrite) {
return $this;
}
$this->entries[$key] = $value;
return $this;
}
/**
* Check if a key value pair is set on the dsc.
*
* @param string $key the list member key
*/
public function has(string $key): bool
{
return isset($this->entries[$key]);
}
/**
* Get a value from the dsc.
*
* @param string $key the list member key
* @param string|null $default the default value to return if no value exists
*/
public function get(string $key, ?string $default = null): ?string
{
return $this->entries[$key] ?? $default;
}
/**
* Mark the dsc as frozen.
*/
public function freeze(): self
{
$this->isFrozen = true;
return $this;
}
/**
* Indicates that the dsc is frozen and cannot be mutated.
*/
public function isFrozen(): bool
{
return $this->isFrozen;
}
/**
* Check if there are any entries set.
*/
public function hasEntries(): bool
{
return !empty($this->entries);
}
/**
* Gets the dsc entries.
*
* @return array
*/
public function getEntries(): array
{
return $this->entries;
}
/**
* Parse the baggage header.
*
* @param string $header the baggage header contents
*/
public static function fromHeader(string $header): self
{
$samplingContext = new self();
foreach (explode(',', $header) as $listMember) {
if (empty(trim($listMember))) {
continue;
}
$keyValueAndProperties = explode(';', $listMember, 2);
$keyValue = trim($keyValueAndProperties[0]);
if (!str_contains($keyValue, '=')) {
continue;
}
[$key, $value] = explode('=', $keyValue, 2);
if (mb_substr($key, 0, mb_strlen(self::SENTRY_ENTRY_PREFIX)) === self::SENTRY_ENTRY_PREFIX) {
$samplingContext->set(rawurldecode(mb_substr($key, mb_strlen(self::SENTRY_ENTRY_PREFIX))), rawurldecode($value));
}
}
// Once we receive a baggage header with Sentry entries from an upstream SDK,
// we freeze the contents so it cannot be mutated anymore by this SDK.
// It should only be propagated to the next downstream SDK or the Sentry server itself.
$samplingContext->isFrozen = $samplingContext->hasEntries();
return $samplingContext;
}
/**
* Create a dsc object.
*
* @see https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#baggage-header
*/
public static function fromTransaction(Transaction $transaction, HubInterface $hub): self
{
$samplingContext = new self();
$samplingContext->set('trace_id', (string) $transaction->getTraceId());
$sampleRate = $transaction->getMetaData()->getSamplingRate();
if ($sampleRate !== null) {
$samplingContext->set('sample_rate', (string) $sampleRate);
}
// Only include the transaction name if it has good quality
if ($transaction->getMetadata()->getSource() !== TransactionSource::url()) {
$samplingContext->set('transaction', $transaction->getName());
}
$client = $hub->getClient();
if ($client !== null) {
$options = $client->getOptions();
if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) {
$samplingContext->set('public_key', $options->getDsn()->getPublicKey());
}
if ($options->getDsn() !== null && $options->getDsn()->getOrgId() !== null) {
$samplingContext->set('org_id', (string) $options->getDsn()->getOrgId());
}
if ($options->getRelease() !== null) {
$samplingContext->set('release', $options->getRelease());
}
if ($options->getEnvironment() !== null) {
$samplingContext->set('environment', $options->getEnvironment());
}
}
if ($transaction->getSampled() !== null) {
$samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false');
}
if ($transaction->getMetadata()->getSampleRand() !== null) {
$samplingContext->set('sample_rand', (string) $transaction->getMetadata()->getSampleRand());
}
$samplingContext->freeze();
return $samplingContext;
}
public static function fromOptions(Options $options, Scope $scope): self
{
$samplingContext = new self();
$samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId());
$samplingContext->set('sample_rand', (string) $scope->getPropagationContext()->getSampleRand());
if ($options->getTracesSampleRate() !== null) {
$samplingContext->set('sample_rate', (string) $options->getTracesSampleRate());
}
if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) {
$samplingContext->set('public_key', $options->getDsn()->getPublicKey());
}
if ($options->getDsn() !== null && $options->getDsn()->getOrgId() !== null) {
$samplingContext->set('org_id', (string) $options->getDsn()->getOrgId());
}
if ($options->getRelease() !== null) {
$samplingContext->set('release', $options->getRelease());
}
if ($options->getEnvironment() !== null) {
$samplingContext->set('environment', $options->getEnvironment());
}
$samplingContext->freeze();
return $samplingContext;
}
/**
* Serialize the dsc as a string.
*/
public function __toString(): string
{
$result = [];
foreach ($this->entries as $key => $value) {
$result[] = rawurlencode(self::SENTRY_ENTRY_PREFIX . $key) . '=' . rawurlencode($value);
}
return implode(',', $result);
}
}
sentry/src/Tracing/SpanStatus.php 0000644 00000013234 15266563341 0013102 0 ustar 00
*/
private static $instances = [];
/**
* Constructor.
*
* @param string $value The value of the enum instance
*/
private function __construct(string $value)
{
$this->value = $value;
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 401 Unauthorized (actually does mean unauthenticated according to RFC 7235).
*/
public static function unauthenticated(): self
{
return self::getInstance('unauthenticated');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 403 Forbidden.
*/
public static function permissionDenied(): self
{
return self::getInstance('permission_denied');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 404 Not Found.
*/
public static function notFound(): self
{
return self::getInstance('not_found');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 409 Already exists.
*/
public static function alreadyExists(): self
{
return self::getInstance('already_exists');
}
/**
* Gets an instance of this enum representing the fact that the operation
* was rejected because the system is not in a state required for the
* operation.
*/
public static function failedPrecondition(): self
{
return self::getInstance('failed_precondition');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 429 Too Many Requests.
*/
public static function resourceExhausted(): self
{
return self::getInstance('resource_exhausted');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 429 Too Many Requests.
*
* @deprecated since version 4.7. To be removed in version 5.0. Use SpanStatus::resourceExhausted() instead.
*/
public static function resourceExchausted(): self
{
return self::resourceExhausted();
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 501 Not Implemented.
*/
public static function unimplemented(): self
{
return self::getInstance('unimplemented');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 503 Service Unavailable.
*/
public static function unavailable(): self
{
return self::getInstance('unavailable');
}
/**
* Gets an instance of this enum representing the fact that the deadline
* expired before operation could complete.
*/
public static function deadlineExceeded(): self
{
return self::getInstance('deadline_exceeded');
}
/**
* Gets an instance of this enum representing the fact that the operation
* completed successfully.
*/
public static function ok(): self
{
return self::getInstance('ok');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 4xx as response status code.
*/
public static function invalidArgument(): self
{
return self::getInstance('invalid_argument');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* 5xx as response status code.
*/
public static function internalError(): self
{
return self::getInstance('internal_error');
}
/**
* Gets an instance of this enum representing the fact that the server returned
* with any non-standard HTTP status code.
*/
public static function unknownError(): self
{
return self::getInstance('unknown_error');
}
/**
* Returns an instance of this enum according to the given HTTP status code.
*
* @param int $statusCode The HTTP status code
*/
public static function createFromHttpStatusCode(int $statusCode): self
{
switch (true) {
case $statusCode === 401:
return self::unauthenticated();
case $statusCode === 403:
return self::permissionDenied();
case $statusCode === 404:
return self::notFound();
case $statusCode === 409:
return self::alreadyExists();
case $statusCode === 413:
return self::failedPrecondition();
case $statusCode === 429:
return self::resourceExhausted();
case $statusCode === 501:
return self::unimplemented();
case $statusCode === 503:
return self::unavailable();
case $statusCode === 504:
return self::deadlineExceeded();
case $statusCode < 400:
return self::ok();
case $statusCode < 500:
return self::invalidArgument();
case $statusCode < 600:
return self::internalError();
default:
return self::unknownError();
}
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/Tracing/Transaction.php 0000644 00000011405 15266563341 0013260 0 ustar 00 hub = $hub ?? SentrySdk::getCurrentHub();
$this->name = $context->getName();
$this->metadata = $context->getMetadata();
$this->transaction = $this;
}
/**
* Gets the name of this transaction.
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets the name of this transaction.
*
* @param string $name The name
*
* @return $this
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Gets the transaction metadata.
*/
public function getMetadata(): TransactionMetadata
{
return $this->metadata;
}
/**
* Gets the transaction dynamic sampling context.
*/
public function getDynamicSamplingContext(): DynamicSamplingContext
{
if ($this->metadata->getDynamicSamplingContext() !== null) {
return $this->metadata->getDynamicSamplingContext();
}
$samplingContext = DynamicSamplingContext::fromTransaction($this->transaction, $this->hub);
$this->getMetadata()->setDynamicSamplingContext($samplingContext);
return $samplingContext;
}
/**
* Attaches a {@see SpanRecorder} to the transaction itself.
*
* @param int $maxSpans The maximum number of spans that can be recorded
*/
public function initSpanRecorder(int $maxSpans = 1000): self
{
if ($this->spanRecorder === null) {
$this->spanRecorder = new SpanRecorder($maxSpans);
}
$this->spanRecorder->add($this);
return $this;
}
public function initProfiler(): Profiler
{
if ($this->profiler === null) {
$client = $this->hub->getClient();
$options = $client !== null ? $client->getOptions() : null;
$this->profiler = new Profiler($options);
}
return $this->profiler;
}
public function getProfiler(): ?Profiler
{
return $this->profiler;
}
public function detachProfiler(): self
{
$this->profiler = null;
return $this;
}
/**
* {@inheritdoc}
*/
public function finish(?float $endTimestamp = null): ?EventId
{
if ($this->profiler !== null) {
$this->profiler->stop();
}
if ($this->endTimestamp !== null) {
// Transaction was already finished once and we don't want to re-flush it
return null;
}
parent::finish($endTimestamp);
if ($this->sampled !== true) {
return null;
}
$finishedSpans = [];
if ($this->spanRecorder !== null) {
foreach ($this->spanRecorder->getSpans() as $span) {
if ($span->getSpanId() !== $this->getSpanId() && $span->getEndTimestamp() !== null) {
$finishedSpans[] = $span;
}
}
}
$event = Event::createTransaction();
$event->setSpans($finishedSpans);
$event->setStartTimestamp($this->startTimestamp);
$event->setTimestamp($this->endTimestamp);
$event->setTags($this->tags);
$event->setTransaction($this->name);
$event->setContext('trace', $this->getTraceContext());
$event->setSdkMetadata('dynamic_sampling_context', $this->getDynamicSamplingContext());
$event->setSdkMetadata('transaction_metadata', $this->getMetadata());
if ($this->profiler !== null) {
$profile = $this->profiler->getProfile();
if ($profile !== null) {
$event->setSdkMetadata('profile', $profile);
}
}
return $this->hub->captureEvent($event);
}
}
sentry/src/Tracing/PropagationContext.php 0000644 00000012261 15266563341 0014624 0 ustar 00 traceId = TraceId::generate();
$context->spanId = SpanId::generate();
$context->parentSpanId = null;
$context->parentSampled = null;
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
$context->dynamicSamplingContext = null;
return $context;
}
public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self
{
return self::parseTraceparentAndBaggage($sentryTraceHeader, $baggageHeader);
}
public static function fromEnvironment(string $sentryTrace, string $baggage): self
{
return self::parseTraceparentAndBaggage($sentryTrace, $baggage);
}
/**
* Returns a string that can be used for the `sentry-trace` header & meta tag.
*/
public function toTraceparent(): string
{
return \sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId);
}
/**
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
*
* @deprecated since version 4.12. To be removed in version 5.0.
*/
public function toW3CTraceparent(): string
{
return '';
}
/**
* Returns a string that can be used for the `baggage` header & meta tag.
*/
public function toBaggage(): string
{
if ($this->dynamicSamplingContext === null) {
$hub = SentrySdk::getCurrentHub();
$client = $hub->getClient();
if ($client !== null) {
$options = $client->getOptions();
if ($options !== null) {
$hub->configureScope(function (Scope $scope) use ($options) {
$this->dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope);
});
}
}
}
return (string) $this->dynamicSamplingContext;
}
/**
* @return array
*/
public function getTraceContext(): array
{
$result = [
'trace_id' => (string) $this->traceId,
'span_id' => (string) $this->spanId,
];
if ($this->parentSpanId !== null) {
$result['parent_span_id'] = (string) $this->parentSpanId;
}
return $result;
}
public function getTraceId(): TraceId
{
return $this->traceId;
}
public function setTraceId(TraceId $traceId): void
{
$this->traceId = $traceId;
}
public function getParentSpanId(): ?SpanId
{
return $this->parentSpanId;
}
public function setParentSpanId(?SpanId $parentSpanId): void
{
$this->parentSpanId = $parentSpanId;
}
public function getSpanId(): SpanId
{
return $this->spanId;
}
public function setSpanId(SpanId $spanId): self
{
$this->spanId = $spanId;
return $this;
}
public function getDynamicSamplingContext(): ?DynamicSamplingContext
{
return $this->dynamicSamplingContext;
}
public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): self
{
$this->dynamicSamplingContext = $dynamicSamplingContext;
return $this;
}
public function getSampleRand(): ?float
{
return $this->sampleRand;
}
public function setSampleRand(?float $sampleRand): self
{
$this->sampleRand = $sampleRand;
return $this;
}
private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self
{
$context = self::fromDefaults();
$parsedData = self::parseTraceAndBaggageHeaders($traceparent, $baggage);
if ($parsedData['traceId'] !== null) {
$context->traceId = $parsedData['traceId'];
}
if ($parsedData['parentSpanId'] !== null) {
$context->parentSpanId = $parsedData['parentSpanId'];
}
if ($parsedData['parentSampled'] !== null) {
$context->parentSampled = $parsedData['parentSampled'];
}
if ($parsedData['dynamicSamplingContext'] !== null) {
$context->dynamicSamplingContext = $parsedData['dynamicSamplingContext'];
}
if ($parsedData['sampleRand'] !== null) {
$context->sampleRand = $parsedData['sampleRand'];
}
return $context;
}
}
sentry/src/Spotlight/SpotlightClient.php 0000644 00000003513 15266563341 0014476 0 ustar 00 getStringBody();
if ($requestData === null) {
throw new \RuntimeException('The request data is empty.');
}
$curlHandle = curl_init();
curl_setopt($curlHandle, \CURLOPT_URL, $url);
curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, [
'Content-Type: application/x-sentry-envelope',
]);
curl_setopt($curlHandle, \CURLOPT_TIMEOUT, 2.0);
curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, 1.0);
curl_setopt($curlHandle, \CURLOPT_ENCODING, '');
curl_setopt($curlHandle, \CURLOPT_POST, true);
curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData);
curl_setopt($curlHandle, \CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1);
$body = curl_exec($curlHandle);
if ($body === false) {
$errorCode = curl_errno($curlHandle);
$error = curl_error($curlHandle);
if (\PHP_MAJOR_VERSION < 8) {
curl_close($curlHandle);
}
$message = 'cURL Error (' . $errorCode . ') ' . $error;
return new Response(0, [], $message);
}
$statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE);
if (\PHP_MAJOR_VERSION < 8) {
curl_close($curlHandle);
}
return new Response($statusCode, [], '');
}
}
sentry/src/ExceptionMechanism.php 0000644 00000005131 15266563341 0013166 0 ustar 00 Arbitrary extra data that might help the user
* understand the error thrown by this mechanism
*/
private $data;
/**
* Class constructor.
*
* @param string $type Unique identifier of this mechanism determining
* rendering and processing of the mechanism data
* @param bool $handled Flag indicating whether the exception has been
* handled by the user (e.g. via try..catch)
* @param array $data Arbitrary extra data that might help the user
* understand the error thrown by this mechanism
*/
public function __construct(string $type, bool $handled, array $data = [])
{
$this->type = $type;
$this->handled = $handled;
$this->data = $data;
}
/**
* Returns the unique identifier of this mechanism determining rendering and
* processing of the mechanism data.
*/
public function getType(): string
{
return $this->type;
}
/**
* Returns the flag indicating whether the exception has been handled by the
* user (e.g. via try..catch).
*/
public function isHandled(): bool
{
return $this->handled;
}
/**
* Returns arbitrary extra data that might help the user understand the error
* thrown by this mechanism.
*
* @return array
*/
public function getData(): array
{
return $this->data;
}
/**
* Sets the arbitrary extra data.
*
* @param array $data
*/
public function setData(array $data): self
{
$this->data = $data;
return $this;
}
}
sentry/src/Profiling/Profiler.php 0000644 00000003531 15266563341 0013120 0 ustar 00 logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
$this->profile = new Profile($options);
$this->initProfiler();
}
public function start(): void
{
if ($this->profiler !== null) {
$this->profiler->start();
}
}
public function stop(): void
{
if ($this->profiler !== null) {
$this->profiler->stop();
$this->profile->setExcimerLog($this->profiler->flush());
}
}
public function getProfile(): ?Profile
{
if ($this->profiler === null) {
return null;
}
return $this->profile;
}
private function initProfiler(): void
{
if (!\extension_loaded('excimer')) {
$this->logger->warning('The profiler was started but is not available because the "excimer" extension is not loaded.');
return;
}
$this->profiler = new \ExcimerProfiler();
$this->profile->setStartTimeStamp(microtime(true));
$this->profiler->setEventType(\EXCIMER_REAL);
$this->profiler->setPeriod(self::SAMPLE_RATE);
$this->profiler->setMaxDepth(self::MAX_STACK_DEPTH);
}
}
sentry/src/Profiling/Profile.php 0000644 00000026072 15266563341 0012743 0 ustar 00 ,
* samples: array,
* stacks: array>,
* },
* }
* @phpstan-type ExcimerLogStackEntryTrace array{
* file: string,
* line: int,
* class?: string,
* function?: string,
* closure_line?: int,
* }
* @phpstan-type ExcimerLogStackEntry array{
* trace: array,
* timestamp: float
* }
*
* @internal
*/
final class Profile
{
use PrefixStripper;
/**
* @var string The version of the profile format
*/
private const VERSION = '1';
/**
* @var string The thread ID
*/
private const THREAD_ID = '0';
/**
* @var int The minimum number of samples required for a profile
*/
private const MIN_SAMPLE_COUNT = 2;
/**
* @var int The maximum duration of a profile in seconds
*/
private const MAX_PROFILE_DURATION = 30;
/**
* @var float The start time of the profile as a Unix timestamp with microseconds
*/
private $startTimeStamp;
/**
* @var \ExcimerLog|array The data of the profile
*/
private $excimerLog;
/**
* @var EventId|null The event ID of the profile
*/
private $eventId;
/**
* @var Options|null
*/
private $options;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(?Options $options = null)
{
$this->options = $options;
$this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
}
public function setStartTimeStamp(float $startTimeStamp): void
{
$this->startTimeStamp = $startTimeStamp;
}
/**
* @param \ExcimerLog|array $excimerLog
*/
public function setExcimerLog($excimerLog): void
{
$this->excimerLog = $excimerLog;
}
public function setEventId(EventId $eventId): void
{
$this->eventId = $eventId;
}
/**
* @return SentryProfile|null
*/
public function getFormattedData(Event $event): ?array
{
if (!$this->validateExcimerLog()) {
$this->logger->warning('The profile does not contain enough samples, the profile will be discarded.');
return null;
}
$osContext = $event->getOsContext();
if (!$this->validateOsContext($osContext)) {
$this->logger->warning('The OS context is not missing or invalid, the profile will be discarded.');
return null;
}
$runtimeContext = $event->getRuntimeContext();
if (!$this->validateRuntimeContext($runtimeContext)) {
$this->logger->warning('The runtime context is not missing or invalid, the profile will be discarded.');
return null;
}
if (!$this->validateEvent($event)) {
$this->logger->warning('The event is missing a transaction and/or trace ID, the profile will be discarded.');
return null;
}
$frames = [];
$frameHashMap = [];
$stacks = [];
$stackHashMap = [];
$registerStack = static function (array $stack) use (&$stacks, &$stackHashMap): int {
$stackHash = md5(serialize($stack));
if (\array_key_exists($stackHash, $stackHashMap) === false) {
$stackHashMap[$stackHash] = \count($stacks);
$stacks[] = $stack;
}
return $stackHashMap[$stackHash];
};
$samples = [];
$duration = 0;
$loggedStacks = $this->prepareStacks();
foreach ($loggedStacks as $stack) {
$stackFrames = [];
foreach ($stack['trace'] as $frame) {
$absolutePath = $frame['file'];
$lineno = $frame['line'];
$frameKey = "{$absolutePath}:{$lineno}";
$frameIndex = $frameHashMap[$frameKey] ?? null;
if ($frameIndex === null) {
$file = $this->stripPrefixFromFilePath($this->options, $absolutePath);
$module = null;
if (isset($frame['class'], $frame['function'])) {
// Class::method
$function = $frame['class'] . '::' . $frame['function'];
$module = $frame['class'];
} elseif (isset($frame['function'])) {
// {closure}
$function = $frame['function'];
} else {
// /index.php
$function = $file;
}
$frameHashMap[$frameKey] = $frameIndex = \count($frames);
$frames[] = [
'filename' => $file,
'abs_path' => $absolutePath,
'module' => $module,
'function' => $function,
'lineno' => $lineno,
];
}
$stackFrames[] = $frameIndex;
}
$stackId = $registerStack($stackFrames);
$duration = $stack['timestamp'];
$samples[] = [
'stack_id' => $stackId,
'thread_id' => self::THREAD_ID,
'elapsed_since_start_ns' => (int) round($duration * 1e+9),
];
}
if (!$this->validateMaxDuration((float) $duration)) {
$this->logger->warning(\sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION));
return null;
}
$startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC'));
if ($startTime === false) {
$this->logger->warning(\sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp));
return null;
}
return [
'device' => [
'architecture' => $osContext->getMachineType(),
],
'event_id' => $this->eventId ? (string) $this->eventId : SentryUid::generate(),
'os' => [
'name' => $osContext->getName(),
'version' => $osContext->getVersion(),
'build_number' => $osContext->getBuild() ?? '',
],
'platform' => 'php',
'release' => $event->getRelease() ?? '',
'environment' => $event->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT,
'runtime' => [
'name' => $runtimeContext->getName(),
'sapi' => $runtimeContext->getSAPI(),
'version' => $runtimeContext->getVersion(),
],
'timestamp' => $startTime->format(\DATE_RFC3339_EXTENDED),
'transaction' => [
'id' => (string) $event->getId(),
'name' => $event->getTransaction(),
'trace_id' => $event->getTraceId(),
'active_thread_id' => self::THREAD_ID,
],
'version' => self::VERSION,
'profile' => [
'frames' => $frames,
'samples' => $samples,
'stacks' => $stacks,
],
];
}
/**
* This method is mainly used to be able to mock the ExcimerLog class in the tests.
*
* @return array
*/
private function prepareStacks(): array
{
$stacks = [];
foreach ($this->excimerLog as $stack) {
if ($stack instanceof \ExcimerLogEntry) {
$stacks[] = [
'trace' => $stack->getTrace(),
'timestamp' => $stack->getTimestamp(),
];
} else {
/** @var ExcimerLogStackEntry $stack */
$stacks[] = $stack;
}
}
return $stacks;
}
private function validateExcimerLog(): bool
{
if (\is_array($this->excimerLog)) {
$sampleCount = \count($this->excimerLog);
} else {
$sampleCount = $this->excimerLog->count();
}
return $sampleCount >= self::MIN_SAMPLE_COUNT;
}
private function validateMaxDuration(float $duration): bool
{
if ($duration > self::MAX_PROFILE_DURATION) {
return false;
}
return true;
}
/**
* @phpstan-assert-if-true OsContext $osContext
* @phpstan-assert-if-true !null $osContext->getVersion()
* @phpstan-assert-if-true !null $osContext->getMachineType()
*/
private function validateOsContext(?OsContext $osContext): bool
{
if ($osContext === null) {
return false;
}
if ($osContext->getVersion() === null) {
return false;
}
if ($osContext->getMachineType() === null) {
return false;
}
return true;
}
/**
* @phpstan-assert-if-true RuntimeContext $runtimeContext
* @phpstan-assert-if-true !null $runtimeContext->getVersion()
*/
private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool
{
if ($runtimeContext === null) {
return false;
}
if ($runtimeContext->getVersion() === null) {
return false;
}
return true;
}
/**
* @phpstan-assert-if-true !null $event->getTransaction()
* @phpstan-assert-if-true !null $event->getTraceId()
*/
private function validateEvent(Event $event): bool
{
if ($event->getTransaction() === null) {
return false;
}
if ($event->getTraceId() === null) {
return false;
}
return true;
}
}
sentry/src/HttpClient/Response.php 0000644 00000003322 15266563341 0013257 0 ustar 00 statusCode = $statusCode;
$this->headers = $headers;
$this->error = $error;
foreach ($headers as $name => $value) {
$this->headerNames[strtolower($name)] = $name;
}
}
public function getStatusCode(): int
{
return $this->statusCode;
}
public function isSuccess(): bool
{
return $this->statusCode >= 200 && $this->statusCode <= 299;
}
public function hasHeader(string $name): bool
{
return isset($this->headerNames[strtolower($name)]);
}
/**
* @return string[]
*/
public function getHeader(string $header): array
{
if (!$this->hasHeader($header)) {
return [];
}
$header = $this->headerNames[strtolower($header)];
return $this->headers[$header];
}
public function getHeaderLine(string $name): string
{
$value = $this->getHeader($name);
if (empty($value)) {
return '';
}
return implode(',', $value);
}
public function getError(): string
{
return $this->error;
}
public function hasError(): bool
{
return $this->error !== '';
}
}
sentry/src/HttpClient/HttpClientInterface.php 0000644 00000000305 15266563341 0015356 0 ustar 00 stringBody !== null;
}
public function getStringBody(): ?string
{
return $this->stringBody;
}
public function setStringBody(string $stringBody): void
{
$this->stringBody = $stringBody;
}
}
sentry/src/HttpClient/HttpClient.php 0000644 00000010421 15266563341 0013535 0 ustar 00 sdkIdentifier = $sdkIdentifier;
$this->sdkVersion = $sdkVersion;
}
public function sendRequest(Request $request, Options $options): Response
{
$dsn = $options->getDsn();
if ($dsn === null) {
throw new \RuntimeException('The DSN option must be set to use the HttpClient.');
}
$requestData = $request->getStringBody();
if ($requestData === null) {
throw new \RuntimeException('The request data is empty.');
}
if (!\extension_loaded('curl')) {
throw new \RuntimeException('The cURL PHP extension must be enabled to use the HttpClient.');
}
$curlHandle = curl_init();
$requestHeaders = Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion);
if (
\extension_loaded('zlib')
&& $options->isHttpCompressionEnabled()
) {
$requestData = gzcompress($requestData, -1, \ZLIB_ENCODING_GZIP);
$requestHeaders[] = 'Content-Encoding: gzip';
}
$responseHeaders = [];
$responseHeaderCallback = function ($curlHandle, $headerLine) use (&$responseHeaders): int {
return Http::parseResponseHeaders($headerLine, $responseHeaders);
};
curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl());
curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, $requestHeaders);
curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion);
curl_setopt($curlHandle, \CURLOPT_TIMEOUT_MS, $options->getHttpTimeout() * 1000);
curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT_MS, $options->getHttpConnectTimeout() * 1000);
curl_setopt($curlHandle, \CURLOPT_ENCODING, '');
curl_setopt($curlHandle, \CURLOPT_POST, true);
curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData);
curl_setopt($curlHandle, \CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, \CURLOPT_HEADERFUNCTION, $responseHeaderCallback);
curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1);
$httpSslVerifyPeer = $options->getHttpSslVerifyPeer();
if (!$httpSslVerifyPeer) {
curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, false);
}
$httpSslNativeCa = $options->getHttpSslNativeCa();
if ($httpSslNativeCa) {
if (
\defined('CURLSSLOPT_NATIVE_CA')
&& isset(curl_version()['version'])
&& version_compare(curl_version()['version'], '7.71', '>=')
) {
curl_setopt($curlHandle, \CURLOPT_SSL_OPTIONS, \CURLSSLOPT_NATIVE_CA);
}
}
$httpProxy = $options->getHttpProxy();
if ($httpProxy !== null) {
curl_setopt($curlHandle, \CURLOPT_PROXY, $httpProxy);
curl_setopt($curlHandle, \CURLOPT_HEADEROPT, \CURLHEADER_SEPARATE);
}
$httpProxyAuthentication = $options->getHttpProxyAuthentication();
if ($httpProxyAuthentication !== null) {
curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication);
}
/** @var string|false $body */
$body = curl_exec($curlHandle);
if ($body === false) {
$errorCode = curl_errno($curlHandle);
$error = curl_error($curlHandle);
if (\PHP_MAJOR_VERSION < 8) {
curl_close($curlHandle);
}
$message = 'cURL Error (' . $errorCode . ') ' . $error;
return new Response(0, [], $message);
}
$statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE);
if (\PHP_MAJOR_VERSION < 8) {
curl_close($curlHandle);
}
$error = $statusCode >= 400 ? $body : '';
return new Response($statusCode, $responseHeaders, $error);
}
}
sentry/src/ExceptionDataBag.php 0000644 00000005322 15266563341 0012547 0 ustar 00
*/
final class ExceptionDataBag
{
/**
* @var string The type of exception, e.g. RuntimeException
*/
private $type;
/**
* @var string The value of the exception
*/
private $value;
/**
* @var Stacktrace|null An optional stack trace object corresponding to the Stack Trace Interface
*/
private $stacktrace;
/**
* @var ExceptionMechanism|null An optional object describing the mechanism that created this exception
*/
private $mechanism;
public function __construct(\Throwable $exception, ?Stacktrace $stacktrace = null, ?ExceptionMechanism $mechanism = null)
{
$this->type = \get_class($exception);
$this->value = $exception->getMessage();
$this->stacktrace = $stacktrace;
$this->mechanism = $mechanism;
}
/**
* Gets the type of exception, e.g. RuntimeException.
*/
public function getType(): string
{
return $this->type;
}
/**
* Sets the type of the exception.
*
* @param string $type The exception type
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Gets the value of the exception.
*/
public function getValue(): string
{
return $this->value;
}
/**
* Sets the value of the exception.
*/
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
/**
* Gets the stack trace object corresponding to the Stack Trace Interface.
*/
public function getStacktrace(): ?Stacktrace
{
return $this->stacktrace;
}
/**
* Sets the stack trace object corresponding to the Stack Trace Interface.
*
* @param Stacktrace $stacktrace The stacktrace
*/
public function setStacktrace(Stacktrace $stacktrace): self
{
$this->stacktrace = $stacktrace;
return $this;
}
/**
* Gets the object describing the mechanism that created this exception.
*/
public function getMechanism(): ?ExceptionMechanism
{
return $this->mechanism;
}
/**
* Sets the object describing the mechanism that created this exception.
*
* @param ExceptionMechanism|null $mechanism The mechanism that created this exception
*/
public function setMechanism(?ExceptionMechanism $mechanism): self
{
$this->mechanism = $mechanism;
return $this;
}
}
sentry/src/ClientBuilder.php 0000644 00000007207 15266563341 0012136 0 ustar 00 options = $options ?? new Options();
}
/**
* @param array $options The client options, in naked array form
*/
public static function create(array $options = []): self
{
return new self(new Options($options));
}
public function getOptions(): Options
{
return $this->options;
}
public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self
{
$this->representationSerializer = $representationSerializer;
return $this;
}
public function getLogger(): ?LoggerInterface
{
return $this->logger ?? $this->options->getLogger();
}
public function setLogger(LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
public function setSdkIdentifier(string $sdkIdentifier): self
{
$this->sdkIdentifier = $sdkIdentifier;
return $this;
}
public function setSdkVersion(string $sdkVersion): self
{
$this->sdkVersion = $sdkVersion;
return $this;
}
public function getTransport(): TransportInterface
{
return $this->transport
?? $this->options->getTransport()
?? new HttpTransport(
$this->options,
$this->getHttpClient(),
new PayloadSerializer($this->options),
$this->getLogger()
);
}
public function setTransport(TransportInterface $transport): self
{
$this->transport = $transport;
return $this;
}
public function getHttpClient(): HttpClientInterface
{
return $this->httpClient
?? $this->options->getHttpClient()
?? new HttpClient($this->sdkIdentifier, $this->sdkVersion);
}
public function setHttpClient(HttpClientInterface $httpClient): self
{
$this->httpClient = $httpClient;
return $this;
}
public function getClient(): ClientInterface
{
return new Client(
$this->options,
$this->getTransport(),
$this->sdkIdentifier,
$this->sdkVersion,
$this->representationSerializer,
$this->getLogger()
);
}
}
sentry/src/ErrorHandler.php 0000644 00000044621 15266563341 0012001 0 ustar 00 \d+) bytes exhausted[^\r\n]* \(tried to allocate \d+ bytes\)/';
/**
* The fatal error types that cannot be silenced using the @ operator in PHP 8+.
*/
private const PHP8_UNSILENCEABLE_FATAL_ERRORS = \E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR;
/**
* @var self|null The current registered handler (this class is a singleton)
*/
private static $handlerInstance;
/**
* @var callable[] List of listeners that will act on each captured error
*
* @psalm-var (callable(\ErrorException): void)[]
*/
private $errorListeners = [];
/**
* @var callable[] List of listeners that will act of each captured fatal error
*
* @psalm-var (callable(FatalErrorException): void)[]
*/
private $fatalErrorListeners = [];
/**
* @var callable[] List of listeners that will act on each captured exception
*
* @psalm-var (callable(\Throwable): void)[]
*/
private $exceptionListeners = [];
/**
* @var \ReflectionProperty A reflection cached instance that points to the
* trace property of the exception objects
*/
private $exceptionReflection;
/**
* @var callable|null The previous error handler, if any
*/
private $previousErrorHandler;
/**
* @var callable|null The previous exception handler, if any
*
* @psalm-var null|callable(\Throwable): void
*/
private $previousExceptionHandler;
/**
* @var bool Whether the error handler has been registered
*/
private $isErrorHandlerRegistered = false;
/**
* @var bool Whether the exception handler has been registered
*/
private $isExceptionHandlerRegistered = false;
/**
* @var bool Whether the fatal error handler has been registered
*/
private $isFatalErrorHandlerRegistered = false;
/**
* @var int|null the amount of bytes of memory to increase the memory limit by when we are capturing a out of memory error, set to null to not increase the memory limit
*/
private $memoryLimitIncreaseOnOutOfMemoryErrorValue = 5 * 1024 * 1024; // 5 MiB
/**
* @var Options|null The SDK options
*/
private $options;
/**
* @var bool Whether the memory limit has been increased
*/
private static $didIncreaseMemoryLimit = false;
/**
* @var string|null A portion of pre-allocated memory data that will be reclaimed in case a fatal error occurs to handle it
*
* @phpstan-ignore-next-line This property is used to reserve memory for the fatal error handler and is thus never read
*/
private static $reservedMemory;
/**
* @var bool Whether the fatal error handler should be disabled
*/
private static $disableFatalErrorHandler = false;
/**
* @var string[] List of error levels and their description
*/
private const ERROR_LEVELS_DESCRIPTION = [
\E_DEPRECATED => 'Deprecated',
\E_USER_DEPRECATED => 'User Deprecated',
\E_NOTICE => 'Notice',
\E_USER_NOTICE => 'User Notice',
// This is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices
2048 => 'Runtime Notice',
\E_WARNING => 'Warning',
\E_USER_WARNING => 'User Warning',
\E_COMPILE_WARNING => 'Compile Warning',
\E_CORE_WARNING => 'Core Warning',
\E_USER_ERROR => 'User Error',
\E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
\E_COMPILE_ERROR => 'Compile Error',
\E_PARSE => 'Parse Error',
\E_ERROR => 'Error',
\E_CORE_ERROR => 'Core Error',
];
/**
* Constructor.
*
* @throws \ReflectionException If hooking into the \Exception class to
* make the `trace` property accessible fails
*/
private function __construct()
{
$this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace');
if (\PHP_VERSION_ID < 80100) {
$this->exceptionReflection->setAccessible(true);
}
}
/**
* Registers the error handler once and returns its instance.
*/
public static function registerOnceErrorHandler(?Options $options = null): self
{
if (self::$handlerInstance === null) {
self::$handlerInstance = new self();
}
self::$handlerInstance->options = $options;
if (self::$handlerInstance->isErrorHandlerRegistered) {
return self::$handlerInstance;
}
$errorHandlerCallback = \Closure::fromCallable([self::$handlerInstance, 'handleError']);
self::$handlerInstance->isErrorHandlerRegistered = true;
self::$handlerInstance->previousErrorHandler = set_error_handler($errorHandlerCallback);
if (self::$handlerInstance->previousErrorHandler === null) {
restore_error_handler();
// Specifying the error types caught by the error handler with the
// first call to the set_error_handler method would cause the PHP
// bug https://bugs.php.net/63206 if the handler is not the first
// one in the chain of handlers
set_error_handler($errorHandlerCallback, \E_ALL);
}
return self::$handlerInstance;
}
/**
* Registers the fatal error handler and reserves a certain amount of memory
* that will be reclaimed to handle the errors (to prevent out of memory
* issues while handling them) and returns its instance.
*
* @param int $reservedMemorySize The amount of memory to reserve for the fatal
* error handler expressed in bytes
*/
public static function registerOnceFatalErrorHandler(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self
{
if ($reservedMemorySize <= 0) {
throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.');
}
if (self::$handlerInstance === null) {
self::$handlerInstance = new self();
}
if (self::$handlerInstance->isFatalErrorHandlerRegistered) {
return self::$handlerInstance;
}
self::$handlerInstance->isFatalErrorHandlerRegistered = true;
self::$reservedMemory = str_repeat('x', $reservedMemorySize);
register_shutdown_function(\Closure::fromCallable([self::$handlerInstance, 'handleFatalError']));
return self::$handlerInstance;
}
/**
* Registers the exception handler, effectively replacing the current one
* and returns its instance. The previous one will be saved anyway and
* called when appropriate.
*/
public static function registerOnceExceptionHandler(): self
{
if (self::$handlerInstance === null) {
self::$handlerInstance = new self();
}
if (self::$handlerInstance->isExceptionHandlerRegistered) {
return self::$handlerInstance;
}
self::$handlerInstance->isExceptionHandlerRegistered = true;
self::$handlerInstance->previousExceptionHandler = set_exception_handler(\Closure::fromCallable([self::$handlerInstance, 'handleException']));
return self::$handlerInstance;
}
/**
* Adds a listener to the current error handler that will be called every
* time an error is captured.
*
* @param callable $listener A callable that will act as a listener
* and that must accept a single argument
* of type \ErrorException
*
* @psalm-param callable(\ErrorException): void $listener
*/
public function addErrorHandlerListener(callable $listener): void
{
$this->errorListeners[] = $listener;
}
/**
* Adds a listener to the current error handler that will be called every
* time a fatal error handler is captured.
*
* @param callable $listener A callable that will act as a listener
* and that must accept a single argument
* of type \Sentry\Exception\FatalErrorException
*
* @psalm-param callable(FatalErrorException): void $listener
*/
public function addFatalErrorHandlerListener(callable $listener): void
{
$this->fatalErrorListeners[] = $listener;
}
/**
* Adds a listener to the current error handler that will be called every
* time an exception is captured.
*
* @param callable $listener A callable that will act as a listener
* and that must accept a single argument
* of type \Throwable
*
* @psalm-param callable(\Throwable): void $listener
*/
public function addExceptionHandlerListener(callable $listener): void
{
$this->exceptionListeners[] = $listener;
}
/**
* Sets the amount of memory to increase the memory limit by when we are capturing a out of memory error.
*
* @param int|null $valueInBytes the number of bytes to increase the memory limit by, or null to not increase the memory limit
*/
public function setMemoryLimitIncreaseOnOutOfMemoryErrorInBytes(?int $valueInBytes): void
{
if ($valueInBytes !== null && $valueInBytes <= 0) {
throw new \InvalidArgumentException('The $valueInBytes argument must be greater than 0 or null.');
}
$this->memoryLimitIncreaseOnOutOfMemoryErrorValue = $valueInBytes;
}
/**
* Handles errors by capturing them through the client according to the
* configured bit field.
*
* @param int $level The level of the error raised, represented by
* one of the E_* constants
* @param string $message The error message
* @param string $file The filename the error was raised in
* @param int $line The line number the error was raised at
* @param array|null $errcontext The error context (deprecated since PHP 7.2)
*
* @return bool If the function returns `false` then the PHP native error
* handler will be called
*
* @throws \Throwable
*/
private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool
{
$isSilencedError = error_reporting() === 0;
if (\PHP_MAJOR_VERSION >= 8) {
// Starting from PHP8, when a silenced error occurs the `error_reporting()`
// function will return a bitmask of fatal errors that are unsilenceable.
// If by subtracting from this value those errors the result is 0, we can
// conclude that the error was silenced.
$isSilencedError = 0 === (error_reporting() & ~self::PHP8_UNSILENCEABLE_FATAL_ERRORS);
// However, starting from PHP8 some fatal errors are unsilenceable,
// so we have to check for them to avoid reporting any of them as
// silenced instead
if ($level === (self::PHP8_UNSILENCEABLE_FATAL_ERRORS & $level)) {
$isSilencedError = false;
}
}
if ($this->shouldHandleError($level, $isSilencedError)) {
if ($isSilencedError) {
$errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line);
} else {
$errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line);
}
$backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $errorAsException->getFile(), $errorAsException->getLine());
$this->exceptionReflection->setValue($errorAsException, $backtrace);
$this->invokeListeners($this->errorListeners, $errorAsException);
}
if ($this->previousErrorHandler !== null) {
return false !== ($this->previousErrorHandler)($level, $message, $file, $line, $errcontext);
}
return false;
}
private function shouldHandleError(int $level, bool $silenced): bool
{
// If we were not given any options, we should handle all errors
if ($this->options === null) {
return true;
}
if ($silenced) {
return $this->options->shouldCaptureSilencedErrors();
}
return ($this->options->getErrorTypes() & $level) !== 0;
}
/**
* Tries to handle a fatal error if any and relay them to the listeners.
* It only tries to do this if we still have some reserved memory at
* disposal. This method is used as callback of a shutdown function.
*/
private function handleFatalError(): void
{
if (self::$disableFatalErrorHandler) {
return;
}
// Free the reserved memory that allows us to potentially handle OOM errors
self::$reservedMemory = null;
$error = error_get_last();
if (!empty($error) && $error['type'] & (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_CORE_WARNING | \E_COMPILE_ERROR | \E_COMPILE_WARNING)) {
// If we did not do so already and we are allowed to increase the memory limit, we do so when we detect an OOM error
if (self::$didIncreaseMemoryLimit === false
&& $this->memoryLimitIncreaseOnOutOfMemoryErrorValue !== null
&& preg_match(self::OOM_MESSAGE_MATCHER, $error['message'], $matches) === 1
) {
$currentMemoryLimit = (int) $matches['memory_limit'];
ini_set('memory_limit', (string) ($currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue));
self::$didIncreaseMemoryLimit = true;
}
$errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']);
$this->exceptionReflection->setValue($errorAsException, []);
$this->invokeListeners($this->fatalErrorListeners, $errorAsException);
}
}
/**
* Handles the given exception by passing it to all the listeners,
* then forwarding it to another handler.
*
* @param \Throwable $exception The exception to handle
*
* @throws \Throwable
*/
private function handleException(\Throwable $exception): void
{
$this->invokeListeners($this->exceptionListeners, $exception);
$previousExceptionHandlerException = $exception;
// Unset the previous exception handler to prevent infinite loop in case
// we need to handle an exception thrown from it
$previousExceptionHandler = $this->previousExceptionHandler;
$this->previousExceptionHandler = null;
try {
if ($previousExceptionHandler !== null) {
$previousExceptionHandler($exception);
return;
}
} catch (\Throwable $previousExceptionHandlerException) {
// This `catch` statement is here to forcefully override the
// $previousExceptionHandlerException variable with the exception
// we just caught
}
// If the instance of the exception we're handling is the same as the one
// caught from the previous exception handler then we give it back to the
// native PHP handler to prevent an infinite loop
if ($exception === $previousExceptionHandlerException) {
// Disable the fatal error handler or the error will be reported twice
self::$disableFatalErrorHandler = true;
throw $exception;
}
$this->handleException($previousExceptionHandlerException);
}
/**
* Cleans and returns the backtrace without the first frames that belong to
* this error handler.
*
* @param array> $backtrace The backtrace to clear
* @param string $file The filename the backtrace was raised in
* @param int $line The line number the backtrace was raised at
*
* @psalm-param list $backtrace
*
* @return array
*/
private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array
{
$cleanedBacktrace = $backtrace;
$index = 0;
while ($index < \count($backtrace)) {
if (isset($backtrace[$index]['file'], $backtrace[$index]['line']) && $backtrace[$index]['line'] === $line && $backtrace[$index]['file'] === $file) {
$cleanedBacktrace = \array_slice($cleanedBacktrace, 1 + $index);
break;
}
++$index;
}
return $cleanedBacktrace;
}
/**
* Invokes all the listeners and pass the exception to all of them.
*
* @param callable[] $listeners The array of listeners to be called
* @param \Throwable $throwable The exception to be passed onto listeners
*/
private function invokeListeners(array $listeners, \Throwable $throwable): void
{
foreach ($listeners as $listener) {
try {
$listener($throwable);
} catch (\Throwable $exception) {
// Do nothing as this should be as transparent as possible
}
}
}
}
sentry/src/CheckInStatus.php 0000644 00000002071 15266563341 0012113 0 ustar 00 A list of cached enum instances
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
public static function ok(): self
{
return self::getInstance('ok');
}
public static function error(): self
{
return self::getInstance('error');
}
public static function inProgress(): self
{
return self::getInstance('in_progress');
}
public function __toString(): string
{
return $this->value;
}
private static function getInstance(string $value): self
{
if (!isset(self::$instances[$value])) {
self::$instances[$value] = new self($value);
}
return self::$instances[$value];
}
}
sentry/src/Integration/FrameContextifierIntegration.php 0000644 00000011634 15266563341 0017515 0 ustar 00
*/
final class FrameContextifierIntegration implements IntegrationInterface
{
/**
* @var LoggerInterface A PSR-3 logger
*/
private $logger;
/**
* Creates a new instance of this integration.
*
* @param LoggerInterface|null $logger A PSR-3 logger
*/
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?? new NullLogger();
}
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$client = SentrySdk::getCurrentHub()->getClient();
if ($client === null) {
return $event;
}
$maxContextLines = $client->getOptions()->getContextLines();
$integration = $client->getIntegration(self::class);
if ($integration === null || $maxContextLines === null) {
return $event;
}
$stacktrace = $event->getStacktrace();
if ($stacktrace !== null) {
$integration->addContextToStacktraceFrames($maxContextLines, $stacktrace);
}
foreach ($event->getExceptions() as $exception) {
if ($exception->getStacktrace() !== null) {
$integration->addContextToStacktraceFrames($maxContextLines, $exception->getStacktrace());
}
}
return $event;
});
}
/**
* Contextifies the frames of the given stacktrace.
*
* @param int $maxContextLines The maximum number of lines of code to read
* @param Stacktrace $stacktrace The stacktrace object
*/
private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void
{
foreach ($stacktrace->getFrames() as $frame) {
if ($frame->isInternal()) {
continue;
}
$this->addContextToStacktraceFrame($maxContextLines, $frame);
}
}
/**
* Contextifies the given frame.
*
* @param int $maxContextLines The maximum number of lines of code to read
*/
private function addContextToStacktraceFrame(int $maxContextLines, Frame $frame): void
{
if ($frame->getAbsoluteFilePath() === null) {
return;
}
$sourceCodeExcerpt = $this->getSourceCodeExcerpt($maxContextLines, $frame->getAbsoluteFilePath(), $frame->getLine());
$frame->setPreContext($sourceCodeExcerpt['pre_context']);
$frame->setContextLine($sourceCodeExcerpt['context_line']);
$frame->setPostContext($sourceCodeExcerpt['post_context']);
}
/**
* Gets an excerpt of the source code around a given line.
*
* @param int $maxContextLines The maximum number of lines of code to read
* @param string $filePath The file path
* @param int $lineNumber The line to centre about
*
* @return array
*
* @psalm-return array{
* pre_context: string[],
* context_line: string|null,
* post_context: string[]
* }
*/
private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, int $lineNumber): array
{
$frame = [
'pre_context' => [],
'context_line' => null,
'post_context' => [],
];
$target = max(0, $lineNumber - ($maxContextLines + 1));
$currentLineNumber = $target + 1;
try {
$file = new \SplFileObject($filePath);
$file->seek($target);
while (!$file->eof()) {
/** @var string $line */
$line = $file->current();
$line = rtrim($line, "\r\n");
if ($currentLineNumber === $lineNumber) {
$frame['context_line'] = $line;
} elseif ($currentLineNumber < $lineNumber) {
$frame['pre_context'][] = $line;
} elseif ($currentLineNumber > $lineNumber) {
$frame['post_context'][] = $line;
}
++$currentLineNumber;
if ($currentLineNumber > $lineNumber + $maxContextLines) {
break;
}
$file->next();
}
} catch (\Throwable $exception) {
$this->logger->warning(
\sprintf('Failed to get the source code excerpt for the file "%s".', $filePath),
['exception' => $exception]
);
}
return $frame;
}
}
sentry/src/Integration/ExceptionListenerIntegration.php 0000644 00000001725 15266563341 0017543 0 ustar 00 addExceptionHandlerListener(static function (\Throwable $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null) {
return;
}
$integration->captureException($currentHub, $exception);
});
}
}
sentry/src/Integration/ModulesIntegration.php 0000644 00000004072 15266563341 0015505 0 ustar 00 The list of installed vendors
*/
private static $packages = [];
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$integration = SentrySdk::getCurrentHub()->getIntegration(self::class);
// The integration could be bound to a client that is not the one
// attached to the current hub. If this is the case, bail out
if ($integration !== null) {
$event->setModules(self::getComposerPackages());
}
return $event;
});
}
/**
* @return array
*/
private static function getComposerPackages(): array
{
if (empty(self::$packages)) {
foreach (self::getInstalledPackages() as $package) {
try {
self::$packages[$package] = PrettyVersions::getVersion($package)->getPrettyVersion();
} catch (\Throwable $exception) {
continue;
}
}
}
return self::$packages;
}
/**
* @return string[]
*/
private static function getInstalledPackages(): array
{
if (class_exists(InstalledVersions::class)) {
return InstalledVersions::getInstalledPackages();
}
if (class_exists(Versions::class)) {
// BC layer for Composer 1, using a transient dependency
return array_keys(Versions::VERSIONS);
}
// this should not happen
return ['sentry/sentry'];
}
}
sentry/src/Integration/RequestFetcher.php 0000644 00000001120 15266563341 0014611 0 ustar 00 options = $options;
}
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
ErrorHandler::registerOnceErrorHandler($this->options)
->addErrorHandlerListener(
static function (\ErrorException $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = $currentHub->getClient();
// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null || $client === null) {
return;
}
if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) {
return;
}
if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) {
return;
}
$integration->captureException($currentHub, $exception);
}
);
}
/**
* @internal this is a convenience method to create an instance of this integration for tests
*/
public static function make(Options $options): self
{
$integration = new self();
$integration->setOptions($options);
return $integration;
}
}
sentry/src/Integration/RequestFetcherInterface.php 0000644 00000000734 15266563341 0016444 0 ustar 00
*/
final class EnvironmentIntegration implements IntegrationInterface
{
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$integration = SentrySdk::getCurrentHub()->getIntegration(self::class);
if ($integration !== null) {
$event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext()));
$event->setOsContext($integration->updateServerOsContext($event->getOsContext()));
}
return $event;
});
}
private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeContext
{
if ($runtimeContext === null) {
$runtimeContext = new RuntimeContext('php');
}
if ($runtimeContext->getVersion() === null) {
$runtimeContext->setVersion(PHPVersion::parseVersion());
}
if ($runtimeContext->getSAPI() === null) {
$runtimeContext->setSAPI(\PHP_SAPI);
}
return $runtimeContext;
}
private function updateServerOsContext(?OsContext $osContext): ?OsContext
{
if (!\function_exists('php_uname')) {
return $osContext;
}
if ($osContext === null) {
$osContext = new OsContext(php_uname('s'));
}
if ($osContext->getVersion() === null) {
$osContext->setVersion(php_uname('r'));
}
if ($osContext->getBuild() === null) {
$osContext->setBuild(php_uname('v'));
}
if ($osContext->getKernelVersion() === null) {
$osContext->setKernelVersion(php_uname('a'));
}
if ($osContext->getMachineType() === null) {
$osContext->setMachineType(php_uname('m'));
}
return $osContext;
}
}
sentry/src/Integration/FatalErrorListenerIntegration.php 0000644 00000002371 15266563341 0017644 0 ustar 00
*/
final class FatalErrorListenerIntegration extends AbstractErrorListenerIntegration
{
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
$errorHandler = ErrorHandler::registerOnceFatalErrorHandler();
$errorHandler->addFatalErrorHandlerListener(static function (FatalErrorException $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = $currentHub->getClient();
// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null || $client === null) {
return;
}
if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) {
return;
}
$integration->captureException($currentHub, $exception);
});
}
}
sentry/src/Integration/AbstractErrorListenerIntegration.php 0000644 00000002607 15266563341 0020362 0 ustar 00 withScope(function (Scope $scope) use ($hub, $exception): void {
$scope->addEventProcessor(\Closure::fromCallable([$this, 'addExceptionMechanismToEvent']));
$hub->captureException($exception);
});
}
/**
* Adds the exception mechanism to the event.
*
* @param Event $event The event object
*/
protected function addExceptionMechanismToEvent(Event $event): Event
{
$exceptions = $event->getExceptions();
foreach ($exceptions as $exception) {
$data = [];
$mechanism = $exception->getMechanism();
if ($mechanism !== null) {
$data = $mechanism->getData();
}
$exception->setMechanism(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false, $data));
}
return $event;
}
}
sentry/src/Integration/TransactionIntegration.php 0000644 00000002563 15266563341 0016365 0 ustar 00
*/
final class TransactionIntegration implements IntegrationInterface
{
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event, EventHint $hint): Event {
$integration = SentrySdk::getCurrentHub()->getIntegration(self::class);
// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null) {
return $event;
}
if ($event->getTransaction() !== null) {
return $event;
}
if (isset($hint->extra['transaction']) && \is_string($hint->extra['transaction'])) {
$event->setTransaction($hint->extra['transaction']);
} elseif (isset($_SERVER['PATH_INFO'])) {
$event->setTransaction($_SERVER['PATH_INFO']);
}
return $event;
});
}
}
sentry/src/Integration/IntegrationRegistry.php 0000644 00000011122 15266563341 0015677 0 ustar 00 , bool> The registered integrations
*/
private $integrations = [];
private function __construct()
{
}
/**
* Gets the current singleton instance or creates a new one if it didn't
* exists yet.
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Setups the integrations according to the given options. For each integration
* the {@see IntegrationInterface::setupOnce()} method will be called only once
* during the application lifetime.
*
* @param Options $options The SDK client options
*
* @return array, IntegrationInterface>
*/
public function setupIntegrations(Options $options, LoggerInterface $logger): array
{
$integrations = [];
$installed = [];
foreach ($this->getIntegrationsToSetup($options) as $integration) {
$integrationName = \get_class($integration);
$integrations[$integrationName] = $integration;
if ($this->setupIntegration($integration, $options)) {
$installed[] = $integrationName;
}
}
if (\count($installed) > 0) {
$logger->debug(\sprintf('The "%s" integration(s) have been installed.', implode(', ', $installed)));
}
return $integrations;
}
private function setupIntegration(IntegrationInterface $integration, Options $options): bool
{
$integrationName = \get_class($integration);
if (isset($this->integrations[$integrationName])) {
return false;
}
if ($integration instanceof OptionAwareIntegrationInterface) {
$integration->setOptions($options);
}
$integration->setupOnce();
$this->integrations[$integrationName] = true;
return true;
}
/**
* @return IntegrationInterface[]
*/
private function getIntegrationsToSetup(Options $options): array
{
$integrations = [];
$defaultIntegrations = $this->getDefaultIntegrations($options);
$userIntegrations = $options->getIntegrations();
if (\is_array($userIntegrations)) {
$userIntegrationsClasses = array_map('get_class', $userIntegrations);
$pickedIntegrationsClasses = [];
foreach ($defaultIntegrations as $defaultIntegration) {
$integrationClassName = \get_class($defaultIntegration);
if (!\in_array($integrationClassName, $userIntegrationsClasses, true) && !isset($pickedIntegrationsClasses[$integrationClassName])) {
$integrations[] = $defaultIntegration;
$pickedIntegrationsClasses[$integrationClassName] = true;
}
}
foreach ($userIntegrations as $userIntegration) {
$integrationClassName = \get_class($userIntegration);
if (!isset($pickedIntegrationsClasses[$integrationClassName])) {
$integrations[] = $userIntegration;
$pickedIntegrationsClasses[$integrationClassName] = true;
}
}
} else {
$integrations = $userIntegrations($defaultIntegrations);
if (!\is_array($integrations)) {
throw new \UnexpectedValueException(\sprintf('Expected the callback set for the "integrations" option to return a list of integrations. Got: "%s".', get_debug_type($integrations)));
}
}
return $integrations;
}
/**
* @return IntegrationInterface[]
*/
private function getDefaultIntegrations(Options $options): array
{
if (!$options->hasDefaultIntegrations()) {
return [];
}
$integrations = [
new RequestIntegration(),
new TransactionIntegration(),
new FrameContextifierIntegration(),
new EnvironmentIntegration(),
new ModulesIntegration(),
];
if ($options->getDsn() !== null) {
array_unshift($integrations, new ExceptionListenerIntegration(), new ErrorListenerIntegration(), new FatalErrorListenerIntegration());
}
return $integrations;
}
}
sentry/src/Integration/RequestIntegration.php 0000644 00000023772 15266563341 0015535 0 ustar 00
*/
final class RequestIntegration implements IntegrationInterface
{
/**
* This constant represents the size limit in bytes beyond which the body
* of the request is not captured when the `max_request_body_size` option
* is set to `small`.
*/
private const REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH = 10 ** 3;
/**
* This constant represents the size limit in bytes beyond which the body
* of the request is not captured when the `max_request_body_size` option
* is set to `medium`.
*/
private const REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH = 10 ** 4;
/**
* This constant is a map of maximum allowed sizes for each value of the
* `max_request_body_size` option.
*/
private const MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP = [
'never' => 0,
'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH,
'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH,
'always' => \PHP_INT_MAX,
];
/**
* This constant defines the default list of headers that may contain
* sensitive data and that will be sanitized if sending PII is disabled.
*/
private const DEFAULT_SENSITIVE_HEADERS = [
'Authorization',
'Cookie',
'Set-Cookie',
'X-Forwarded-For',
'X-Real-IP',
];
/**
* @var RequestFetcherInterface PSR-7 request fetcher
*/
private $requestFetcher;
/**
* @var array The options
*
* @psalm-var array{
* pii_sanitize_headers: string[]
* }
*/
private $options;
/**
* Constructor.
*
* @param RequestFetcherInterface|null $requestFetcher PSR-7 request fetcher
* @param array $options The options
*
* @psalm-param array{
* pii_sanitize_headers?: string[]
* } $options
*/
public function __construct(?RequestFetcherInterface $requestFetcher = null, array $options = [])
{
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$this->requestFetcher = $requestFetcher ?? new RequestFetcher();
$this->options = $resolver->resolve($options);
}
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(function (Event $event): Event {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = $currentHub->getClient();
// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null || $client === null) {
return $event;
}
$this->processEvent($event, $client->getOptions());
return $event;
});
}
private function processEvent(Event $event, Options $options): void
{
$request = $this->requestFetcher->fetchRequest();
if ($request === null) {
return;
}
$requestData = [
'url' => (string) $request->getUri(),
'method' => $request->getMethod(),
];
if ($request->getUri()->getQuery()) {
$requestData['query_string'] = $request->getUri()->getQuery();
}
if ($options->shouldSendDefaultPii()) {
$serverParams = $request->getServerParams();
if (!empty($serverParams['REMOTE_ADDR'])) {
$user = $event->getUser();
$requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR'];
if ($user === null) {
$user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']);
} elseif ($user->getIpAddress() === null) {
$user->setIpAddress($serverParams['REMOTE_ADDR']);
}
$event->setUser($user);
}
$requestData['cookies'] = $request->getCookieParams();
$requestData['headers'] = $request->getHeaders();
} else {
$requestData['headers'] = $this->sanitizeHeaders($request->getHeaders());
}
$requestBody = $this->captureRequestBody($options, $request);
if (!empty($requestBody)) {
$requestData['data'] = $requestBody;
}
$event->setRequest($requestData);
}
/**
* Removes headers containing potential PII.
*
* @param array $headers Array containing request headers
*
* @return array
*/
private function sanitizeHeaders(array $headers): array
{
foreach ($headers as $name => $values) {
// Cast the header name into a string, to avoid errors on numeric headers
$name = (string) $name;
if (!\in_array(strtolower($name), $this->options['pii_sanitize_headers'], true)) {
continue;
}
foreach ($values as $headerLine => $headerValue) {
$headers[$name][$headerLine] = '[Filtered]';
}
}
return $headers;
}
/**
* Gets the decoded body of the request, if available. If the Content-Type
* header contains "application/json" then the content is decoded and if
* the parsing fails then the raw data is returned. If there are submitted
* fields or files, all of their information are parsed and returned.
*
* @param Options $options The options of the client
* @param ServerRequestInterface $request The server request
*
* @return mixed
*/
private function captureRequestBody(Options $options, ServerRequestInterface $request)
{
$maxRequestBodySize = $options->getMaxRequestBodySize();
$requestBodySize = (int) $request->getHeaderLine('Content-Length');
if (!$this->isRequestBodySizeWithinReadBounds($requestBodySize, $maxRequestBodySize)) {
return null;
}
$requestData = $request->getParsedBody();
$requestData = array_replace(
$this->parseUploadedFiles($request->getUploadedFiles()),
\is_array($requestData) ? $requestData : []
);
if (!empty($requestData)) {
return $requestData;
}
$requestBody = '';
$maxLength = self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize];
if ($maxLength > 0) {
$stream = $request->getBody();
while ($maxLength > 0 && !$stream->eof()) {
if ('' === $buffer = $stream->read(min($maxLength, self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH))) {
break;
}
$requestBody .= $buffer;
$maxLength -= \strlen($buffer);
}
}
if ($request->getHeaderLine('Content-Type') === 'application/json') {
try {
return JSON::decode($requestBody);
} catch (JsonException $exception) {
// Fallback to returning the raw data from the request body
}
}
return $requestBody;
}
/**
* Create an array with the same structure as $uploadedFiles, but replacing
* each UploadedFileInterface with an array of info.
*
* @param array $uploadedFiles The uploaded files info from a PSR-7 server request
*
* @return array
*/
private function parseUploadedFiles(array $uploadedFiles): array
{
$result = [];
foreach ($uploadedFiles as $key => $item) {
if ($item instanceof UploadedFileInterface) {
$result[$key] = [
'client_filename' => $item->getClientFilename(),
'client_media_type' => $item->getClientMediaType(),
'size' => $item->getSize(),
];
} elseif (\is_array($item)) {
$result[$key] = $this->parseUploadedFiles($item);
} else {
throw new \UnexpectedValueException(\sprintf('Expected either an object implementing the "%s" interface or an array. Got: "%s".', UploadedFileInterface::class, \is_object($item) ? \get_class($item) : \gettype($item)));
}
}
return $result;
}
private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string $maxRequestBodySize): bool
{
if ($requestBodySize <= 0) {
return false;
}
if ($maxRequestBodySize === 'none' || $maxRequestBodySize === 'never') {
return false;
}
if ($maxRequestBodySize === 'small' && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) {
return false;
}
if ($maxRequestBodySize === 'medium' && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) {
return false;
}
return true;
}
/**
* Configures the options of the client.
*
* @param OptionsResolver $resolver The resolver for the options
*/
private function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('pii_sanitize_headers', self::DEFAULT_SENSITIVE_HEADERS);
$resolver->setAllowedTypes('pii_sanitize_headers', 'string[]');
$resolver->setNormalizer('pii_sanitize_headers', static function (SymfonyOptions $options, array $value): array {
return array_map('strtolower', $value);
});
}
}
sentry/src/Options.php 0000644 00000132336 15266563341 0011046 0 ustar 00
*/
final class Options
{
/**
* The default maximum number of breadcrumbs that will be sent with an
* event.
*/
public const DEFAULT_MAX_BREADCRUMBS = 100;
/**
* The default maximum execution time in seconds for the request+response
* as a whole.
*/
public const DEFAULT_HTTP_TIMEOUT = 5;
/**
* The default maximum number of seconds to wait while trying to connect to a
* server.
*/
public const DEFAULT_HTTP_CONNECT_TIMEOUT = 2;
/**
* @var array The configuration options
*/
private $options;
/**
* @var OptionsResolver The options resolver
*/
private $resolver;
/**
* Class constructor.
*
* @param array $options The configuration options
*/
public function __construct(array $options = [])
{
$this->resolver = new OptionsResolver();
$this->configureOptions($this->resolver);
$this->options = $this->resolver->resolve($options);
if ($this->options['enable_tracing'] === true && $this->options['traces_sample_rate'] === null) {
$this->options = array_merge($this->options, ['traces_sample_rate' => 1]);
}
}
/**
* Gets the prefixes which should be stripped from filenames to create
* relative paths.
*
* @return string[]
*/
public function getPrefixes(): array
{
return $this->options['prefixes'];
}
/**
* Sets the prefixes which should be stripped from filenames to create
* relative paths.
*
* @param string[] $prefixes The prefixes
*/
public function setPrefixes(array $prefixes): self
{
$options = array_merge($this->options, ['prefixes' => $prefixes]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the sampling factor to apply to events. A value of 0 will deny
* sending any events, and a value of 1 will send 100% of events.
*/
public function getSampleRate(): float
{
return $this->options['sample_rate'];
}
/**
* Sets the sampling factor to apply to events. A value of 0 will deny
* sending any events, and a value of 1 will send 100% of events.
*
* @param float $sampleRate The sampling factor
*/
public function setSampleRate(float $sampleRate): self
{
$options = array_merge($this->options, ['sample_rate' => $sampleRate]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the sampling factor to apply to transaction. A value of 0 will deny
* sending any transaction, and a value of 1 will send 100% of transaction.
*/
public function getTracesSampleRate(): ?float
{
return $this->options['traces_sample_rate'];
}
/**
* Sets if tracing should be enabled or not. If null tracesSampleRate takes
* precedence.
*
* @param bool|null $enableTracing Boolean if tracing should be enabled or not
*
* @deprecated since version 4.7. To be removed in version 5.0
*/
public function setEnableTracing(?bool $enableTracing): self
{
$options = array_merge($this->options, ['enable_tracing' => $enableTracing]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets if tracing is enabled or not.
*
* @return bool|null If the option `enable_tracing` is set or not
*
* @deprecated since version 4.7. To be removed in version 5.0
*/
public function getEnableTracing(): ?bool
{
return $this->options['enable_tracing'];
}
/**
* Sets if logs should be enabled or not.
*
* @param bool|null $enableLogs Boolean if logs should be enabled or not
*/
public function setEnableLogs(?bool $enableLogs): self
{
$options = array_merge($this->options, ['enable_logs' => $enableLogs]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets if logs is enabled or not.
*/
public function getEnableLogs(): bool
{
return $this->options['enable_logs'] ?? false;
}
/**
* Sets the sampling factor to apply to transactions. A value of 0 will deny
* sending any transactions, and a value of 1 will send 100% of transactions.
*
* @param ?float $sampleRate The sampling factor
*/
public function setTracesSampleRate(?float $sampleRate): self
{
$options = array_merge($this->options, ['traces_sample_rate' => $sampleRate]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getProfilesSampleRate(): ?float
{
/** @var int|float|null $value */
$value = $this->options['profiles_sample_rate'] ?? null;
return $value ?? null;
}
public function setProfilesSampleRate(?float $sampleRate): self
{
$options = array_merge($this->options, ['profiles_sample_rate' => $sampleRate]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets whether tracing is enabled or not. The feature is enabled when at
* least one of the `traces_sample_rate` and `traces_sampler` options is
* set and `enable_tracing` is set and not false.
*/
public function isTracingEnabled(): bool
{
if ($this->getEnableTracing() !== null && $this->getEnableTracing() === false) {
return false;
}
return $this->getTracesSampleRate() !== null || $this->getTracesSampler() !== null;
}
/**
* Gets whether the stacktrace will be attached on captureMessage.
*/
public function shouldAttachStacktrace(): bool
{
return $this->options['attach_stacktrace'];
}
/**
* Sets whether the stacktrace will be attached on captureMessage.
*
* @param bool $enable Flag indicating if the stacktrace will be attached to captureMessage calls
*/
public function setAttachStacktrace(bool $enable): self
{
$options = array_merge($this->options, ['attach_stacktrace' => $enable]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets whether a metric has their code location attached.
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function shouldAttachMetricCodeLocations(): bool
{
return $this->options['attach_metric_code_locations'];
}
/**
* Sets whether a metric will have their code location attached.
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function setAttachMetricCodeLocations(bool $enable): self
{
$options = array_merge($this->options, ['attach_metric_code_locations' => $enable]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the number of lines of code context to capture, or null if none.
*/
public function getContextLines(): ?int
{
return $this->options['context_lines'];
}
/**
* Sets the number of lines of code context to capture, or null if none.
*
* @param int|null $contextLines The number of lines of code
*/
public function setContextLines(?int $contextLines): self
{
$options = array_merge($this->options, ['context_lines' => $contextLines]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the environment.
*/
public function getEnvironment(): ?string
{
return $this->options['environment'];
}
/**
* Sets the environment.
*
* @param string|null $environment The environment
*/
public function setEnvironment(?string $environment): self
{
$options = array_merge($this->options, ['environment' => $environment]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the list of paths to exclude from in_app detection.
*
* @return string[]
*/
public function getInAppExcludedPaths(): array
{
return $this->options['in_app_exclude'];
}
/**
* Sets the list of paths to exclude from in_app detection.
*
* @param string[] $paths The list of paths
*/
public function setInAppExcludedPaths(array $paths): self
{
$options = array_merge($this->options, ['in_app_exclude' => $paths]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the list of paths which has to be identified as in_app.
*
* @return string[]
*/
public function getInAppIncludedPaths(): array
{
return $this->options['in_app_include'];
}
/**
* Set the list of paths to include in in_app detection.
*
* @param string[] $paths The list of paths
*/
public function setInAppIncludedPaths(array $paths): self
{
$options = array_merge($this->options, ['in_app_include' => $paths]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a PSR-3 compatible logger to log internal debug messages.
*/
public function getLogger(): ?LoggerInterface
{
return $this->options['logger'];
}
/**
* Helper to always get a logger instance even if it was not set.
*/
public function getLoggerOrNullLogger(): LoggerInterface
{
return $this->getLogger() ?? new NullLogger();
}
/**
* Sets a PSR-3 compatible logger to log internal debug messages.
*/
public function setLogger(LoggerInterface $logger): self
{
$options = array_merge($this->options, ['logger' => $logger]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function isSpotlightEnabled(): bool
{
return \is_string($this->options['spotlight']) || $this->options['spotlight'];
}
/**
* @param bool|string $enable can be passed a boolean or the Spotlight URL (which will also enable Spotlight)
*/
public function enableSpotlight($enable): self
{
$options = array_merge($this->options, ['spotlight' => $enable]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getSpotlightUrl(): string
{
if (\is_string($this->options['spotlight'])) {
return $this->options['spotlight'];
}
return $this->options['spotlight_url'];
}
/**
* @return $this
*
* @deprecated since version 4.11. To be removed in 5.x. You may use `enableSpotlight` instead.
*/
public function setSpotlightUrl(string $url): self
{
$options = array_merge($this->options, ['spotlight_url' => $url]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the release tag to be passed with every event sent to Sentry.
*/
public function getRelease(): ?string
{
return $this->options['release'];
}
/**
* Sets the release tag to be passed with every event sent to Sentry.
*
* @param string|null $release The release
*/
public function setRelease(?string $release): self
{
$options = array_merge($this->options, ['release' => $release]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the DSN of the Sentry server the authenticated user is bound to.
*/
public function getDsn(): ?Dsn
{
return $this->options['dsn'];
}
/**
* Gets the Org ID.
*/
public function getOrgId(): ?int
{
return $this->options['org_id'];
}
/**
* Sets the Org ID.
*/
public function setOrgId(int $orgId): self
{
$options = array_merge($this->options, ['org_id' => $orgId]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the name of the server the SDK is running on (e.g. the hostname).
*/
public function getServerName(): string
{
return $this->options['server_name'];
}
/**
* Sets the name of the server the SDK is running on (e.g. the hostname).
*
* @param string $serverName The server name
*/
public function setServerName(string $serverName): self
{
$options = array_merge($this->options, ['server_name' => $serverName]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a list of exceptions to be ignored and not sent to Sentry.
*
* @return string[]
*
* @psalm-return list>
*/
public function getIgnoreExceptions(): array
{
return $this->options['ignore_exceptions'];
}
/**
* Sets a list of exceptions to be ignored and not sent to Sentry.
*
* @param string[] $ignoreErrors The list of exceptions to be ignored
*/
public function setIgnoreExceptions(array $ignoreErrors): self
{
$options = array_merge($this->options, ['ignore_exceptions' => $ignoreErrors]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a list of transaction names to be ignored and not sent to Sentry.
*
* @return string[]
*/
public function getIgnoreTransactions(): array
{
return $this->options['ignore_transactions'];
}
/**
* Sets a list of transaction names to be ignored and not sent to Sentry.
*
* @param string[] $ignoreTransaction The list of transaction names to be ignored
*/
public function setIgnoreTransactions(array $ignoreTransaction): self
{
$options = array_merge($this->options, ['ignore_transactions' => $ignoreTransaction]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked before an event is sent to the server.
* If `null` is returned it won't be sent.
*
* @psalm-return callable(Event, ?EventHint): ?Event
*/
public function getBeforeSendCallback(): callable
{
return $this->options['before_send'];
}
/**
* Sets a callable to be called to decide whether an event should
* be captured or not.
*
* @param callable $callback The callable
*
* @psalm-param callable(Event, ?EventHint): ?Event $callback
*/
public function setBeforeSendCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_send' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked before an transaction is sent to the server.
* If `null` is returned it won't be sent.
*
* @psalm-return callable(Event, ?EventHint): ?Event
*/
public function getBeforeSendTransactionCallback(): callable
{
return $this->options['before_send_transaction'];
}
/**
* Sets a callable to be called to decide whether an transaction should
* be captured or not.
*
* @param callable $callback The callable
*
* @psalm-param callable(Event, ?EventHint): ?Event $callback
*/
public function setBeforeSendTransactionCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_send_transaction' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked before a check-in is sent to the server.
* If `null` is returned it won't be sent.
*
* @psalm-return callable(Event, ?EventHint): ?Event
*/
public function getBeforeSendCheckInCallback(): callable
{
return $this->options['before_send_check_in'];
}
/**
* Sets a callable to be called to decide whether a check-in should
* be captured or not.
*
* @param callable $callback The callable
*
* @psalm-param callable(Event, ?EventHint): ?Event $callback
*/
public function setBeforeSendCheckInCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_send_check_in' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked before an log is sent to the server.
* If `null` is returned it won't be sent.
*
* @psalm-return callable(Log): ?Log
*/
public function getBeforeSendLogCallback(): callable
{
return $this->options['before_send_log'];
}
/**
* Sets a callable to be called to decide whether a log should
* be captured or not.
*
* @param callable $callback The callable
*
* @psalm-param callable(Log): ?Log $callback
*/
public function setBeforeSendLogCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_send_log' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked before metrics are sent to the server.
* If `null` is returned it won't be sent.
*
* @psalm-return callable(Event, ?EventHint): ?Event
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function getBeforeSendMetricsCallback(): callable
{
return $this->options['before_send_metrics'];
}
/**
* Sets a callable to be called to decide whether metrics should
* be send or not.
*
* @param callable $callback The callable
*
* @psalm-param callable(Event, ?EventHint): ?Event $callback
*
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function setBeforeSendMetricsCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_send_metrics' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets an allow list of trace propagation targets.
*
* @return string[]|null
*/
public function getTracePropagationTargets(): ?array
{
return $this->options['trace_propagation_targets'];
}
/**
* Set an allow list of trace propagation targets.
*
* @param string[] $tracePropagationTargets Trace propagation targets
*/
public function setTracePropagationTargets(array $tracePropagationTargets): self
{
$options = array_merge($this->options, ['trace_propagation_targets' => $tracePropagationTargets]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Returns whether strict trace propagation is enabled or not.
*/
public function isStrictTracePropagationEnabled(): bool
{
return $this->options['strict_trace_propagation'];
}
/**
* Sets if strict trace propagation should be enabled or not.
*/
public function enableStrictTracePropagation(bool $strictTracePropagation): self
{
$options = array_merge($this->options, ['strict_trace_propagation' => $strictTracePropagation]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a list of default tags for events.
*
* @return array
*/
public function getTags(): array
{
return $this->options['tags'];
}
/**
* Sets a list of default tags for events.
*
* @param array $tags A list of tags
*/
public function setTags(array $tags): self
{
$options = array_merge($this->options, ['tags' => $tags]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report.
*/
public function getErrorTypes(): int
{
return $this->options['error_types'] ?? error_reporting();
}
/**
* Sets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report.
*
* @param int $errorTypes The bit mask
*/
public function setErrorTypes(int $errorTypes): self
{
$options = array_merge($this->options, ['error_types' => $errorTypes]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the maximum number of breadcrumbs sent with events.
*/
public function getMaxBreadcrumbs(): int
{
return $this->options['max_breadcrumbs'];
}
/**
* Sets the maximum number of breadcrumbs sent with events.
*
* @param int $maxBreadcrumbs The maximum number of breadcrumbs
*/
public function setMaxBreadcrumbs(int $maxBreadcrumbs): self
{
$options = array_merge($this->options, ['max_breadcrumbs' => $maxBreadcrumbs]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked when adding a breadcrumb.
*
* @psalm-return callable(Breadcrumb): ?Breadcrumb
*/
public function getBeforeBreadcrumbCallback(): callable
{
return $this->options['before_breadcrumb'];
}
/**
* Sets a callback that will be invoked when adding a breadcrumb, allowing
* to optionally modify it before adding it to future events. Note that you
* must return a valid breadcrumb from this callback. If you do not wish to
* modify the breadcrumb, simply return it at the end. Returning `null` will
* cause the breadcrumb to be dropped.
*
* @param callable $callback The callback
*
* @psalm-param callable(Breadcrumb): ?Breadcrumb $callback
*/
public function setBeforeBreadcrumbCallback(callable $callback): self
{
$options = array_merge($this->options, ['before_breadcrumb' => $callback]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Sets the list of integrations that should be installed after SDK was
* initialized or a function that receives default integrations and returns
* a new, updated list.
*
* @param IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[] $integrations The list or callable
*/
public function setIntegrations($integrations): self
{
$options = array_merge($this->options, ['integrations' => $integrations]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Returns all configured integrations that will be used by the Client.
*
* @return IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[]
*/
public function getIntegrations()
{
return $this->options['integrations'];
}
public function setTransport(TransportInterface $transport): self
{
$options = array_merge($this->options, ['transport' => $transport]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getTransport(): ?TransportInterface
{
return $this->options['transport'];
}
public function setHttpClient(HttpClientInterface $httpClient): self
{
$options = array_merge($this->options, ['http_client' => $httpClient]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getHttpClient(): ?HttpClientInterface
{
return $this->options['http_client'];
}
/**
* Should default PII be sent by default.
*/
public function shouldSendDefaultPii(): bool
{
return $this->options['send_default_pii'];
}
/**
* Sets if default PII should be sent with every event (if possible).
*
* @param bool $enable Flag indicating if default PII will be sent
*/
public function setSendDefaultPii(bool $enable): self
{
$options = array_merge($this->options, ['send_default_pii' => $enable]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Returns whether the default integrations are enabled.
*/
public function hasDefaultIntegrations(): bool
{
return $this->options['default_integrations'];
}
/**
* Sets whether the default integrations are enabled.
*
* @param bool $enable Flag indicating whether the default integrations should be enabled
*/
public function setDefaultIntegrations(bool $enable): self
{
$options = array_merge($this->options, ['default_integrations' => $enable]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the max length for values in the event payload.
*/
public function getMaxValueLength(): int
{
return $this->options['max_value_length'];
}
/**
* Sets the max length for specific values in the event payload.
*
* @param int $maxValueLength The number of characters after which the values containing text will be truncated
*/
public function setMaxValueLength(int $maxValueLength): self
{
$options = array_merge($this->options, ['max_value_length' => $maxValueLength]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the http proxy setting.
*/
public function getHttpProxy(): ?string
{
return $this->options['http_proxy'];
}
/**
* Sets the http proxy. Be aware this option only works when curl client is used.
*
* @param string|null $httpProxy The http proxy
*/
public function setHttpProxy(?string $httpProxy): self
{
$options = array_merge($this->options, ['http_proxy' => $httpProxy]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getHttpProxyAuthentication(): ?string
{
return $this->options['http_proxy_authentication'];
}
public function setHttpProxyAuthentication(?string $httpProxy): self
{
$options = array_merge($this->options, ['http_proxy_authentication' => $httpProxy]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the maximum number of seconds to wait while trying to connect to a server.
*/
public function getHttpConnectTimeout(): float
{
return $this->options['http_connect_timeout'];
}
/**
* Sets the maximum number of seconds to wait while trying to connect to a server.
*
* @param float $httpConnectTimeout The amount of time in seconds
*/
public function setHttpConnectTimeout(float $httpConnectTimeout): self
{
$options = array_merge($this->options, ['http_connect_timeout' => $httpConnectTimeout]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the maximum execution time for the request+response as a whole.
*/
public function getHttpTimeout(): float
{
return $this->options['http_timeout'];
}
/**
* Sets the maximum execution time for the request+response as a whole. The
* value should also include the time for the connect phase, so it should be
* greater than the value set for the `http_connect_timeout` option.
*
* @param float $httpTimeout The amount of time in seconds
*/
public function setHttpTimeout(float $httpTimeout): self
{
$options = array_merge($this->options, ['http_timeout' => $httpTimeout]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getHttpSslVerifyPeer(): bool
{
return $this->options['http_ssl_verify_peer'];
}
public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): self
{
$options = array_merge($this->options, ['http_ssl_verify_peer' => $httpSslVerifyPeer]);
$this->options = $this->resolver->resolve($options);
return $this;
}
public function getHttpSslNativeCa(): bool
{
return $this->options['http_ssl_native_ca'];
}
public function setHttpSslNativeCa(bool $httpSslNativeCa): self
{
$options = array_merge($this->options, ['http_ssl_native_ca' => $httpSslNativeCa]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Returns whether the requests should be compressed using GZIP or not.
*/
public function isHttpCompressionEnabled(): bool
{
return $this->options['http_compression'];
}
/**
* Sets whether the request should be compressed using JSON or not.
*/
public function setEnableHttpCompression(bool $enabled): self
{
$options = array_merge($this->options, ['http_compression' => $enabled]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets whether the silenced errors should be captured or not.
*
* @return bool If true, errors silenced through the @ operator will be reported,
* ignored otherwise
*/
public function shouldCaptureSilencedErrors(): bool
{
return $this->options['capture_silenced_errors'];
}
/**
* Sets whether the silenced errors should be captured or not.
*
* @param bool $shouldCapture If set to true, errors silenced through the @
* operator will be reported, ignored otherwise
*/
public function setCaptureSilencedErrors(bool $shouldCapture): self
{
$options = array_merge($this->options, ['capture_silenced_errors' => $shouldCapture]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the limit up to which integrations should capture the HTTP request
* body.
*/
public function getMaxRequestBodySize(): string
{
return $this->options['max_request_body_size'];
}
/**
* Sets the limit up to which integrations should capture the HTTP request
* body.
*
* @param string $maxRequestBodySize The limit up to which request body are
* captured. It can be set to one of the
* following values:
*
* - never: request bodies are never sent
* - small: only small request bodies will
* be captured where the cutoff for small
* depends on the SDK (typically 4KB)
* - medium: medium-sized requests and small
* requests will be captured. (typically 10KB)
* - always: the SDK will always capture the
* request body for as long as sentry can
* make sense of it
*/
public function setMaxRequestBodySize(string $maxRequestBodySize): self
{
$options = array_merge($this->options, ['max_request_body_size' => $maxRequestBodySize]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets the callbacks used to customize how objects are serialized in the payload
* of the event.
*
* @return array
*/
public function getClassSerializers(): array
{
return $this->options['class_serializers'];
}
/**
* Sets a list of callables that will be called to customize how objects are
* serialized in the event's payload. The list must be a map of FQCN/callable
* pairs.
*
* @param array $serializers The list of serializer callbacks
*/
public function setClassSerializers(array $serializers): self
{
$options = array_merge($this->options, ['class_serializers' => $serializers]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Gets a callback that will be invoked when we sample a Transaction.
*
* @psalm-return null|callable(Tracing\SamplingContext): float
*/
public function getTracesSampler(): ?callable
{
return $this->options['traces_sampler'];
}
/**
* Sets a callback that will be invoked when we take the sampling decision for Transactions.
* Return a number between 0 and 1 to define the sample rate for the provided SamplingContext.
*
* @param ?callable $sampler The sampler
*
* @psalm-param null|callable(Tracing\SamplingContext): float $sampler
*/
public function setTracesSampler(?callable $sampler): self
{
$options = array_merge($this->options, ['traces_sampler' => $sampler]);
$this->options = $this->resolver->resolve($options);
return $this;
}
/**
* Configures the options of the client.
*
* @param OptionsResolver $resolver The resolver for the options
*
* @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
* @throws \Symfony\Component\OptionsResolver\Exception\AccessException
*/
private function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'integrations' => [],
'default_integrations' => true,
'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')),
'sample_rate' => 1,
'enable_tracing' => null,
'enable_logs' => false,
'traces_sample_rate' => null,
'traces_sampler' => null,
'profiles_sample_rate' => null,
'attach_stacktrace' => false,
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
'attach_metric_code_locations' => false,
'context_lines' => 5,
'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null,
'logger' => null,
'spotlight' => $_SERVER['SENTRY_SPOTLIGHT'] ?? null,
/**
* @deprecated since version 4.11. To be removed in 5.0. You may use `spotlight` instead.
*/
'spotlight_url' => 'http://localhost:8969',
'release' => $_SERVER['SENTRY_RELEASE'] ?? $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] ?? null,
'dsn' => $_SERVER['SENTRY_DSN'] ?? null,
'org_id' => null,
'server_name' => gethostname(),
'ignore_exceptions' => [],
'ignore_transactions' => [],
'before_send' => static function (Event $event): Event {
return $event;
},
'before_send_transaction' => static function (Event $transaction): Event {
return $transaction;
},
'before_send_check_in' => static function (Event $checkIn): Event {
return $checkIn;
},
'before_send_log' => static function (Log $log): Log {
return $log;
},
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
'before_send_metrics' => static function (Event $metrics): ?Event {
return null;
},
'trace_propagation_targets' => null,
'strict_trace_propagation' => false,
'tags' => [],
'error_types' => null,
'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS,
'before_breadcrumb' => static function (Breadcrumb $breadcrumb): Breadcrumb {
return $breadcrumb;
},
'in_app_exclude' => [],
'in_app_include' => [],
'send_default_pii' => false,
'max_value_length' => 1024,
'transport' => null,
'http_client' => null,
'http_proxy' => null,
'http_proxy_authentication' => null,
'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT,
'http_timeout' => self::DEFAULT_HTTP_TIMEOUT,
'http_ssl_verify_peer' => true,
'http_ssl_native_ca' => false,
'http_compression' => true,
'capture_silenced_errors' => false,
'max_request_body_size' => 'medium',
'class_serializers' => [],
]);
$resolver->setAllowedTypes('prefixes', 'string[]');
$resolver->setAllowedTypes('sample_rate', ['int', 'float']);
$resolver->setAllowedTypes('enable_tracing', ['null', 'bool']);
$resolver->setAllowedTypes('enable_logs', 'bool');
$resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']);
$resolver->setAllowedTypes('traces_sampler', ['null', 'callable']);
$resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']);
$resolver->setAllowedTypes('attach_stacktrace', 'bool');
$resolver->setAllowedTypes('attach_metric_code_locations', 'bool');
$resolver->setAllowedTypes('context_lines', ['null', 'int']);
$resolver->setAllowedTypes('environment', ['null', 'string']);
$resolver->setAllowedTypes('in_app_exclude', 'string[]');
$resolver->setAllowedTypes('in_app_include', 'string[]');
$resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]);
$resolver->setAllowedTypes('spotlight', ['bool', 'string', 'null']);
$resolver->setAllowedTypes('spotlight_url', 'string');
$resolver->setAllowedTypes('release', ['null', 'string']);
$resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]);
$resolver->setAllowedTypes('org_id', ['null', 'int']);
$resolver->setAllowedTypes('server_name', 'string');
$resolver->setAllowedTypes('before_send', ['callable']);
$resolver->setAllowedTypes('before_send_transaction', ['callable']);
$resolver->setAllowedTypes('before_send_log', 'callable');
$resolver->setAllowedTypes('ignore_exceptions', 'string[]');
$resolver->setAllowedTypes('ignore_transactions', 'string[]');
$resolver->setAllowedTypes('trace_propagation_targets', ['null', 'string[]']);
$resolver->setAllowedTypes('strict_trace_propagation', 'bool');
$resolver->setAllowedTypes('tags', 'string[]');
$resolver->setAllowedTypes('error_types', ['null', 'int']);
$resolver->setAllowedTypes('max_breadcrumbs', 'int');
$resolver->setAllowedTypes('before_breadcrumb', ['callable']);
$resolver->setAllowedTypes('integrations', ['Sentry\\Integration\\IntegrationInterface[]', 'callable']);
$resolver->setAllowedTypes('send_default_pii', 'bool');
$resolver->setAllowedTypes('default_integrations', 'bool');
$resolver->setAllowedTypes('max_value_length', 'int');
$resolver->setAllowedTypes('transport', ['null', TransportInterface::class]);
$resolver->setAllowedTypes('http_client', ['null', HttpClientInterface::class]);
$resolver->setAllowedTypes('http_proxy', ['null', 'string']);
$resolver->setAllowedTypes('http_proxy_authentication', ['null', 'string']);
$resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']);
$resolver->setAllowedTypes('http_timeout', ['int', 'float']);
$resolver->setAllowedTypes('http_ssl_verify_peer', 'bool');
$resolver->setAllowedTypes('http_ssl_native_ca', 'bool');
$resolver->setAllowedTypes('http_compression', 'bool');
$resolver->setAllowedTypes('capture_silenced_errors', 'bool');
$resolver->setAllowedTypes('max_request_body_size', 'string');
$resolver->setAllowedTypes('class_serializers', 'array');
$resolver->setAllowedValues('max_request_body_size', ['none', 'never', 'small', 'medium', 'always']);
$resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption']));
$resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions']));
$resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption']));
$resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption']));
$resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption']));
$resolver->setNormalizer('prefixes', function (SymfonyOptions $options, array $value) {
return array_map([$this, 'normalizeAbsolutePath'], $value);
});
$resolver->setNormalizer('spotlight', \Closure::fromCallable([$this, 'normalizeBooleanOrUrl']));
$resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, array $value) {
return array_map([$this, 'normalizeAbsolutePath'], $value);
});
$resolver->setNormalizer('in_app_include', function (SymfonyOptions $options, array $value) {
return array_map([$this, 'normalizeAbsolutePath'], $value);
});
}
/**
* Normalizes the given path as an absolute path.
*
* @param string $value The path
*/
private function normalizeAbsolutePath(string $value): string
{
$path = @realpath($value);
if ($path === false) {
$path = $value;
}
return $path;
}
/**
* @return bool|string
*/
private function normalizeBooleanOrUrl(SymfonyOptions $options, ?string $booleanOrUrl)
{
if (empty($booleanOrUrl)) {
return false;
}
if (filter_var($booleanOrUrl, \FILTER_VALIDATE_URL)) {
return $booleanOrUrl;
}
return filter_var($booleanOrUrl, \FILTER_VALIDATE_BOOLEAN);
}
/**
* Normalizes the DSN option by parsing the host, public and secret keys and
* an optional path.
*
* @param SymfonyOptions $options The configuration options
* @param string|bool|Dsn|null $value The actual value of the option to normalize
*/
private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn
{
if ($value === null || \is_bool($value)) {
return null;
}
if ($value instanceof Dsn) {
return $value;
}
switch (strtolower($value)) {
case '':
case 'false':
case '(false)':
case 'empty':
case '(empty)':
case 'null':
case '(null)':
return null;
}
return Dsn::createFromString($value);
}
/**
* Validates the DSN option ensuring that all required pieces are set and
* that the URL is valid.
*
* @param string|bool|Dsn|null $dsn The value of the option
*/
private function validateDsnOption($dsn): bool
{
if ($dsn === null || $dsn instanceof Dsn) {
return true;
}
if (\is_bool($dsn)) {
return $dsn === false;
}
switch (strtolower($dsn)) {
case '':
case 'false':
case '(false)':
case 'empty':
case '(empty)':
case 'null':
case '(null)':
return true;
}
try {
Dsn::createFromString($dsn);
return true;
} catch (\InvalidArgumentException $exception) {
return false;
}
}
/**
* Validates if the value of the max_breadcrumbs option is valid.
*
* @param int $value The value to validate
*/
private function validateMaxBreadcrumbsOptions(int $value): bool
{
return $value >= 0;
}
/**
* Validates that the values passed to the `class_serializers` option are valid.
*
* @param mixed[] $serializers The value to validate
*/
private function validateClassSerializersOption(array $serializers): bool
{
foreach ($serializers as $class => $serializer) {
if (!\is_string($class) || !\is_callable($serializer)) {
return false;
}
}
return true;
}
/**
* Validates that the value passed to the "context_lines" option is valid.
*
* @param int|null $contextLines The value to validate
*/
private function validateContextLinesOption(?int $contextLines): bool
{
return $contextLines === null || $contextLines >= 0;
}
}
sentry/src/Breadcrumb.php 0000644 00000017434 15266563341 0011462 0 ustar 00
*/
final class Breadcrumb
{
/**
* This constant defines the default breadcrumb type.
*/
public const TYPE_DEFAULT = 'default';
/**
* This constant defines the http breadcrumb type.
*/
public const TYPE_HTTP = 'http';
/**
* This constant defines the user breadcrumb type.
*/
public const TYPE_USER = 'user';
/**
* This constant defines the navigation breadcrumb type.
*/
public const TYPE_NAVIGATION = 'navigation';
/**
* This constant defines the error breadcrumb type.
*/
public const TYPE_ERROR = 'error';
/**
* This constant defines the debug level for a breadcrumb.
*/
public const LEVEL_DEBUG = 'debug';
/**
* This constant defines the info level for a breadcrumb.
*/
public const LEVEL_INFO = 'info';
/**
* This constant defines the warning level for a breadcrumb.
*/
public const LEVEL_WARNING = 'warning';
/**
* This constant defines the error level for a breadcrumb.
*/
public const LEVEL_ERROR = 'error';
/**
* This constant defines the fatal level for a breadcrumb.
*/
public const LEVEL_FATAL = 'fatal';
/**
* This constant defines the list of values allowed to be set as severity
* level of the breadcrumb.
*/
private const ALLOWED_LEVELS = [
self::LEVEL_DEBUG,
self::LEVEL_INFO,
self::LEVEL_WARNING,
self::LEVEL_ERROR,
self::LEVEL_FATAL,
];
/**
* @var string The category of the breadcrumb
*/
private $category;
/**
* @var string The type of breadcrumb
*/
private $type;
/**
* @var string|null The message of the breadcrumb
*/
private $message;
/**
* @var string The level of the breadcrumb
*/
private $level;
/**
* @var array The meta data of the breadcrumb
*/
private $metadata;
/**
* @var float The timestamp of the breadcrumb
*/
private $timestamp;
/**
* Constructor.
*
* @param string $level The error level of the breadcrumb
* @param string $type The type of the breadcrumb
* @param string $category The category of the breadcrumb
* @param string|null $message Optional text message
* @param array $metadata Additional information about the breadcrumb
* @param float|null $timestamp Optional timestamp of the breadcrumb
*/
public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = [], ?float $timestamp = null)
{
if (!\in_array($level, self::ALLOWED_LEVELS, true)) {
throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.');
}
$this->type = $type;
$this->level = $level;
$this->category = $category;
$this->message = $message;
$this->metadata = $metadata;
$this->timestamp = $timestamp ?? microtime(true);
}
/**
* Gets the breadcrumb type.
*/
public function getType(): string
{
return $this->type;
}
/**
* Sets the type of the breadcrumb.
*
* @param string $type The type
*
* @return static
*/
public function withType(string $type): self
{
if ($type === $this->type) {
return $this;
}
$new = clone $this;
$new->type = $type;
return $new;
}
/**
* Gets the breadcrumb level.
*/
public function getLevel(): string
{
return $this->level;
}
/**
* Sets the error level of the breadcrumb.
*
* @param string $level The level
*
* @return static
*/
public function withLevel(string $level): self
{
if (!\in_array($level, self::ALLOWED_LEVELS, true)) {
throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.');
}
if ($level === $this->level) {
return $this;
}
$new = clone $this;
$new->level = $level;
return $new;
}
/**
* Gets the breadcrumb category.
*/
public function getCategory(): string
{
return $this->category;
}
/**
* Sets the breadcrumb category.
*
* @param string $category The category
*
* @return static
*/
public function withCategory(string $category): self
{
if ($category === $this->category) {
return $this;
}
$new = clone $this;
$new->category = $category;
return $new;
}
/**
* Gets the breadcrumb message.
*/
public function getMessage(): ?string
{
return $this->message;
}
/**
* Sets the breadcrumb message.
*
* @param string $message The message
*
* @return static
*/
public function withMessage(string $message): self
{
if ($message === $this->message) {
return $this;
}
$new = clone $this;
$new->message = $message;
return $new;
}
/**
* Gets the breadcrumb meta data.
*
* @return array
*/
public function getMetadata(): array
{
return $this->metadata;
}
/**
* Returns an instance of this class with the provided metadata, replacing
* any existing values of any metadata with the same name.
*
* @param string $name The name of the metadata
* @param mixed $value The value
*
* @return static
*/
public function withMetadata(string $name, $value): self
{
if (isset($this->metadata[$name]) && $value === $this->metadata[$name]) {
return $this;
}
$new = clone $this;
$new->metadata[$name] = $value;
return $new;
}
/**
* Returns an instance of this class without the specified metadata
* information.
*
* @param string $name The name of the metadata
*
* @return static|Breadcrumb
*/
public function withoutMetadata(string $name): self
{
if (!isset($this->metadata[$name])) {
return $this;
}
$new = clone $this;
unset($new->metadata[$name]);
return $new;
}
/**
* Gets the breadcrumb timestamp.
*/
public function getTimestamp(): float
{
return $this->timestamp;
}
/**
* Sets the breadcrumb timestamp.
*
* @param float $timestamp The timestamp
*
* @return static
*/
public function withTimestamp(float $timestamp): self
{
if ($timestamp === $this->timestamp) {
return $this;
}
$new = clone $this;
$new->timestamp = $timestamp;
return $new;
}
/**
* Helper method to create an instance of this class from an array of data.
*
* @param array $data Data used to populate the breadcrumb
*
* @psalm-param array{
* level: string,
* type?: string,
* category: string,
* message?: string|null,
* data?: array,
* timestamp?: float|null
* } $data
*/
public static function fromArray(array $data): self
{
return new self(
$data['level'],
$data['type'] ?? self::TYPE_DEFAULT,
$data['category'],
$data['message'] ?? null,
$data['data'] ?? [],
$data['timestamp'] ?? null
);
}
}
sentry/src/Event.php 0000644 00000051503 15266563341 0010470 0 ustar 00 ,
* }
* @phpstan-type SdkPackageEntry array{
* name: string,
* version: string,
* }
*/
final class Event
{
public const DEFAULT_ENVIRONMENT = 'production';
/**
* @var EventId The ID
*/
private $id;
/**
* @var float|null The date and time of when this event was generated
*/
private $timestamp;
/**
* This property is used if it's a Transaction event together with $timestamp it's the duration of the transaction.
*
* @var float|null The date and time of when this event was generated
*/
private $startTimestamp;
/**
* @var Severity|null The severity of this event
*/
private $level;
/**
* @var string|null The name of the logger which created the record
*/
private $logger;
/**
* @var string|null the name of the transaction (or culprit) which caused this exception
*/
private $transaction;
/**
* @var CheckIn|null The check in data
*/
private $checkIn;
/**
* @var Log[]
*/
private $logs = [];
/**
* @var string|null The name of the server (e.g. the host name)
*/
private $serverName;
/**
* @var string|null The release of the program
*/
private $release;
/**
* @var string|null The error message
*/
private $message;
/**
* @var string|null The formatted error message
*/
private $messageFormatted;
/**
* @var string[] The parameters to use to format the message
*/
private $messageParams = [];
/**
* @var string|null The environment where this event generated (e.g. production)
*/
private $environment;
/**
* @var array A list of relevant modules and their versions
*/
private $modules = [];
/**
* @var array The request data
*/
private $request = [];
/**
* @var array A list of tags associated to this event
*/
private $tags = [];
/**
* @var OsContext|null The server OS context data
*/
private $osContext;
/**
* @var RuntimeContext|null The runtime context data
*/
private $runtimeContext;
/**
* @var UserDataBag|null The user context data
*/
private $user;
/**
* @var array> An arbitrary mapping of additional contexts associated to this event
*/
private $contexts = [];
/**
* @var array An arbitrary mapping of additional metadata
*/
private $extra = [];
/**
* @var string[] An array of strings used to dictate the deduplication of this event
*/
private $fingerprint = [];
/**
* @var Breadcrumb[] The associated breadcrumbs
*/
private $breadcrumbs = [];
/**
* @var Span[] The array of spans if it's a transaction
*/
private $spans = [];
/**
* @var ExceptionDataBag[] The exceptions
*/
private $exceptions = [];
/**
* @var Stacktrace|null The stacktrace that generated this event
*/
private $stacktrace;
/**
* A place to stash data which is needed at some point in the SDK's
* event processing pipeline but which shouldn't get sent to Sentry.
*
* @var array
*/
private $sdkMetadata = [];
/**
* @var string The Sentry SDK identifier
*/
private $sdkIdentifier = Client::SDK_IDENTIFIER;
/**
* @var string The Sentry SDK version
*/
private $sdkVersion = Client::SDK_VERSION;
/**
* @var SdkPackageEntry[] The Sentry SDK packages
*/
private $sdkPackages = [
[
'name' => 'composer:sentry/sentry',
'version' => Client::SDK_VERSION,
],
];
/**
* @var EventType The type of the Event
*/
private $type;
/**
* @var Profile|null The profile data
*/
private $profile;
private function __construct(?EventId $eventId, EventType $eventType)
{
$this->id = $eventId ?? EventId::generate();
$this->timestamp = microtime(true);
$this->type = $eventType;
}
/**
* Creates a new event.
*
* @param EventId|null $eventId The ID of the event
*/
public static function createEvent(?EventId $eventId = null): self
{
return new self($eventId, EventType::event());
}
/**
* Creates a new transaction event.
*
* @param EventId|null $eventId The ID of the event
*/
public static function createTransaction(?EventId $eventId = null): self
{
return new self($eventId, EventType::transaction());
}
public static function createCheckIn(?EventId $eventId = null): self
{
return new self($eventId, EventType::checkIn());
}
public static function createLogs(?EventId $eventId = null): self
{
return new self($eventId, EventType::logs());
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public static function createMetrics(?EventId $eventId = null): self
{
return new self($eventId, EventType::metrics());
}
/**
* Gets the ID of this event.
*/
public function getId(): EventId
{
return $this->id;
}
/**
* Gets the identifier of the SDK package that generated this event.
*
* @internal
*/
public function getSdkIdentifier(): string
{
return $this->sdkIdentifier;
}
/**
* Sets the identifier of the SDK package that generated this event.
*
* @internal
*/
public function setSdkIdentifier(string $sdkIdentifier): self
{
$this->sdkIdentifier = $sdkIdentifier;
return $this;
}
/**
* Gets the version of the SDK package that generated this Event.
*
* @internal
*/
public function getSdkVersion(): string
{
return $this->sdkVersion;
}
/**
* Sets the version of the SDK package that generated this Event.
*
* @internal
*/
public function setSdkVersion(string $sdkVersion): self
{
$this->sdkVersion = $sdkVersion;
return $this;
}
/**
* Append a package to the list of SDK packages.
*
* @param SdkPackageEntry $package The package to append
*
* @return $this
*
* @internal
*/
public function appendSdkPackage(array $package): self
{
$this->sdkPackages[] = $package;
return $this;
}
/**
* Gets the SDK playload that will be sent to Sentry.
*
* @see https://develop.sentry.dev/sdk/data-model/event-payloads/sdk/
*
* @return array{name: string, version: string, packages: SdkPackageEntry[]}
*
* @internal
*/
public function getSdkPayload(): array
{
return [
'name' => $this->sdkIdentifier,
'version' => $this->sdkVersion,
'packages' => $this->sdkPackages,
];
}
/**
* Gets the timestamp of when this event was generated.
*/
public function getTimestamp(): ?float
{
return $this->timestamp;
}
/**
* Sets the timestamp of when the Event was created.
*/
public function setTimestamp(?float $timestamp): self
{
$this->timestamp = $timestamp;
return $this;
}
/**
* Gets the severity of this event.
*/
public function getLevel(): ?Severity
{
return $this->level;
}
/**
* Sets the severity of this event.
*
* @param Severity|null $level The severity
*/
public function setLevel(?Severity $level): self
{
$this->level = $level;
return $this;
}
/**
* Gets the name of the logger which created the event.
*/
public function getLogger(): ?string
{
return $this->logger;
}
/**
* Sets the name of the logger which created the event.
*
* @param string|null $logger The logger name
*/
public function setLogger(?string $logger): self
{
$this->logger = $logger;
return $this;
}
/**
* Gets the name of the transaction (or culprit) which caused this
* exception.
*/
public function getTransaction(): ?string
{
return $this->transaction;
}
/**
* Sets the name of the transaction (or culprit) which caused this
* exception.
*
* @param string|null $transaction The transaction name
*/
public function setTransaction(?string $transaction): self
{
$this->transaction = $transaction;
return $this;
}
public function getCheckIn(): ?CheckIn
{
return $this->checkIn;
}
public function setCheckIn(?CheckIn $checkIn): self
{
$this->checkIn = $checkIn;
return $this;
}
/**
* @return Log[]
*/
public function getLogs(): array
{
return $this->logs;
}
/**
* @param Log[] $logs
*/
public function setLogs(array $logs): self
{
$this->logs = $logs;
return $this;
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function getMetrics(): array
{
return [];
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function setMetrics(array $metrics): self
{
return $this;
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function getMetricsSummary(): array
{
return [];
}
/**
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
*/
public function setMetricsSummary(array $metricsSummary): self
{
return $this;
}
/**
* Gets the name of the server.
*/
public function getServerName(): ?string
{
return $this->serverName;
}
/**
* Sets the name of the server.
*
* @param string|null $serverName The server name
*/
public function setServerName(?string $serverName): self
{
$this->serverName = $serverName;
return $this;
}
/**
* Gets the release of the program.
*/
public function getRelease(): ?string
{
return $this->release;
}
/**
* Sets the release of the program.
*
* @param string|null $release The release
*/
public function setRelease(?string $release): self
{
$this->release = $release;
return $this;
}
/**
* Gets the error message.
*/
public function getMessage(): ?string
{
return $this->message;
}
/**
* Gets the formatted message.
*/
public function getMessageFormatted(): ?string
{
return $this->messageFormatted;
}
/**
* Gets the parameters to use to format the message.
*
* @return string[]
*/
public function getMessageParams(): array
{
return $this->messageParams;
}
/**
* Sets the error message.
*
* @param string $message The message
* @param string[] $params The parameters to use to format the message
* @param string|null $formatted The formatted message
*/
public function setMessage(string $message, array $params = [], ?string $formatted = null): self
{
$this->message = $message;
$this->messageParams = $params;
$this->messageFormatted = $formatted;
return $this;
}
/**
* Gets a list of relevant modules and their versions.
*
* @return array
*/
public function getModules(): array
{
return $this->modules;
}
/**
* Sets a list of relevant modules and their versions.
*
* @param array $modules
*/
public function setModules(array $modules): self
{
$this->modules = $modules;
return $this;
}
/**
* Gets the request data.
*
* @return array
*/
public function getRequest(): array
{
return $this->request;
}
/**
* Sets the request data.
*
* @param array $request The request data
*/
public function setRequest(array $request): self
{
$this->request = $request;
return $this;
}
/**
* Gets an arbitrary mapping of additional contexts.
*
* @return array>
*/
public function getContexts(): array
{
return $this->contexts;
}
/**
* Sets data to the context by a given name.
*
* @param string $name The name that uniquely identifies the context
* @param array $data The data of the context
*/
public function setContext(string $name, array $data): self
{
if (!empty($data)) {
$this->contexts[$name] = $data;
}
return $this;
}
/**
* Gets an arbitrary mapping of additional metadata.
*
* @return array
*/
public function getExtra(): array
{
return $this->extra;
}
/**
* Sets an arbitrary mapping of additional metadata.
*
* @param array $extra The context object
*/
public function setExtra(array $extra): self
{
$this->extra = $extra;
return $this;
}
/**
* Gets a list of tags associated to this event.
*
* @return array
*/
public function getTags(): array
{
return $this->tags;
}
/**
* Sets a list of tags associated to this event.
*
* @param array $tags The tags to set
*/
public function setTags(array $tags): self
{
$this->tags = $tags;
return $this;
}
/**
* Sets or updates a tag in this event.
*
* @param string $key The key that uniquely identifies the tag
* @param string $value The value
*/
public function setTag(string $key, string $value): self
{
$this->tags[$key] = $value;
return $this;
}
/**
* Removes a given tag from the event.
*
* @param string $key The key that uniquely identifies the tag
*/
public function removeTag(string $key): self
{
unset($this->tags[$key]);
return $this;
}
/**
* Gets the user context.
*/
public function getUser(): ?UserDataBag
{
return $this->user;
}
/**
* Sets the user context.
*
* @param UserDataBag|null $user The context object
*/
public function setUser(?UserDataBag $user): self
{
$this->user = $user;
return $this;
}
/**
* Gets the server OS context.
*/
public function getOsContext(): ?OsContext
{
return $this->osContext;
}
/**
* Sets the server OS context.
*
* @param OsContext|null $osContext The context object
*/
public function setOsContext(?OsContext $osContext): self
{
$this->osContext = $osContext;
return $this;
}
/**
* Gets the runtime context data.
*/
public function getRuntimeContext(): ?RuntimeContext
{
return $this->runtimeContext;
}
/**
* Sets the runtime context data.
*
* @param RuntimeContext|null $runtimeContext The context object
*/
public function setRuntimeContext(?RuntimeContext $runtimeContext): self
{
$this->runtimeContext = $runtimeContext;
return $this;
}
/**
* Gets an array of strings used to dictate the deduplication of this
* event.
*
* @return string[]
*/
public function getFingerprint(): array
{
return $this->fingerprint;
}
/**
* Sets an array of strings used to dictate the deduplication of this
* event.
*
* @param string[] $fingerprint The strings
*/
public function setFingerprint(array $fingerprint): self
{
$this->fingerprint = $fingerprint;
return $this;
}
/**
* Gets the environment in which this event was generated.
*/
public function getEnvironment(): ?string
{
return $this->environment;
}
/**
* Sets the environment in which this event was generated.
*
* @param string|null $environment The name of the environment
*/
public function setEnvironment(?string $environment): self
{
$this->environment = $environment;
return $this;
}
/**
* Gets the breadcrumbs.
*
* @return Breadcrumb[]
*/
public function getBreadcrumbs(): array
{
return $this->breadcrumbs;
}
/**
* Set new breadcrumbs to the event.
*
* @param Breadcrumb[] $breadcrumbs The breadcrumb array
*/
public function setBreadcrumb(array $breadcrumbs): self
{
$this->breadcrumbs = $breadcrumbs;
return $this;
}
/**
* Gets the exception.
*
* @return ExceptionDataBag[]
*/
public function getExceptions(): array
{
return $this->exceptions;
}
/**
* Sets the exceptions.
*
* @param ExceptionDataBag[] $exceptions The exceptions
*/
public function setExceptions(array $exceptions): self
{
foreach ($exceptions as $exception) {
if (!$exception instanceof ExceptionDataBag) {
throw new \UnexpectedValueException(\sprintf('Expected an instance of the "%s" class. Got: "%s".', ExceptionDataBag::class, get_debug_type($exception)));
}
}
$this->exceptions = $exceptions;
return $this;
}
/**
* Gets the stacktrace that generated this event.
*/
public function getStacktrace(): ?Stacktrace
{
return $this->stacktrace;
}
/**
* Sets the stacktrace that generated this event.
*
* @param Stacktrace|null $stacktrace The stacktrace instance
*/
public function setStacktrace(?Stacktrace $stacktrace): self
{
$this->stacktrace = $stacktrace;
return $this;
}
public function getType(): EventType
{
return $this->type;
}
/**
* Sets the SDK metadata with the given name.
*
* @param string $name The name that uniquely identifies the SDK metadata
* @param mixed $data The data of the SDK metadata
*/
public function setSdkMetadata(string $name, $data): self
{
$this->sdkMetadata[$name] = $data;
return $this;
}
/**
* Gets the SDK metadata.
*
* @psalm-template T of string|null
*
* @psalm-param T $name
*
* @return mixed
*
* @psalm-return (T is string ? mixed : array|null)
*/
public function getSdkMetadata(?string $name = null)
{
if ($name !== null) {
return $this->sdkMetadata[$name] ?? null;
}
return $this->sdkMetadata;
}
/**
* Gets a timestamp representing when the measuring of a transaction started.
*/
public function getStartTimestamp(): ?float
{
return $this->startTimestamp;
}
/**
* Sets a timestamp representing when the measuring of a transaction started.
*
* @param float|null $startTimestamp The start time of the measurement
*/
public function setStartTimestamp(?float $startTimestamp): self
{
$this->startTimestamp = $startTimestamp;
return $this;
}
/**
* A list of timed application events that have a start and end time.
*
* @return Span[]
*/
public function getSpans(): array
{
return $this->spans;
}
/**
* Sets a list of timed application events that have a start and end time.
*
* @param Span[] $spans The list of spans
*/
public function setSpans(array $spans): self
{
$this->spans = $spans;
return $this;
}
public function getProfile(): ?Profile
{
return $this->profile;
}
public function setProfile(?Profile $profile): self
{
$this->profile = $profile;
return $this;
}
public function getTraceId(): ?string
{
$traceId = $this->getContexts()['trace']['trace_id'];
if (\is_string($traceId) && !empty($traceId)) {
return $traceId;
}
return null;
}
}
sentry/src/UserDataBag.php 0000644 00000014663 15266563341 0011537 0 ustar 00 Additional data
*/
private $metadata = [];
/**
* UserDataBag constructor.
*
* @param string|int|null $id
*/
public function __construct(
$id = null,
?string $email = null,
?string $ipAddress = null,
?string $username = null,
?string $segment = null
) {
$this->setId($id);
$this->setEmail($email);
$this->setIpAddress($ipAddress);
$this->setUsername($username);
$this->setSegment($segment);
}
/**
* Creates an instance of this object from a user ID.
*
* @param string|int $id The ID of the user
*/
public static function createFromUserIdentifier($id): self
{
return new self($id);
}
/**
* Creates an instance of this object from an IP address.
*
* @param string $ipAddress The IP address of the user
*/
public static function createFromUserIpAddress(string $ipAddress): self
{
return new self(null, null, $ipAddress);
}
/**
* Creates an instance of this object from the given data.
*
* @param array $data The raw data
*/
public static function createFromArray(array $data): self
{
$instance = new self();
foreach ($data as $field => $value) {
switch ($field) {
case 'id':
$instance->setId($value);
break;
case 'ip_address':
$instance->setIpAddress($value);
break;
case 'email':
$instance->setEmail($value);
break;
case 'username':
$instance->setUsername($value);
break;
case 'segment':
$instance->setSegment($value);
break;
default:
$instance->setMetadata($field, $value);
break;
}
}
return $instance;
}
/**
* Gets the ID of the user.
*
* @return string|int|null
*/
public function getId()
{
return $this->id;
}
/**
* Sets the ID of the user.
*
* @param string|int|null $id The ID
*/
public function setId($id): self
{
if ($id !== null && !\is_string($id) && !\is_int($id)) {
throw new \UnexpectedValueException(\sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id)));
}
$this->id = $id;
return $this;
}
/**
* Gets the username of the user.
*/
public function getUsername(): ?string
{
return $this->username;
}
/**
* Sets the username of the user.
*
* @param string|null $username The username
*/
public function setUsername(?string $username): self
{
$this->username = $username;
return $this;
}
/**
* Gets the email of the user.
*/
public function getEmail(): ?string
{
return $this->email;
}
/**
* Sets the email of the user.
*
* @param string|null $email The email
*/
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
/**
* Gets the segement of the user.
*
* @deprecated since version 4.4. To be removed in version 5.0
*/
public function getSegment(): ?string
{
return $this->segment;
}
/**
* Sets the segment of the user.
*
* @param string|null $segment The segment
*
* @deprecated since version 4.4. To be removed in version 5.0. You may use a custom tag or context instead.
*/
public function setSegment(?string $segment): self
{
$this->segment = $segment;
return $this;
}
/**
* Gets the ip address of the user.
*/
public function getIpAddress(): ?string
{
return $this->ipAddress;
}
/**
* Sets the ip address of the user.
*
* @param string|null $ipAddress The ip address
*/
public function setIpAddress(?string $ipAddress): self
{
if ($ipAddress !== null && filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) {
throw new \InvalidArgumentException(\sprintf('The "%s" value is not a valid IP address.', $ipAddress));
}
$this->ipAddress = $ipAddress;
return $this;
}
/**
* Gets additional metadata.
*
* @return array
*/
public function getMetadata(): array
{
return $this->metadata;
}
/**
* Sets the given field in the additional metadata.
*
* @param string $name The name of the field
* @param mixed $value The value
*/
public function setMetadata(string $name, $value): self
{
$this->metadata[$name] = $value;
return $this;
}
/**
* Removes the given field from the additional metadata.
*
* @param string $name The name of the field
*/
public function removeMetadata(string $name): self
{
unset($this->metadata[$name]);
return $this;
}
/**
* Merges the given context with this one.
*
* @param UserDataBag $other The context to merge the data with
*
* @return $this
*/
public function merge(self $other): self
{
$this->id = $other->id;
$this->email = $other->email;
$this->ipAddress = $other->ipAddress;
$this->username = $other->username;
$this->segment = $other->segment;
$this->metadata = array_merge($this->metadata, $other->metadata);
return $this;
}
}
sentry/src/Util/Str.php 0000644 00000002323 15266563341 0011070 0 ustar 00 $values
*/
public static function vsprintfOrNull(string $message, array $values): ?string
{
if (empty($values)) {
return $message;
}
foreach ($values as $value) {
// If the value is not a scalar or null, we cannot safely format it
if (!\is_scalar($value) && $value !== null) {
return null;
}
}
try {
$result = @vsprintf($message, $values);
// @phpstan-ignore-next-line on PHP 7 `vsprintf` does not throw an exception but can return `false`
return $result === false ? null : $result;
} catch (\Error $e) { // This is technically a `ValueError` in PHP 8.0+ but this works in PHP 7 as well
return null;
}
}
}
sentry/src/Util/PHPVersion.php 0000644 00000001536 15266563341 0012322 0 ustar 00 \d\.\d\.\d{1,2})(?-(beta|rc)-?(\d+)?(-dev)?)?/i';
/**
* Parses the given string representing a PHP version and returns it in a
* normalized form.
*
* @param string $version The string to parse
*/
public static function parseVersion(string $version = \PHP_VERSION): string
{
if (!preg_match(self::VERSION_PARSING_REGEX, $version, $matches)) {
return $version;
}
$version = $matches['base'];
if (isset($matches['extra'])) {
$version .= $matches['extra'];
}
return $version;
}
}
sentry/src/Util/PrefixStripper.php 0000644 00000001162 15266563341 0013306 0 ustar 00 getPrefixes() as $prefix) {
if (mb_substr($filePath, 0, mb_strlen($prefix)) === $prefix) {
return mb_substr($filePath, mb_strlen($prefix));
}
}
return $filePath;
}
}
sentry/src/Util/Http.php 0000644 00000002366 15266563341 0011246 0 ustar 00 getPublicKey(),
];
return [
'Content-Type: application/x-sentry-envelope',
'X-Sentry-Auth: Sentry ' . implode(', ', $authHeader),
];
}
/**
* @param string[][] $headers
*
* @param-out string[][] $headers
*/
public static function parseResponseHeaders(string $headerLine, array &$headers): int
{
if (strpos($headerLine, ':') === false) {
return \strlen($headerLine);
}
[$name, $value] = explode(':', trim($headerLine), 2);
$name = trim($name);
$value = trim($value);
if (isset($headers[$name])) {
$headers[$name][] = $value;
} else {
$headers[$name] = (array) $value;
}
return \strlen($headerLine);
}
}
sentry/src/Util/ClockMock.php 0000644 00000011630 15266563341 0012166 0 ustar 00
* @author Dominic Tubach
*/
class ClockMock
{
private static $now;
public static function withClockMock($enable = null): ?bool
{
if ($enable === null) {
return self::$now !== null;
}
self::$now = is_numeric($enable) ? (float) $enable : ($enable ? microtime(true) : null);
return null;
}
public static function time(): int
{
if (self::$now === null) {
return time();
}
return (int) self::$now;
}
public static function sleep($s): int
{
if (self::$now === null) {
return sleep($s);
}
self::$now += (int) $s;
return 0;
}
public static function usleep($us): void
{
if (self::$now === null) {
usleep($us);
} else {
self::$now += $us / 1000000;
}
}
/**
* @return string|float
*/
public static function microtime($asFloat = false)
{
if (self::$now === null) {
return microtime($asFloat);
}
if ($asFloat) {
return self::$now;
}
return \sprintf('%0.6f00 %d', self::$now - (int) self::$now, (int) self::$now);
}
public static function date($format, $timestamp = null): string
{
if ($timestamp === null) {
$timestamp = self::time();
}
return date($format, $timestamp);
}
public static function gmdate($format, $timestamp = null): string
{
if ($timestamp === null) {
$timestamp = self::time();
}
return gmdate($format, $timestamp);
}
/**
* @return array|int|float
*/
public static function hrtime($asNumber = false)
{
$ns = (self::$now - (int) self::$now) * 1000000000;
if ($asNumber) {
$number = \sprintf('%d%d', (int) self::$now, $ns);
return \PHP_INT_SIZE === 8 ? (int) $number : (float) $number;
}
return [(int) self::$now, (int) $ns];
}
/**
* @return false|int
*/
public static function strtotime(string $datetime, ?int $timestamp = null)
{
if ($timestamp === null) {
$timestamp = self::time();
}
return strtotime($datetime, $timestamp);
}
public static function register($class): void
{
$self = static::class;
$mockedNs = [substr($class, 0, strrpos($class, '\\'))];
if (strpos($class, '\\Tests\\') > 0) {
$ns = str_replace('\\Tests\\', '\\', $class);
$mockedNs[] = substr($ns, 0, strrpos($ns, '\\'));
} elseif (strpos($class, 'Tests\\') === 0) {
$mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6);
}
foreach ($mockedNs as $ns) {
if (\function_exists($ns . '\time')) {
continue;
}
eval(<<