Skip to content

Commit b423e88

Browse files
authored
feat(logs): add log_flush_threshold flag to trigger auto flush (#2032)
1 parent c63aae7 commit b423e88

5 files changed

Lines changed: 139 additions & 0 deletions

File tree

src/Logs/LogsAggregator.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ public function add(
152152
}
153153

154154
$this->logs[] = $log;
155+
156+
$logFlushThreshold = $options->getLogFlushThreshold();
157+
158+
if ($logFlushThreshold !== null && \count($this->logs) >= $logFlushThreshold) {
159+
$this->flush($hub);
160+
}
155161
}
156162

157163
public function flush(?HubInterface $hub = null): ?EventId

src/Options.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,32 @@ public function getEnableLogs(): bool
184184
return $this->options['enable_logs'] ?? false;
185185
}
186186

187+
/**
188+
* Gets the number of buffered logs that trigger an immediate flush.
189+
*/
190+
public function getLogFlushThreshold(): ?int
191+
{
192+
/**
193+
* @var int|null $logFlushThreshold
194+
*/
195+
$logFlushThreshold = $this->options['log_flush_threshold'];
196+
197+
return $logFlushThreshold;
198+
}
199+
200+
/**
201+
* Sets the number of buffered logs that trigger an immediate flush.
202+
* null will never trigger an immediate flush.
203+
*/
204+
public function setLogFlushThreshold(?int $logFlushThreshold): self
205+
{
206+
$options = array_merge($this->options, ['log_flush_threshold' => $logFlushThreshold]);
207+
208+
$this->options = $this->resolver->resolve($options);
209+
210+
return $this;
211+
}
212+
187213
/**
188214
* Sets if metrics should be enabled or not.
189215
*/
@@ -1337,6 +1363,7 @@ private function configureOptions(OptionsResolver $resolver): void
13371363
'sample_rate' => 1,
13381364
'enable_tracing' => null,
13391365
'enable_logs' => false,
1366+
'log_flush_threshold' => null,
13401367
'enable_metrics' => true,
13411368
'traces_sample_rate' => null,
13421369
'traces_sampler' => null,
@@ -1414,6 +1441,7 @@ private function configureOptions(OptionsResolver $resolver): void
14141441
$resolver->setAllowedTypes('sample_rate', ['int', 'float']);
14151442
$resolver->setAllowedTypes('enable_tracing', ['null', 'bool']);
14161443
$resolver->setAllowedTypes('enable_logs', 'bool');
1444+
$resolver->setAllowedTypes('log_flush_threshold', ['null', 'int']);
14171445
$resolver->setAllowedTypes('enable_metrics', 'bool');
14181446
$resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']);
14191447
$resolver->setAllowedTypes('traces_sampler', ['null', 'callable']);
@@ -1467,6 +1495,7 @@ private function configureOptions(OptionsResolver $resolver): void
14671495
$resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions']));
14681496
$resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption']));
14691497
$resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption']));
1498+
$resolver->setAllowedValues('log_flush_threshold', \Closure::fromCallable([$this, 'validateLogFlushThresholdOption']));
14701499

14711500
$resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption']));
14721501

@@ -1632,4 +1661,14 @@ private function validateContextLinesOption(?int $contextLines): bool
16321661
{
16331662
return $contextLines === null || $contextLines >= 0;
16341663
}
1664+
1665+
/**
1666+
* Validates that the value passed to the "log_flush_threshold" option is valid.
1667+
*
1668+
* @param int|null $logFlushThreshold The value to validate
1669+
*/
1670+
private function validateLogFlushThresholdOption(?int $logFlushThreshold): bool
1671+
{
1672+
return $logFlushThreshold === null || $logFlushThreshold > 0;
1673+
}
16351674
}

src/functions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* in_app_include?: array<string>,
5050
* integrations?: IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[],
5151
* logger?: LoggerInterface|null,
52+
* log_flush_threshold?: int|null,
5253
* max_breadcrumbs?: int,
5354
* max_request_body_size?: "none"|"never"|"small"|"medium"|"always",
5455
* max_value_length?: int,

