Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(php): reimagine fire and forget and development mode #1181

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
10 changes: 8 additions & 2 deletions packages/php/examples/laravel/config/readme.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

/**
* Since ReadMe doesn't want to take your API down if you happen to be
* unable to send metrics to us, development mode is enabled by default and
* will do two things:
* unable to send metrics to us, fire and forget mode is enabled by default
* and will do two things:
*
* - Make all API requests asynchronously.
* - Silence all possible errors in transit.
Expand All @@ -26,6 +26,12 @@
* enabling this option so you can see any possible error that may be
* present in your payload before you deploy to production.
*/
'fire_and_forget' => true,

/**
* When true, the log will be marked as a development log. This is great for
* separating staging or test data from data coming from customers.
*/
'development_mode' => false,

/**
Expand Down
23 changes: 16 additions & 7 deletions packages/php/src/Metrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Metrics
protected const README_API = 'https://dash.readme.com';

private bool $development_mode = false;
private bool $fire_and_forget = true;
private array $denylist = [];
private array $allowlist = [];
private string|null $base_log_url = null;
Expand All @@ -44,6 +45,9 @@ public function __construct(public string $api_key, public string $group_handler
$this->development_mode = array_key_exists('development_mode', $options)
? (bool)$options['development_mode']
: false;
$this->fire_and_forget = array_key_exists('fire_and_forget', $options)
? (bool)$options['fire_and_forget']
: true;

if (isset($options['denylist']) && is_array($options['denylist'])) {
$this->denylist = $options['denylist'];
Expand All @@ -61,11 +65,11 @@ public function __construct(public string $api_key, public string $group_handler
$this->base_log_url = $options['base_log_url'];
}

// In development mode, requests are sent asynchronously (as well as PHP can without directly invoking
// In fire_and_forget mode, requests are sent asynchronously (as well as PHP can without directly invoking
// shell cURL commands), so a very small timeout here ensures that the Metrics code will finish as fast as
// possible, send the POST request to the background and continue on with whatever else the application
// needs to execute.
$curl_timeout = (!$this->development_mode) ? 0.2 : 0;
$curl_timeout = (!$this->fire_and_forget) ? 0.2 : 0;

$this->curl_handler = new CurlMultiHandler();
$this->client = (isset($options['client'])) ? $options['client'] : new Client([
Expand Down Expand Up @@ -113,8 +117,8 @@ public function track(Request $request, Response &$response): void
'User-Agent' => $this->user_agent
];

// If not in development mode, all requests should be async.
if (!$this->development_mode) {
// If not in fire_and_forget mode, all requests should be async.
if ($this->fire_and_forget) {
try {
$promise = $this->client->postAsync('/v1/request', [
'headers' => $headers,
Expand Down Expand Up @@ -200,11 +204,11 @@ private function getProjectBaseUrl(): ?string
$cache->base_url = $json->baseUrl;
$cache->last_updated = time();
} catch (\Exception $e) {
// If we're running in development mode, toss any errors that happen when we try to call the ReadMe API.
//
// If we're running in fire_and_forget mode, toss any errors that happen when we try to call the
// ReadMe API.
// These errors will likely be from invalid API keys, so it'll be good to surface those to users before
// it hits production.
if ($this->development_mode) {
if (!$this->fire_and_forget) {
try {
// If we don't have a ClientException here, throw again so we end up below and handle this
// exception as a non-HTTP response problem.
Expand Down Expand Up @@ -278,6 +282,11 @@ public function isInDevelopmentMode(): bool
return $this->development_mode;
}

public function isInFireAndForgetMode(): bool
{
return $this->fire_and_forget;
}

public function getDenylist(): array
{
return $this->denylist;
Expand Down
1 change: 1 addition & 0 deletions packages/php/src/Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public function __construct()
'blacklist' => config('readme.blacklist', []),
'whitelist' => config('readme.whitelist', []),
'base_log_url' => config('readme.base_log_url'),
'fire_and_forget' => config('readme.fire_and_forget', true),
]
);
}
Expand Down
13 changes: 10 additions & 3 deletions packages/php/src/config.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

/**
* Since ReadMe doesn't want to take your API down if you happen to be
* unable to send metrics to us, development mode is enabled by default and
* will do two things:
* unable to send metrics to us, fire and forget mode is enabled by default
* and will do two things:
*
* - Make all API requests asynchronously.
* - Silence all possible errors in transit.
Expand All @@ -26,7 +26,14 @@
* enabling this option so you can see any possible error that may be
* present in your payload before you deploy to production.
*/
'development_mode' => true,
'fire_and_forget' => true,


/**
* When true, the log will be marked as a development log. This is great for
* separating staging or test data from data coming from customers.
*/
'development_mode' => false,

