Skip to content

Commit 57e3994

Browse files
authored
Merge pull request #15 from OnrampLab/14-feat-add-datadoghandler
add datadoghandler
2 parents 86fca1b + 1a5ded4 commit 57e3994

File tree

3 files changed

+258
-0
lines changed

3 files changed

+258
-0
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,41 @@ return [
6868

6969
```
7070

71+
### DatadogHandler
72+
73+
You can adding following block into `config/logging.php`.
74+
75+
```php
76+
use Monolog\Formatter\JsonFormatter;
77+
use Onramplab\LaravelLogEnhancement\Handlers\DatadogHandler;
78+
79+
return [
80+
//...
81+
82+
'channels' => [
83+
//...
84+
85+
'datadog' => [
86+
'driver' => 'monolog',
87+
'level' => 'info',
88+
'handler' => DatadogHandler::class,
89+
'handler_with' => [
90+
'key' => env('DD_LOG_API_KEY'),
91+
'region' => env('DD_LOG_REGION', 'us5'),
92+
'attributes' => [
93+
'hostname' => gethostname(),
94+
'source' => env('DD_LOG_SOURCE', 'laravel'),
95+
'service' => env('DD_LOG_SERVICE'),
96+
'tags' => env('DD_LOG_TAG'),
97+
],
98+
],
99+
'formatter' => JsonFormatter::class,
100+
],
101+
]
102+
];
103+
104+
```
105+
71106
## Testing
72107

73108
Run the tests with:

src/Handlers/DatadogHandler.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
namespace Onramplab\LaravelLogEnhancement\Handlers;
4+
5+
use Exception;
6+
use Monolog\Formatter\JsonFormatter;
7+
use Monolog\Handler\AbstractProcessingHandler;
8+
use Monolog\Handler\Curl\Util;
9+
use Monolog\Handler\MissingExtensionException;
10+
use Monolog\Logger;
11+
12+
class DatadogHandler extends AbstractProcessingHandler
13+
{
14+
protected const BASE_HOST = 'datadoghq.com';
15+
16+
/**
17+
* Datadog Api Key access
18+
*/
19+
private string $key;
20+
21+
/**
22+
* Datadog Api Host
23+
*/
24+
private string $apiHost;
25+
26+
/**
27+
* Datadog optionals attributes
28+
*/
29+
private array $attributes;
30+
31+
/**
32+
* @param string $key Datadog Api Key access
33+
* @param string $region Datadog Region
34+
* @param array $attributes Some options fore Datadog Logs
35+
* @param string|int $level The minimum logging level at which this handler will be triggered
36+
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
37+
*/
38+
public function __construct(
39+
string $key,
40+
string $region = null,
41+
array $attributes = [],
42+
$level = Logger::DEBUG,
43+
bool $bubble = true
44+
) {
45+
if (!extension_loaded('curl')) {
46+
throw new MissingExtensionException('The curl extension is needed to use the DatadogHandler');
47+
}
48+
49+
parent::__construct($level, $bubble);
50+
51+
$this->key = $this->getApiKey($key);
52+
$this->apiHost = $this->getApiHost($region);
53+
$this->attributes = $attributes;
54+
}
55+
56+
/**
57+
* Handles a log record
58+
*/
59+
protected function write(array $record): void
60+
{
61+
$this->send($record['formatted']);
62+
}
63+
64+
/**
65+
* Send request to @link https://docs.datadoghq.com/api/latest/logs/?code-lang=curl#send-logs
66+
* @param string $record
67+
*/
68+
protected function send($record): void
69+
{
70+
$parameters = [
71+
'ddsource' => $this->getSource(),
72+
'hostname' => $this->getHostname(),
73+
'service' => $this->getService($record),
74+
'ddtags' => $this->getTags()
75+
];
76+
$queryString = http_build_query($parameters);
77+
$url = sprintf('https://http-intake.logs.%s/api/v2/logs?%s', $this->apiHost, $queryString);
78+
79+
$headers = [
80+
'DD-API-KEY: ' . $this->key,
81+
'Accept: application/json',
82+
'Content-Type: application/json',
83+
];
84+
85+
$ch = curl_init();
86+
87+
curl_setopt($ch, CURLOPT_URL, $url);
88+
curl_setopt($ch, CURLOPT_POST, true);
89+
curl_setopt($ch, CURLOPT_POSTFIELDS, $record);
90+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
91+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
92+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
93+
94+
Util::execute($ch);
95+
}
96+
97+
/**
98+
* Get Datadog Api Key.
99+
* @param string $key
100+
*
101+
* @return string
102+
*/
103+
protected function getApiKey($key)
104+
{
105+
if ($key) {
106+
return $key;
107+
} else {
108+
throw new Exception('The Datadog Api Key is required');
109+
}
110+
}
111+
112+
/**
113+
* Get Datadog Region host.
114+
* @param ?string $region
115+
*
116+
* @return string
117+
*/
118+
protected function getApiHost($region)
119+
{
120+
return $region ? sprintf("%s.%s", $region, self::BASE_HOST) : self::BASE_HOST;
121+
}
122+
/**
123+
* Get Datadog Source from $attributes params.
124+
*
125+
* @return string
126+
*/
127+
protected function getSource()
128+
{
129+
return !empty($this->attributes['source']) ? $this->attributes['source'] : 'php';
130+
}
131+
132+
/**
133+
* Get Datadog Service from $attributes params.
134+
*
135+
* @return string
136+
*/
137+
protected function getService($record)
138+
{
139+
$channel = json_decode($record, true);
140+
141+
return !empty($this->attributes['service']) ? $this->attributes['service'] : $channel['channel'];
142+
}
143+
144+
/**
145+
* Get Datadog Hostname from $attributes params.
146+
*
147+
* @return string
148+
*/
149+
protected function getHostname()
150+
{
151+
return !empty($this->attributes['hostname']) ? $this->attributes['hostname'] : $_SERVER['SERVER_NAME'];
152+
}
153+
154+
/**
155+
* Get Datadog Tags from $attributes params.
156+
*
157+
* @return string
158+
*/
159+
protected function getTags()
160+
{
161+
return !empty($this->attributes['tags']) ? $this->attributes['tags'] : '';
162+
}
163+
164+
/**
165+
* Returns the default formatter to use with this handler
166+
*
167+
* @return JsonFormatter
168+
*/
169+
protected function getDefaultFormatter(): JsonFormatter
170+
{
171+
return new JsonFormatter();
172+
}
173+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Onramplab\LaravelLogEnhancement\Tests\Unit\Handlers;
4+
5+
use Onramplab\LaravelLogEnhancement\Tests\TestCase;
6+
use Onramplab\LaravelLogEnhancement\Handlers\DatadogHandler;
7+
use ReflectionClass;
8+
use ReflectionProperty;
9+
10+
class DatadogHandlerTest extends TestCase
11+
{
12+
/**
13+
* @test
14+
*/
15+
public function construct_should_init()
16+
{
17+
$handler = new DatadogHandler(
18+
'my-api-key',
19+
'us5',
20+
[
21+
'source' => 'laravel',
22+
'service' => 'my-service',
23+
'hostname' => 'my-host',
24+
'tags' => 'env:local',
25+
]
26+
);
27+
28+
$properties = $this->getPrivateProperties($handler);
29+
30+
$this->assertEquals('my-api-key', $properties['key']);
31+
$this->assertEquals('us5.datadoghq.com', $properties['apiHost']);
32+
$this->assertEquals('laravel', $properties['attributes']['source']);
33+
$this->assertEquals('my-service', $properties['attributes']['service']);
34+
$this->assertEquals('my-host', $properties['attributes']['hostname']);
35+
$this->assertEquals('env:local', $properties['attributes']['tags']);
36+
}
37+
38+
private function getPrivateProperties($object)
39+
{
40+
$reflection = new ReflectionClass($object);
41+
$properties = [];
42+
43+
foreach ($reflection->getProperties(ReflectionProperty::IS_PRIVATE) as $property) {
44+
$property->setAccessible(true);
45+
$properties[$property->getName()] = $property->getValue($object);
46+
}
47+
48+
return $properties;
49+
}
50+
}

0 commit comments

Comments
 (0)