Skip to content
Merged
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
20 changes: 20 additions & 0 deletions agent/src/Console/Log.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ class Log
{
private const LEVEL_INFO = 'INFO';
private const LEVEL_ERROR = 'ERROR';
private const LEVEL_DEBUG = 'DEBUG';

/**
* @var bool Whether to output debug messages or not
*/
private static $verbose = false;

private static function output(string $level, string $message): void
{
if ($level === self::LEVEL_DEBUG && !self::$verbose) {
return;
}

echo \sprintf("sentry-agent [%-19s] [%-5s] %s\n", date('Y-m-d H:i:s'), $level, $message);
}

Expand All @@ -26,4 +36,14 @@ public static function error(string $message): void
{
self::output(self::LEVEL_ERROR, $message);
}

public static function debug(string $message): void
{
self::output(self::LEVEL_DEBUG, $message);
}

public static function setVerbose(bool $verbose): void
{
self::$verbose = $verbose;
}
}
126 changes: 102 additions & 24 deletions agent/src/sentry-agent.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,118 @@
use Sentry\Agent\EnvelopeQueue;
use Sentry\Agent\Server;

// @TODO: Improve the help output to be a little more useful and explain the different options
if (($argv[1] ?? '') === 'help' || ($argv[1] ?? '') === '--help' || ($argv[1] ?? '') === '-h') {
echo 'Usage: ./sentry-agent [listen_address] [listen_port] [upstream_timeout] [upstream_concurrency] [queue_limit]' . \PHP_EOL;
exit;
}
$vendorPath = __DIR__ . '/../vendor';

if (class_exists('Phar') && Phar::running(false) !== '') {
// If running the .phar directly from ./vendor/bin/, we don't want to use $_composer_autoload_path since this
// will load the projects files and lead to ClassNotFound errors.
// We want to use the autoload.php from the phar itself.
require __DIR__ . '/../vendor/autoload.php';
require_once "{$vendorPath}/autoload.php";
} else {
// This works fine for local development or if running the phar from ./vendor/sentry/sentry-agent/bin/
require $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';
require_once $_composer_autoload_path ?? "{$vendorPath}/autoload.php";
}

$sentryAgentVersion = '0.0.0';

if (file_exists("{$vendorPath}/composer/installed.php")) {
$installed = require "{$vendorPath}/composer/installed.php";

$sentryAgentVersion = $installed['root']['pretty_version'] ?? $sentryAgentVersion;
}

function printHelp(): void
{
global $sentryAgentVersion;

echo <<<HELP
Sentry Agent {$sentryAgentVersion}

Description:
A local agent that listens for Sentry SDK requests and forwards them to the destined Sentry server.

Usage:
./sentry-agent [options]

Options:
-h, --help Display this help output
--listen=ADDRESS The address the agent listens for connections on [default: "127.0.0.1:5148"]
--upstream-timeout=SECONDS The timeout for the connection to Sentry (in seconds) [default: "2.0"]
--upstream-concurrency=REQUESTS Configures the amount of concurrent requests the agent is allowed to make towards Sentry [default: "10"]
--queue-limit=ENVELOPES How many envelopes we want to keep in memory before we start dropping them [default: "1000"]
-v, --verbose When supplied the agent will print debug messages to the console, otherwise only errors and info messages are printed

HELP;
}

$options = getopt('h', ['listen::', 'upstream-timeout::', 'upstream-concurrency::', 'queue-limit::', 'help']);

if ($options === false) {
Log::error('Failed to parse command line options.');

exit(1);
}

$getOption = static function (string $key, $default = null) use ($options) {
if (!isset($options[$key])) {
return $default;
}

// If the option is provided multiple times, we take the first value.
$value = is_array($options[$key])
? $options[$key][0]
: $options[$key];

// Options without a value are returned as false by getopt. We treat them as boolean flags and return true instead.
return $value === false
? true
: $value;
};

$firstArgument = $argv[1] ?? '-';

