Skip to content

Commit cbec306

Browse files
spawniastayallive
andauthored
Improve storage integration (#750)
Co-authored-by: Alex Bouma <[email protected]>
1 parent f24f251 commit cbec306

File tree

10 files changed

+436
-235
lines changed

10 files changed

+436
-235
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- The filesystem adapters for the `sentry` driver now extend the well-known Laravel classes they decorate,
8+
`Illuminate\Filesystem\FilesystemAdapter` and `Illuminate\Filesystem\AwsS3V3Adapter`.
9+
10+
Enabling the feature can be simplified by wrapping the configuration for all disks
11+
with a call to `Sentry\Laravel\Features\Storage\Integration::configureDisks()`
12+
in your `config/filesystems.php` file:
13+
14+
```php
15+
'disks' => Sentry\Laravel\Features\Storage\Integration::configureDisks([
16+
'local' => [
17+
'driver' => 'local',
18+
'root' => storage_path('app'),
19+
'throw' => false,
20+
],
21+
22+
// ...
23+
], /* enableSpans: */ true, /* enableBreadcrumbs: */ true),
24+
```
25+
26+
Alternatively, you can enable this feature only for select disks:
27+
28+
```php
29+
'disks' => [
30+
'local' => [
31+
'driver' => 'local',
32+
'root' => storage_path('app'),
33+
'throw' => false,
34+
],
35+
36+
's3' => Sentry\Laravel\Features\Storage\Integration::configureDisk('s3', [
37+
// ...
38+
], /* enableSpans: */ true, /* enableBreadcrumbs: */ true),
39+
],
40+
```
41+
42+
By default, both spans and breadcrumbs are enabled.
43+
You may disable them by passing the second argument `$enableSpans` or the third argument `$enableBreadcrumbs`.
44+
345
## 3.7.3
446

547
The Sentry SDK team is happy to announce the immediate availability of Sentry Laravel SDK v3.7.3.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Features\Storage;
4+
5+
trait CloudFilesystemDecorator
6+
{
7+
use FilesystemDecorator;
8+
9+
public function url($path)
10+
{
11+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
12+
}
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Features\Storage;
4+
5+
trait FilesystemAdapterDecorator
6+
{
7+
use CloudFilesystemDecorator;
8+
9+
public function assertExists($path, $content = null)
10+
{
11+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
12+
}
13+
14+
public function assertMissing($path)
15+
{
16+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
17+
}
18+
19+
public function assertDirectoryEmpty($path)
20+
{
21+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
22+
}
23+
24+
public function temporaryUrl($path, $expiration, array $options = [])
25+
{
26+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path', 'expiration', 'options'));
27+
}
28+
29+
public function temporaryUploadUrl($path, $expiration, array $options = [])
30+
{
31+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path', 'expiration', 'options'));
32+
}
33+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Features\Storage;
4+
5+
use Illuminate\Contracts\Filesystem\Filesystem;
6+
use Sentry\Breadcrumb;
7+
use Sentry\Laravel\Integration;
8+
use Sentry\Laravel\Util\Filesize;
9+
use Sentry\Tracing\SpanContext;
10+
use function Sentry\trace;
11+
12+
/**
13+
* Decorates the underlying filesystem by wrapping all calls to it with tracing.
14+
*
15+
* Parameters such as paths, directories or options are attached to the span as data,
16+
* parameters that contain file contents are omitted due to potential problems with
17+
* payload size or sensitive data.
18+
*/
19+
trait FilesystemDecorator
20+
{
21+
/** @var Filesystem */
22+
protected $filesystem;
23+
24+
/** @var array */
25+
protected $defaultData;
26+
27+
/** @var bool */
28+
protected $recordSpans;
29+
30+
/** @var bool */
31+
protected $recordBreadcrumbs;
32+
33+
/**
34+
* Execute the method on the underlying filesystem and wrap it with tracing and log a breadcrumb.
35+
*
36+
* @param list<mixed> $args
37+
* @param array<string, mixed> $data
38+
*
39+
* @return mixed
40+
*/
41+
protected function withSentry(string $method, array $args, ?string $description, array $data)
42+
{
43+
$op = "file.{$method}"; // See https://develop.sentry.dev/sdk/performance/span-operations/#web-server
44+
$data = array_merge($data, $this->defaultData);
45+
46+
if ($this->recordBreadcrumbs) {
47+
Integration::addBreadcrumb(new Breadcrumb(
48+
Breadcrumb::LEVEL_INFO,
49+
Breadcrumb::TYPE_DEFAULT,
50+
$op,
51+
$description,
52+
$data
53+
));
54+
}
55+
56+
if ($this->recordSpans) {
57+
$spanContext = new SpanContext;
58+
$spanContext->setOp($op);
59+
$spanContext->setData($data);
60+
$spanContext->setDescription($description);
61+
62+
return trace(function () use ($method, $args) {
63+
return $this->filesystem->{$method}(...$args);
64+
}, $spanContext);
65+
}
66+
67+
return $this->filesystem->{$method}(...$args);
68+
}
69+
70+
public function exists($path)
71+
{
72+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
73+
}
74+
75+
public function get($path)
76+
{
77+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
78+
}
79+
80+
public function readStream($path)
81+
{
82+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
83+
}
84+
85+
public function put($path, $contents, $options = [])
86+
{
87+
$description = is_string($contents) ? sprintf('%s (%s)', $path, Filesize::toHuman(strlen($contents))) : $path;
88+
89+
return $this->withSentry(__FUNCTION__, func_get_args(), $description, compact('path', 'options'));
90+
}
91+
92+
public function writeStream($path, $resource, array $options = [])
93+
{
94+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path', 'options'));
95+
}
96+
97+
public function getVisibility($path)
98+
{
99+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
100+
}
101+
102+
public function setVisibility($path, $visibility)
103+
{
104+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path', 'visibility'));
105+
}
106+
107+
public function prepend($path, $data, $separator = PHP_EOL)
108+
{
109+
$description = is_string($data) ? sprintf('%s (%s)', $path, Filesize::toHuman(strlen($data))) : $path;
110+
111+
return $this->withSentry(__FUNCTION__, func_get_args(), $description, compact('path'));
112+
}
113+
114+
public function append($path, $data, $separator = PHP_EOL)
115+
{
116+
$description = is_string($data) ? sprintf('%s (%s)', $path, Filesize::toHuman(strlen($data))) : $path;
117+
118+
return $this->withSentry(__FUNCTION__, func_get_args(), $description, compact('path'));
119+
}
120+
121+
public function delete($paths)
122+
{
123+
if (is_array($paths)) {
124+
$data = compact('paths');
125+
$description = sprintf('%s paths', count($paths));
126+
} else {
127+
$data = ['path' => $paths];
128+
$description = $paths;
129+
}
130+
131+
return $this->withSentry(__FUNCTION__, func_get_args(), $description, $data);
132+
}
133+
134+
public function copy($from, $to)
135+
{
136+
return $this->withSentry(__FUNCTION__, func_get_args(), sprintf('from "%s" to "%s"', $from, $to), compact('from', 'to'));
137+
}
138+
139+
public function move($from, $to)
140+
{
141+
return $this->withSentry(__FUNCTION__, func_get_args(), sprintf('from "%s" to "%s"', $from, $to), compact('from', 'to'));
142+
}
143+
144+
public function size($path)
145+
{
146+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
147+
}
148+
149+
public function lastModified($path)
150+
{
151+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
152+
}
153+
154+
public function files($directory = null, $recursive = false)
155+
{
156+
return $this->withSentry(__FUNCTION__, func_get_args(), $directory, compact('directory', 'recursive'));
157+
}
158+
159+
public function allFiles($directory = null)
160+
{
161+
return $this->withSentry(__FUNCTION__, func_get_args(), $directory, compact('directory'));
162+
}
163+
164+
public function directories($directory = null, $recursive = false)
165+
{
166+
return $this->withSentry(__FUNCTION__, func_get_args(), $directory, compact('directory', 'recursive'));
167+
}
168+
169+
public function allDirectories($directory = null)
170+
{
171+
return $this->withSentry(__FUNCTION__, func_get_args(), $directory, compact('directory'));
172+
}
173+
174+
public function makeDirectory($path)
175+
{
176+
return $this->withSentry(__FUNCTION__, func_get_args(), $path, compact('path'));
177+
}
178+
179+
public function deleteDirectory($directory)
180+
{
181+
return $this->withSentry(__FUNCTION__, func_get_args(), $directory, compact('directory'));
182+
}
183+
184+
public function __call($name, $arguments)
185+
{
186+
return $this->filesystem->{$name}(...$arguments);
187+
}
188+
}

src/Sentry/Laravel/Features/Storage/Integration.php

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use Illuminate\Contracts\Filesystem\Cloud as CloudFilesystem;
66
use Illuminate\Contracts\Filesystem\Filesystem;
77
use Illuminate\Contracts\Foundation\Application;
8+
use Illuminate\Filesystem\AwsS3V3Adapter;
9+
use Illuminate\Filesystem\FilesystemAdapter;
810
use Illuminate\Filesystem\FilesystemManager;
911
use RuntimeException;
1012
use Sentry\Laravel\Features\Feature;
@@ -22,11 +24,6 @@ public function isApplicable(): bool
2224
}
2325

2426
public function register(): void
25-
{
26-
$this->registerDiskDriver();
27-
}
28-
29-
private function registerDiskDriver(): void
3027
{
3128
$this->container()->afterResolving(FilesystemManager::class, function (FilesystemManager $filesystemManager): void {
3229
$filesystemManager->extend(
@@ -57,18 +54,81 @@ function (Application $application, array $config) use ($filesystemManager): Fil
5754
return $this->resolve($disk);
5855
})->bindTo($filesystemManager, FilesystemManager::class);
5956

57+
/** @var Filesystem $originalFilesystem */
6058
$originalFilesystem = $diskResolver($disk, $config);
6159

6260
$defaultData = ['disk' => $disk, 'driver' => $config['driver']];
6361

6462
$recordSpans = $config['sentry_enable_spans'] ?? $this->isTracingFeatureEnabled(self::FEATURE_KEY);
6563
$recordBreadcrumbs = $config['sentry_enable_breadcrumbs'] ?? $this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY);
6664

67-
return $originalFilesystem instanceof CloudFilesystem
68-
? new SentryCloudFilesystem($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs)
69-
: new SentryFilesystem($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs);
65+
if ($originalFilesystem instanceof AwsS3V3Adapter) {
66+
return new SentryS3V3Adapter($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs);
67+
}
68+
69+
if ($originalFilesystem instanceof FilesystemAdapter) {
70+
return new SentryFilesystemAdapter($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs);
71+
}
72+
73+
if ($originalFilesystem instanceof CloudFilesystem) {
74+
return new SentryCloudFilesystem($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs);
75+
}
76+
77+
return new SentryFilesystem($originalFilesystem, $defaultData, $recordSpans, $recordBreadcrumbs);
7078
}
7179
);
7280
});
7381
}
82+
83+
/**
84+
* Decorates the configuration for a single disk with Sentry driver configuration.
85+
86+
* This replaces the driver with a custom driver that will capture performance traces and breadcrumbs.
87+
*
88+
* The custom driver will be an instance of @see \Sentry\Laravel\Features\Storage\SentryS3V3Adapter
89+
* if the original driver is an @see \Illuminate\Filesystem\AwsS3V3Adapter,
90+
* and an instance of @see \Sentry\Laravel\Features\Storage\SentryFilesystemAdapter
91+
* if the original driver is an @see \Illuminate\Filesystem\FilesystemAdapter.
92+
* If the original driver is neither of those, it will be @see \Sentry\Laravel\Features\Storage\SentryFilesystem
93+
* or @see \Sentry\Laravel\Features\Storage\SentryCloudFilesystem based on the contract of the original driver.
94+
*
95+
* You might run into problems if you expect another specific driver class.
96+
*
97+
* @param array<string, mixed> $diskConfig
98+
*
99+
* @return array<string, mixed>
100+
*/
101+
public static function configureDisk(string $diskName, array $diskConfig, bool $enableSpans = true, bool $enableBreadcrumbs = true): array
102+
{
103+
$currentDriver = $diskConfig['driver'];
104+
105+
if ($currentDriver !== self::STORAGE_DRIVER_NAME) {
106+
$diskConfig['driver'] = self::STORAGE_DRIVER_NAME;
107+
$diskConfig['sentry_disk_name'] = $diskName;
108+
$diskConfig['sentry_original_driver'] = $currentDriver;
109+
$diskConfig['sentry_enable_spans'] = $enableSpans;
110+
$diskConfig['sentry_enable_breadcrumbs'] = $enableBreadcrumbs;
111+
}
112+
113+
return $diskConfig;
114+
}
115+
116+
/**
117+
* Decorates the configuration for all disks with Sentry driver configuration.
118+
*
119+
* @see self::configureDisk()
120+
*
121+
* @param array<string, array<string, mixed>> $diskConfigs
122+
*
123+
* @return array<string, array<string, mixed>>
124+
*/
125+
public static function configureDisks(array $diskConfigs, bool $enableSpans = true, bool $enableBreadcrumbs = true): array
126+
{
127+
$diskConfigsWithSentryDriver = [];
128+
foreach ($diskConfigs as $diskName => $diskConfig) {
129+
$diskConfigsWithSentryDriver[$diskName] = static::configureDisk($diskName, $diskConfig, $enableSpans, $enableBreadcrumbs);
130+
}
131+
132+
return $diskConfigsWithSentryDriver;
133+
}
74134
}

0 commit comments

Comments
 (0)