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
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,18 @@ vendor/bin/sentry-agent

#### Configuration

```php
vendor/bin/sentry-agent [listen_address] [listen_port] [upstream_timeout] [upstream_concurrency] [queue_limit]
```bash
vendor/bin/sentry-agent [options]
```

- `listen_address`, defaults to `127.0.0.1`
- `listen_port`, defaults to `5148`
- `upstream_timeout`, defaults to `2.0` seconds
- `upstream_concurrency`, defaults to `10`
- `queue_limit`, the amount of envelopes to keep in memory, defaults to `1000`
- `--listen=ADDRESS`, defaults to `127.0.0.1:5148`
- `--upstream-timeout=SECONDS`, defaults to `2.0` seconds
- `--upstream-concurrency=REQUESTS`, defaults to `10`
- `--queue-limit=ENVELOPES`, the amount of envelopes to keep in memory, defaults to `1000`
- `--drain-timeout=SECONDS`, defaults to `10.0` seconds
- `--control-server=ADDRESS`, enables the HTTP control server on the specified address
- `--http-proxy=URL`, forwards upstream envelope requests through an HTTP CONNECT proxy
- `--http-proxy-authentication=AUTH`, credentials for proxy basic authentication in `username:password` format

## License

Expand Down
1 change: 1 addition & 0 deletions agent/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"require": {
"php": "^7.2|^8.0",
"ext-json": "*",
"clue/http-proxy-react": "^1.9",
"clue/mq-react": "^1.6",
"react/http": "^1.11",
"react/socket": "^1.16",
Expand Down
131 changes: 130 additions & 1 deletion agent/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 37 additions & 2 deletions agent/src/EnvelopeForwarder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace Sentry\Agent;

use Clue\React\HttpProxy\ProxyConnector;
use Psr\Http\Message\ResponseInterface;
use React\Http\Browser;
use React\Http\Message\ResponseException;
use React\Promise\PromiseInterface;
use React\Socket\Connector;
use Sentry\Dsn;
use Sentry\HttpClient\Response;
use Sentry\Transport\RateLimiter;
Expand Down Expand Up @@ -39,6 +41,11 @@ class EnvelopeForwarder
*/
private $timeout;

/**
* @var Browser
*/
private $browser;

/**
* @var callable(ResponseInterface): null
*/
Expand All @@ -58,9 +65,10 @@ class EnvelopeForwarder
* @param callable(ResponseInterface): null $onEnvelopeSent called when the envelope is sent
* @param callable(\Throwable): null $onEnvelopeError called when the envelope fails to send
*/
public function __construct(float $timeout, callable $onEnvelopeSent, callable $onEnvelopeError)
public function __construct(float $timeout, callable $onEnvelopeSent, callable $onEnvelopeError, ?string $httpProxy = null, ?string $httpProxyAuthentication = null)
{
$this->timeout = $timeout;
$this->browser = $this->createBrowser($httpProxy, $httpProxyAuthentication);
$this->onEnvelopeSent = $onEnvelopeSent;
$this->onEnvelopeError = $onEnvelopeError;
}
Expand Down Expand Up @@ -109,7 +117,7 @@ public function forward(Envelope $envelope): PromiseInterface
}
}

return (new Browser())->withTimeout($this->timeout)->post(
return $this->browser->withTimeout($this->timeout)->post(
$dsn->getEnvelopeApiEndpointUrl(),
$headers,
$body
Expand All @@ -136,6 +144,33 @@ public function forward(Envelope $envelope): PromiseInterface
});
}

private function createBrowser(?string $httpProxy, ?string $httpProxyAuthentication): Browser
{
if ($httpProxy === null) {
return new Browser();
}

$headers = [];

if ($httpProxyAuthentication !== null) {
$proxyParts = parse_url(strpos($httpProxy, '://') === false ? 'http://' . $httpProxy : $httpProxy);

if (\is_array($proxyParts) && (isset($proxyParts['user']) || isset($proxyParts['pass']))) {
throw new \InvalidArgumentException('Proxy credentials must be provided either in the proxy URL or through http proxy authentication, not both.');
}

$headers['Proxy-Authorization'] = 'Basic ' . base64_encode($httpProxyAuthentication);
}

$proxy = new ProxyConnector($httpProxy, null, $headers);
$connector = new Connector([
'tcp' => $proxy,
'dns' => false,
]);

return new Browser($connector);
}

private function getRateLimiter(Dsn $dsn): RateLimiter
{
$key = $dsn->getEnvelopeApiEndpointUrl();
Expand Down
82 changes: 58 additions & 24 deletions agent/src/sentry-agent.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ function printHelp(): void
--queue-limit=ENVELOPES How many envelopes we want to keep in memory before we start dropping them [default: "1000"]
--drain-timeout=SECONDS Time to wait for the queue to drain on shutdown (in seconds) [default: "10.0"]
--control-server=ADDRESS Enable the HTTP control server on the specified address (e.g., "127.0.0.1:5149")
--http-proxy=URL The HTTP CONNECT proxy URL for forwarding envelopes upstream (e.g., "http://127.0.0.1:8080")
--http-proxy-authentication=AUTH Credentials for proxy basic authentication in "username:password" format
-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::', 'drain-timeout::', 'control-server::', 'help']);
$options = getopt('h', ['listen::', 'upstream-timeout::', 'upstream-concurrency::', 'queue-limit::', 'drain-timeout::', 'control-server::', 'http-proxy::', 'http-proxy-authentication::', 'help']);

if ($options === false) {
Log::error('Failed to parse command line options.');
Expand Down Expand Up @@ -141,37 +143,69 @@ function printHelp(): void
exit(1);
}

Log::info("Starting Sentry Agent ({$sentryAgentVersion}), listening on {$listenAddress} (timeout:{$upstreamTimeout}, concurrency:{$upstreamConcurrency}, queue:{$queueLimit})");
$httpProxy = $getOption('http-proxy');

$forwarder = new EnvelopeForwarder(
$upstreamTimeout,
static function (Psr\Http\Message\ResponseInterface $response) {
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
if ($response->getStatusCode() === 200) {
$responseBody = json_decode($response->getBody()->getContents(), true);
if ($httpProxy !== null && !is_string($httpProxy)) {
Log::error('The http proxy must be a string value.');

$eventId = is_array($responseBody) ? $responseBody['id'] ?? null : null;
exit(1);
}

if (!is_string($eventId)) {
$eventId = '<unknown>';
}
$httpProxyAuthentication = $getOption('http-proxy-authentication');

if ($httpProxyAuthentication !== null && !is_string($httpProxyAuthentication)) {
Log::error('The http proxy authentication must be a string value.');

exit(1);
}

if ($httpProxyAuthentication !== null && $httpProxy === null) {
Log::error('The http proxy authentication option requires --http-proxy.');

exit(1);
}

$proxyLogContext = $httpProxy !== null ? ', proxy:enabled' : '';

Log::debug("Envelope sent successfully (ID: {$eventId}, http status: {$response->getStatusCode()}).");
try {
$forwarder = new EnvelopeForwarder(
$upstreamTimeout,
static function (Psr\Http\Message\ResponseInterface $response) {
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
if ($response->getStatusCode() === 200) {
$responseBody = json_decode($response->getBody()->getContents(), true);

$eventId = is_array($responseBody) ? $responseBody['id'] ?? null : null;

if (!is_string($eventId)) {
$eventId = '<unknown>';
}

Log::debug("Envelope sent successfully (ID: {$eventId}, http status: {$response->getStatusCode()}).");
} else {
Log::debug("Envelope sent successfully (http status: {$response->getStatusCode()}).");
}
} else {
Log::debug("Envelope sent successfully (http status: {$response->getStatusCode()}).");
Log::error("Envelope send error: {$response->getStatusCode()} {$response->getReasonPhrase()}");
}
} else {
Log::error("Envelope send error: {$response->getStatusCode()} {$response->getReasonPhrase()}");
}

return null;
},
static function (Throwable $exception) {
Log::error("Envelope send error: {$exception->getMessage()}");
return null;
},
static function (Throwable $exception) {
Log::error("Envelope send error: {$exception->getMessage()}");

return null;
}
);
return null;
},
$httpProxy,
$httpProxyAuthentication
);
} catch (InvalidArgumentException $e) {
Log::error("Failed to configure http proxy: {$e->getMessage()}");

exit(1);
}

Log::info("Starting Sentry Agent ({$sentryAgentVersion}), listening on {$listenAddress} (timeout:{$upstreamTimeout}, concurrency:{$upstreamConcurrency}, queue:{$queueLimit}{$proxyLogContext})");

$queue = new EnvelopeQueue(
$upstreamConcurrency,
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 @@
AFBE92B982EC0B29C172B8DCC97652704441D3559372FDE7A513B66CB759E655836548F1B7A67547BAE87141E7AC3FB12E51B53A835A23B6BEE2A25E7F44F908
E379EBD144ACB3491E2C112C2C4D4A782F3C5C87AE4209E06DE33C6A65315F9D13B83473D397E8773B122C84D80F0DB0E90E0E47C5B7BD3FDCB0A045D703B922
Loading