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

Added Airbrake Adapter #18

Open
wants to merge 3 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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ $adapter = new LogOwl("[YOUR_SERVICE_TICKET]");
$logger = new Logger($adapter);
$logger->addLog($log);

// Airbrake
$adapter = new Airbrake("[YOUR_PROJECT_ID];[YOUR_PROJECT_KEY]");
$logger = new Logger($adapter);
$logger->addLog($log);
```

### Adapters
Expand All @@ -85,6 +89,7 @@ Below is a list of supported adapters, and thier compatibly tested versions alon
| AppSignal | ✅ |
| Raygun | ✅ |
| Log Owl | ✅ |
| Airbrake | ✅ |

` ✅ - supported, 🛠 - work in progress`

Expand Down
162 changes: 162 additions & 0 deletions src/Logger/Adapter/Airbrake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php

namespace Utopia\Logger\Adapter;

use Exception;
use Utopia\Logger\Adapter;
use Utopia\Logger\Log;


/**
* Reference Docs:
* https://docs.airbrake.io/docs/devops-tools/api/#create-notice-v3
*
* https://docs.airbrake.io/docs/devops-tools/api/#post-data-fields-v3
*/
class Airbrake extends Adapter
{
/**
* Base URL for airbrake api endpoint
*/
private string $airbrakeAPIHost;

/**
* API endpoint for pushing error data into airbrake (creating notice)
*/
private string $airbrakeCreateNoticeURL;

/**
* Required to access airbrake API
*
* Ref: https://docs.airbrake.io/docs/devops-tools/api/
*/
protected string $projectKey;

/**
* Project unique ID
*
* Ref: https://docs.airbrake.io/docs/devops-tools/api/
*/
protected string $projectId;

/**
* Airbrake constructor
*
* @param string $configKey Contains projectId and projectKey
*/
public function __construct(string $configKey)
{
$this->airbrakeAPIHost = 'https://api.airbrake.io';

$configChunks = \explode(';', $configKey);
$this->projectId = $configChunks[0];
$this->projectKey = $configChunks[1];

$this->airbrakeCreateNoticeURL = $this->airbrakeAPIHost . "/api/v3/projects/" . $this->projectId . "/notices?key=" . $this->projectKey;
}

/**
* Unique adapter name
*
* @return string
*/
public static function getName(): string
{
return "airbrake";
}

/**
* Push log to airbrake
*
* @param Log $log
* @return int Response status code
* @throws Exception When status code is >= 400
*/
public function push(Log $log): int
{
$breadcrumbObjects = $log->getBreadcrumbs();
$userLog = $log->getUser();
$user = [
"id" => $userLog->getId(),
"name" => $userLog->getUsername(),
"email" => $userLog->getEmail()
];
$context = [
"hostname" => $log->getServer(),
"environment" => $log->getEnvironment(),
"severity" => $log->getType(),
"version" => $log->getVersion(),
"user" => $user,
"action" => $log->getAction()
];
$errors = [];

foreach ($breadcrumbObjects as $breadcrumbObject) {
$error = [
"type" => $breadcrumbObject->getType(),
"message" => $breadcrumbObject->getMessage()
];

\array_push($errors, $error);
}
Comment on lines +94 to +101
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding the breadcrumbs to errors, I think it should come from $log->getExtra()['detailedTrace']. See

if (isset($log->getExtra()['detailedTrace'])) {
foreach ($log->getExtra()['detailedTrace'] as $trace) {
\array_push($stackFrames, [
'filename' => $trace['file'],
'lineno' => $trace['line'],
'function' => $trace['function'],
]);
}
}

Copy link
Author

Choose a reason for hiding this comment

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

Hey @stnguyen90 , sorry for being inactive here and responding so late.
Looking at your comment and the airbrake docs, I think the detailedTrace should go in error/{i}/backtrace as per the doc

Copy link
Author

Choose a reason for hiding this comment

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

I am suggesting this change:

Suggested change
foreach ($breadcrumbObjects as $breadcrumbObject) {
$error = [
"type" => $breadcrumbObject->getType(),
"message" => $breadcrumbObject->getMessage()
];
\array_push($errors, $error);
}
foreach ($breadcrumbObjects as $breadcrumbObject) {
$backtraces = [];
foreach ($detailedTraces as $detailedTrace) {
$backtrace = [
"file" => $detailedTrace['file'],
"line" => $detailedTrace['line'],
"function" => $detailedTrace['function']
];
\array_push($backtraces, $backtrace);
}
$error = [
"type" => $breadcrumbObject->getType(),
"message" => $breadcrumbObject->getMessage(),
"backtrace" => $backtraces
];
\array_push($errors, $error);
}


$requestBody = [
"errors" => $errors,
"context" => $context,
'lastNoticeAt' => $log->getTimestamp()
];

$curlHandler = \curl_init();

$options = array(
CURLOPT_URL => $this->airbrakeCreateNoticeURL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => \json_encode($requestBody),
CURLOPT_HEADEROPT => \CURLHEADER_UNIFIED,
CURLOPT_HTTPHEADER => array('Content-Type: application/json')
);

\curl_setopt_array($curlHandler, $options);

$result = curl_exec($curlHandler);

$response = curl_getinfo($curlHandler, \CURLINFO_HTTP_CODE);

if ($result && $response >= 400) {
throw new Exception("Log could not be pushed with status code " . $response . ": " . \curl_error($curlHandler));
}

\curl_close($curlHandler);

return $response;
}

public function getSupportedTypes(): array
{
return [
Log::TYPE_DEBUG,
Log::TYPE_INFO,
Log::TYPE_WARNING,
Log::TYPE_ERROR,
];
}

public function getSupportedEnvironments(): array
{
return [
Log::ENVIRONMENT_STAGING,
Log::ENVIRONMENT_PRODUCTION,
];
}

public function getSupportedBreadcrumbTypes(): array
{
return [
Log::TYPE_INFO,
Log::TYPE_DEBUG,
Log::TYPE_WARNING,
Log::TYPE_ERROR
];
}
}
3 changes: 2 additions & 1 deletion src/Logger/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class Logger
"raygun",
"sentry",
"appSignal",
"logOwl"
"logOwl",
"airbrake"
];

/**
Expand Down
8 changes: 7 additions & 1 deletion tests/LoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

use PHPUnit\Framework\TestCase;

use Utopia\Logger\Adapter\Airbrake;
use Utopia\Logger\Adapter\AppSignal;
use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
Expand Down Expand Up @@ -190,5 +190,11 @@ public function testAdapters()
$logger = new Logger($adapter);
$response = $logger->addLog($log);
$this->assertEquals(200, $response);

// Test Airbrake
$adapter = new Airbrake(\getenv("TEST_AIRBRAKE_ID") . ';' . \getenv("TEST_AIRBRAKE_KEY"));
$logger = new Logger($adapter);
$response = $logger->addLog($log);
$this->assertEquals(201, $response);
}
}