/**
* An array of keys from your API requests and responses that you wish to
Expand Down
3 changes: 2 additions & 1 deletion packages/php/tests/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public function testConfigFileHasExpectedData(): void
$this->assertSame([
'api_key',
'group_handler',
'fire_and_forget',
'development_mode',
'denylist',
'allowlist',
'base_log_url'
'base_log_url',
], array_keys($config));
}
}
36 changes: 22 additions & 14 deletions packages/php/tests/MetricsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ public function testTrack(bool $development_mode): void

/**
* @group track
* @dataProvider providerDevelopmentModeToggle
* @dataProvider providerFireAndForgetModeToggle
*/
public function testTrackHandlesApiErrors(bool $development_mode): void
public function testTrackHandlesApiErrors(bool $fire_and_forget): void
{
if ($development_mode) {
if (!$fire_and_forget) {
$this->expectException(MetricsException::class);
// `RequestModel` from the API response should get swapped out with `ValidationError`.
$this->expectExceptionMessage('ValidationError: queryString.0.value: Path `value` is required.');
Expand All @@ -173,7 +173,7 @@ public function testTrackHandlesApiErrors(bool $development_mode): void
);

$this->metrics = new Metrics($this->readme_api_key, $this->group_handler, [
'development_mode' => $development_mode,
'fire_and_forget' => $fire_and_forget,
'client' => new Client(['handler' => $handlers->metrics]),
'client_readme' => new Client(['handler' => $handlers->readme])
]);
Expand All @@ -183,7 +183,7 @@ public function testTrackHandlesApiErrors(bool $development_mode): void

$this->metrics->track($request, $response);

if (!$development_mode) {
if ($fire_and_forget) {
$this->assertTrue(
true,
'When not in development mode, exceptions should not have been thrown so this assertion should pass.'
Expand All @@ -193,12 +193,12 @@ public function testTrackHandlesApiErrors(bool $development_mode): void

/**
* @group track
* @dataProvider providerDevelopmentModeToggle
* @dataProvider providerFireAndForgetModeToggle
*/
public function testTrackHandlesApiServerUnavailability(bool $development_mode): void
public function testTrackHandlesApiServerUnavailability(bool $fire_and_forget): void
{
// Exceptions **should** be thrown under development mode!
if ($development_mode) {
if (!$fire_and_forget) {
$this->expectException(ServerException::class);
$this->expectExceptionMessageMatches('/500 Internal Server Error/');
}
Expand All @@ -209,7 +209,7 @@ public function testTrackHandlesApiServerUnavailability(bool $development_mode):
);

$this->metrics = new Metrics($this->readme_api_key, $this->group_handler, [
'development_mode' => $development_mode,
'fire_and_forget' => $fire_and_forget,
'client' => new Client(['handler' => $handlers->metrics]),
'client_readme' => new Client(['handler' => $handlers->readme])
]);
Expand All @@ -219,7 +219,7 @@ public function testTrackHandlesApiServerUnavailability(bool $development_mode):

$this->metrics->track($request, $response);

if (!$development_mode) {
if ($fire_and_forget) {
$this->assertTrue(
true,
'When not in development mode, exceptions should not be thrown which means this assertion should pass.'
Expand Down Expand Up @@ -351,7 +351,7 @@ public function testProjectBaseUrlDataCacheIsRefreshedIfStale(bool $development_
/**
* @group getProjectBaseUrl
*/
public function testProjectBaseUrlIsTemporarilyNullIfReadMeCallFailsWhileNotInDevelopmentMode(): void
public function testProjectBaseUrlIsTemporarilyNullIfReadMeCallFailsWhileInFireAndForgetMode(): void
{
$handlers = $this->getMockHandlers(
new \GuzzleHttp\Psr7\Response(200),
Expand All @@ -367,7 +367,7 @@ public function testProjectBaseUrlIsTemporarilyNullIfReadMeCallFailsWhileNotInDe
);

$this->metrics = new Metrics($this->readme_api_key, $this->group_handler, [
'development_mode' => false,
'fire_and_forget' => true,
'client' => new Client(['handler' => $handlers->metrics]),
'client_readme' => new Client(['handler' => $handlers->readme])
]);
Expand Down Expand Up @@ -407,7 +407,7 @@ public function testProjectBaseUrlFailsInDevelopmentModeIfReadMeCallHasErrorResp
);

$this->metrics = new Metrics($this->readme_api_key, $this->group_handler, [
'development_mode' => true,
'fire_and_forget' => false,
'client' => new Client(['handler' => $handlers->metrics]),
'client_readme' => new Client(['handler' => $handlers->readme])
]);
Expand All @@ -432,7 +432,7 @@ public function testProjectBaseUrlFailsInDevelopmentModeIfItCantTalkToReadMe():
);

$this->metrics = new Metrics($this->readme_api_key, $this->group_handler, [
'development_mode' => true,
'fire_and_forget' => false,
'client' => new Client(['handler' => $handlers->metrics]),
'client_readme' => new Client(['handler' => $handlers->readme])
]);
Expand Down Expand Up @@ -511,4 +511,12 @@ public function providerDevelopmentModeToggle(): array
'development mode off' => [false],
];
}

public function providerFireAndForgetModeToggle(): array
{
return [
'fireAndForget mode on' => [true],
'fireAndForget mode off' => [false],
];
}
}