if ($firstArgument === 'help' || $firstArgument[0] !== '-' || $getOption('h') || $getOption('help')) {
printHelp();

// Showed help, exit.
exit(0);
}

Log::setVerbose($getOption('v') || $getOption('verbose'));

$listenAddress = $getOption('listen', '127.0.0.1:5148');

$upstreamTimeout = (float) $getOption('upstream-timeout', 2.0);

if ($upstreamTimeout <= 0) {
Log::error('The upstream timeout must be a positive number.');

exit(1);
}

$upstreamConcurrency = (int) $getOption('upstream-concurrency', 10);

if ($upstreamConcurrency <= 0) {
Log::error('The upstream concurrency must be a positive integer.');

exit(1);
}

// @TODO: "sentryagent" with a 5 in front, it's a unused "user port": https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=5148
// Maybe there is a better way to select a port to use but for now this is fine.
$listenAddress = ($argv[1] ?? '127.0.0.1') . ':' . ($argv[2] ?? 5148);
$queueLimit = (int) $getOption('queue-limit', 1000);

if ($queueLimit <= 0) {
Log::error('The queue limit must be a positive integer and at least 1.');

// Configures the timeout for the connection to Sentry (in seconds), since we are running in an agent
// we can afford a little longer timeout then we would normally do in a regular PHP context
$upstreamTimeout = (float) ($argv[3] ?? 2.0);
exit(1);
}

// Configures the amount of concurrent requests the agent is allowed to make towards Sentry
$upstreamConcurrency = (int) ($argv[4] ?? 10);
if ($queueLimit < $upstreamConcurrency) {
Log::error('The queue limit must be at least equal to the upstream concurrency.');

// How many envelopes we want to keep in memory before we start dropping them
$queueLimit = (int) ($argv[5] ?? 1000);
exit(1);
}

Log::info("=> Starting Sentry agent, listening on {$listenAddress} with config:");
Log::info(" > upstream timeout: {$upstreamTimeout}");
Log::info(" > upstream concurrency: {$upstreamConcurrency}");
Log::info(" > queue limit: {$queueLimit}");
Log::info("Starting Sentry Agent ({$sentryAgentVersion}), listening on {$listenAddress} (timeout:{$upstreamTimeout}, concurrency:{$upstreamConcurrency}, queue:{$queueLimit})");

$forwarder = new EnvelopeForwarder(
$upstreamTimeout,
Expand All @@ -57,9 +135,9 @@ function (Psr\Http\Message\ResponseInterface $response) {
$eventId = '<unknown>';
}

Log::info("Envelope sent successfully (ID: {$eventId}, http status: {$response->getStatusCode()}).");
Log::debug("Envelope sent successfully (ID: {$eventId}, http status: {$response->getStatusCode()}).");
} else {
Log::info("Envelope sent successfully (http status: {$response->getStatusCode()}).");
Log::debug("Envelope sent successfully (http status: {$response->getStatusCode()}).");
}
} else {
Log::error("Envelope send error: {$response->getStatusCode()} {$response->getReasonPhrase()}");
Expand Down Expand Up @@ -97,7 +175,7 @@ function (Throwable $exception) {
Log::error("Incoming connection error: {$exception->getMessage()}");
},
function (Envelope $envelope) use ($queue) {
Log::info('Envelope received, queueing forward to Sentry...');
Log::debug('Envelope received, queueing forward to Sentry...');

$queue->enqueue($envelope);
}
Expand Down
Binary file modified bin/sentry-agent
Binary file not shown.
2 changes: 1 addition & 1 deletion bin/sentry-agent.sig
Original file line number Diff line number Diff line change
@@ -1 +1 @@
251F7E40F033195CDCE72336B54B36ECC79AFB1C485636619D7E82DB61E57F5327A49A895AB91034379396FEFAA343607073D192A775284D340D2D45EC873FA2
FFC8683782E15055AD138282CE25F5DA03544D28FEBEAEDD35908E6DE6733B368D3679EB5F13AC88AB20AD8E88B11ACE54F78D41180E33F42B541265270618C1