tests/Logs/LogsAggregatorTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Sentry\SentrySdk;
1313
use Sentry\State\Hub;
1414
use Sentry\State\Scope;
15+
use Sentry\Tests\StubTransport;
1516
use Sentry\Tracing\Span;
1617
use Sentry\Tracing\SpanContext;
1718
use Sentry\Tracing\SpanId;
@@ -207,4 +208,55 @@ public function testAttributesAreAddedToLogMessage(): void
207208
$this->assertSame('foo@example.com', $attributes->get('user.email')->getValue());
208209
$this->assertSame('my_user', $attributes->get('user.name')->getValue());
209210
}
211+
212+
public function testFlushesImmediatelyWhenThresholdIsReached(): void
213+
{
214+
StubTransport::$events = [];
215+
216+
$transport = new StubTransport();
217+
$client = ClientBuilder::create([
218+
'enable_logs' => true,
219+
'log_flush_threshold' => 2,
220+
])->setTransport($transport)->getClient();
221+
222+
$hub = new Hub($client);
223+
SentrySdk::setCurrentHub($hub);
224+
225+
$aggregator = new LogsAggregator();
226+
227+
$aggregator->add(LogLevel::info(), 'First message');
228+
229+
$this->assertCount(1, $aggregator->all());
230+
$this->assertCount(0, StubTransport::$events);
231+
232+
$aggregator->add(LogLevel::warn(), 'Second message');
233+
234+
$this->assertCount(0, $aggregator->all());
235+
$this->assertCount(1, StubTransport::$events);
236+
$this->assertCount(2, StubTransport::$events[0]->getLogs());
237+
$this->assertSame('First message', StubTransport::$events[0]->getLogs()[0]->getBody());
238+
$this->assertSame('Second message', StubTransport::$events[0]->getLogs()[1]->getBody());
239+
}
240+
241+
public function testDoesNotFlushImmediatelyWhenThresholdIsNull(): void
242+
{
243+
StubTransport::$events = [];
244+
245+
$transport = new StubTransport();
246+
$client = ClientBuilder::create([
247+
'enable_logs' => true,
248+
'log_flush_threshold' => null,
249+
])->setTransport($transport)->getClient();
250+
251+
$hub = new Hub($client);
252+
SentrySdk::setCurrentHub($hub);
253+
254+
$aggregator = new LogsAggregator();
255+
256+
$aggregator->add(LogLevel::info(), 'First message');
257+
$aggregator->add(LogLevel::warn(), 'Second message');
258+
259+
$this->assertCount(2, $aggregator->all());
260+
$this->assertCount(0, StubTransport::$events);
261+
}
210262
}

tests/OptionsTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ public static function optionsDataProvider(): \Generator
100100
'setEnableLogs',
101101
];
102102

103+
yield [
104+
'log_flush_threshold',
105+
10,
106+
'getLogFlushThreshold',
107+
'setLogFlushThreshold',
108+
];
109+
110+
yield [
111+
'log_flush_threshold',
112+
null,
113+
'getLogFlushThreshold',
114+
'setLogFlushThreshold',
115+
];
116+
103117
yield [
104118
'traces_sample_rate',
105119
0.5,
@@ -641,6 +655,33 @@ public static function contextLinesOptionValidatesInputValueDataProvider(): \Gen
641655
];
642656
}
643657

658+
/**
659+
* @dataProvider logFlushThresholdOptionIsValidatedCorrectlyDataProvider
660+
*/
661+
public function testLogFlushThresholdOptionIsValidatedCorrectly(bool $isValid, $value): void
662+
{
663+
if (!$isValid) {
664+
$this->expectException(InvalidOptionsException::class);
665+
}
666+
667+
$options = new Options(['log_flush_threshold' => $value]);
668+
669+
$this->assertSame($value, $options->getLogFlushThreshold());
670+
}
671+
672+
public static function logFlushThresholdOptionIsValidatedCorrectlyDataProvider(): array
673+
{
674+
return [
675+
[false, -1],
676+
[false, 0],
677+
[true, 1],
678+
[true, 10],
679+
[true, null],
680+
[false, 'string'],
681+
[false, '1'],
682+
];
683+
}
684+
644685
/**
645686
* @backupGlobals enabled
646687
*/

0 commit comments

Comments
 (0)