Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/sentry/publish/sentry.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
'enable_logs' => env('SENTRY_ENABLE_LOGS', true),

// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#log_flush_threshold
// Reaching the threshold triggers an automatic flush, and the telemetry
// flush listener also periodically flushes as a fallback. Memory usage
// grows linearly with the threshold, so keep it <= 5000.
Comment on lines +47 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize the telemetry guidance across Sentry docs

This adds user-facing behavior and threshold guidance to the published configuration, but both component READMEs and all four localized Sentry pages still omit log_flush_threshold, the periodic fallback, and its memory recommendation. Update those six sources so users who follow the package documentation receive the same telemetry behavior and configuration guidance.

AGENTS.md reference: AGENTS.md:L129-L130

Useful? React with 👍 / 👎.

'log_flush_threshold' => env('SENTRY_LOG_FLUSH_THRESHOLD') === null ? null : (int) env('SENTRY_LOG_FLUSH_THRESHOLD'),

// @see: https://docs.sentry.io/platforms/php/configuration/options/#before_send_log
Expand Down
1 change: 1 addition & 0 deletions src/sentry/src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public function __invoke(): array
Metrics\Listener\OnCoroutineServerStart::class,
Metrics\Listener\OnMetricFactoryReady::class,
Metrics\Listener\OnWorkerStart::class,
Metrics\Listener\TelemetryFlushListener::class,
Metrics\Listener\QueueWatcher::class,
Metrics\Listener\RedisPoolWatcher::class,
Metrics\Listener\RequestWatcher::class,
Expand Down
5 changes: 5 additions & 0 deletions src/sentry/src/Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public function isMetricsEnabled(bool $default = true): bool
return (bool) $this->config->get('sentry.enable_metrics', $default);
}

public function isLogsEnabled(bool $default = true): bool
{
return (bool) $this->config->get('sentry.enable_logs', $default);
}

public function isDefaultMetricsEnabled(bool $default = true): bool
{
if (! $this->isMetricsEnabled()) {
Expand Down
79 changes: 79 additions & 0 deletions src/sentry/src/Metrics/Listener/TelemetryFlushListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact huangdijia@gmail.com
*/

namespace FriendsOfHyperf\Sentry\Metrics\Listener;

use FriendsOfHyperf\Sentry\Feature;
use FriendsOfHyperf\Sentry\Metrics\Event\MetricFactoryReady;
use Hyperf\Coordinator\Timer;
use Hyperf\Event\Contract\ListenerInterface;
use Psr\Container\ContainerInterface;
use Sentry\Logs\Logs;
use Sentry\Metrics\TraceMetrics;
use Sentry\SentrySdk;
use Throwable;

class TelemetryFlushListener implements ListenerInterface
{
private Timer $timer;

private bool $ticking = false;

public function __construct(
protected ContainerInterface $container,
protected Feature $feature,
?Timer $timer = null,
) {
$this->timer = $timer ?? new Timer();
}

public function listen(): array
{
return [
MetricFactoryReady::class,
];
Comment on lines +40 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Start the flush timer independently of metrics readiness

With the published defaults (enable_logs=true, enable_metrics=false), this listener is never invoked: every production dispatch of MetricFactoryReady is gated by isMetricsEnabled() or isCommandMetricsEnabled(). Moreover, OnWorkerStart dispatches it only for worker 0, so the other worker processes receive no timer even when metrics are enabled. Consequently, the new fallback does not flush global log buffers in the default configuration or across all workers, leaving the reported log-loss/unbounded-memory scenario unresolved; start it from a lifecycle event that runs in every relevant process or arrange an unconditional per-worker dispatch.

Useful? React with 👍 / 👎.

}

/**
* @param object|MetricFactoryReady $event
*/
public function process(object $event): void
{
if ($this->ticking) {
return;
}

if (! $this->feature->isMetricsEnabled() && ! $this->feature->isLogsEnabled()) {
return;
}

$this->ticking = true;

$this->timer->tick(
$this->feature->getMetricsInterval(),
function (): void {
// End this tick coroutine's own runtime context (if any), so the
// following flush operations target the global context aggregators.
SentrySdk::endContext();

try {
Logs::getInstance()->flush();
} catch (Throwable) {
}

try {
TraceMetrics::getInstance()->flush();
} catch (Throwable) {
}
}
);
}
}
87 changes: 87 additions & 0 deletions tests/Sentry/Metrics/Listener/TelemetryFlushListenerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact huangdijia@gmail.com
*/

namespace FriendsOfHyperf\Tests\Sentry\Metrics\Listener;

use FriendsOfHyperf\Sentry\Feature;
use FriendsOfHyperf\Sentry\Metrics\Event\MetricFactoryReady;
use FriendsOfHyperf\Sentry\Metrics\Listener\TelemetryFlushListener;
use Hyperf\Coordinator\Constants;
use Hyperf\Coordinator\Timer;
use Mockery as m;
use Psr\Container\ContainerInterface;

class FakeTimer extends Timer
{
public int $tickCount = 0;

/**
* @var array<int, callable>
*/
public array $closures = [];

public function tick(float $timeout, callable $closure, string $identifier = Constants::WORKER_EXIT): int
{
++$this->tickCount;
$this->closures[$this->tickCount] = $closure;

return $this->tickCount;
}
}

beforeEach(function () {
$this->feature = m::mock(Feature::class);
$this->feature->shouldReceive('getMetricsInterval')->andReturn(10);

$this->container = m::mock(ContainerInterface::class);
$this->timer = new FakeTimer();
});

afterEach(function () {
m::close();
});

test('process schedules a single tick for repeated calls', function () {
$this->feature->shouldReceive('isMetricsEnabled')->andReturn(true);
$this->feature->shouldReceive('isLogsEnabled')->andReturn(true);

$listener = new TelemetryFlushListener($this->container, $this->feature, $this->timer);

$listener->process(new MetricFactoryReady());
$listener->process(new MetricFactoryReady());

expect($this->timer->tickCount)->toBe(1);
});

test('saved tick closure can be invoked without throwing', function () {
$this->feature->shouldReceive('isMetricsEnabled')->andReturn(true);
$this->feature->shouldReceive('isLogsEnabled')->andReturn(true);

$listener = new TelemetryFlushListener($this->container, $this->feature, $this->timer);
$listener->process(new MetricFactoryReady());

$closure = $this->timer->closures[1];

$closure(false);

expect(true)->toBeTrue();
});

test('no tick is scheduled when logs and metrics are disabled', function () {
$this->feature->shouldReceive('isMetricsEnabled')->andReturn(false);
$this->feature->shouldReceive('isLogsEnabled')->andReturn(false);

$listener = new TelemetryFlushListener($this->container, $this->feature, $this->timer);

$listener->process(new MetricFactoryReady());

expect($this->timer->tickCount)->toBe(0);
});
Loading