Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
sgoettsch committed Oct 18, 2022
1 parent 59d3373 commit b9f5f51
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
composer.lock
/vendor/
/.idea
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Datadog Monolog integration

This log handler for Monolog that pushes logs to datadog.

## Requirements
- PHP 8.0+
- PHP Curl

## Installation

```shell
composer require sgoettsch/monolog-datadog
```

### Basic Usage

```php
<?php

use Monolog\Logger;
use sgoettsch\MonologDatadog\Handler\DatadogHandler;

$apiKey = 'DATADOG-API-KEY';
$host = 'https://http-intake.logs.datadoghq.com'; // could be set to other domains for example for EU hosted accounts
$attributes = [
'hostname' => 'YOUR_HOSTNAME',
'source' => 'php',
'service' => 'YOUR-SERVICE'
];

$logger = new Logger('datadog-channel');

$datadogLogs = new DatadogHandler($apiKey, $host, $attributes, Logger::INFO);

$logger->pushHandler($datadogLogs);

$logger->info('i am an info');
$logger->warning('i am a warning..');
$logger->error('i am an error ');
$logger->notice('i am a notice');
$logger->emergency('i am an emergency');
```
25 changes: 25 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "sgoettsch/monolog-datadog",
"description": "Monolog Handler to forward logs to Datadog",
"type": "library",
"license": "MIT",
"keywords": [
"monolog"
],
"homepage": "https://github.com/sgoettsch/monolog-datadog",
"authors": [
{
"name": "Sebastian Goettsch"
}
],
"require": {
"php": "^8.0",
"ext-curl": "*",
"monolog/monolog": ">=1.0 <3.0"
},
"autoload": {
"psr-4": {
"sgoettsch\\MonologDatadog\\": "src"
}
}
}
43 changes: 43 additions & 0 deletions src/Monolog/Formatter/DatadogFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace sgoettsch\MonologDatadog\Formatter;

use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
use stdClass;

class DatadogFormatter extends JsonFormatter
{
protected $includeStacktraces = true;

/**
* Map Monolog\Logger levels to Datadog's default status type
*/
private const DATADOG_LEVEL_MAP = [
Logger::DEBUG => 'info',
Logger::INFO => 'info',
Logger::NOTICE => 'warning',
Logger::WARNING => 'warning',
Logger::ERROR => 'error',
Logger::ALERT => 'error',
Logger::CRITICAL => 'error',
Logger::EMERGENCY => 'error',
];

public function format(array $record): string
{
$normalized = $this->normalize($record);

if (isset($normalized['context']) && $normalized['context'] === []) {
$normalized['context'] = new stdClass;
}

if (isset($normalized['extra']) && $normalized['extra'] === []) {
$normalized['extra'] = new stdClass;
}

$normalized['status'] = static::DATADOG_LEVEL_MAP[$record['level']];

return $this->toJson($normalized, true);
}
}
159 changes: 159 additions & 0 deletions src/Monolog/Handler/DatadogHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

namespace sgoettsch\MonologDatadog\Handler;

use Monolog\Handler\MissingExtensionException;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\Curl\Util;
use Monolog\Formatter\FormatterInterface;
use sgoettsch\MonologDatadog\Formatter\DatadogFormatter;

class DatadogHandler extends AbstractProcessingHandler
{
/** @var string Datadog API host */
private string $host;

/** @var string Datadog API-Key */
private string $apiKey;

/** @var array Datadog optional attributes */
private array $attributes;

/**
* @param string $apiKey Datadog API-Key
* @param string $host Datadog API host
* @param array $attributes Datadog optional attributes
* @param int|string $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @throws MissingExtensionException
*/
public function __construct(
string $apiKey,
string $host = 'https://http-intake.logs.datadoghq.com',
array $attributes = [],
int|string $level = Logger::DEBUG,
bool $bubble = true
) {
if (!extension_loaded('curl')) {
throw new MissingExtensionException('The curl extension is needed to use the DatadogHandler');
}

parent::__construct($level, $bubble);

$this->apiKey = $apiKey;
$this->host = $host;
$this->attributes = $attributes;
}

/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
protected function write(array $record): void
{
$this->send($record);
}

/**
* Send request to Datadog
*
* @param array $record
*/
protected function send(array $record): void
{
$headers = ['Content-Type:application/json'];

$source = $this->getSource();
$hostname = $this->getHostname();
$service = $this->getService($record);
$tags = $this->getTags($record);

$url = $this->host . '/v1/input/';
$url .= $this->apiKey;
/** @noinspection SpellCheckingInspection */
$url .= '?ddsource=' . $source . '&service=' . $service . '&hostname=' . $hostname . '&ddtags=' . $tags;

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $record['formatted']);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

Util::execute($ch);
}

/**
* Get Datadog Source from $attributes params.
*
* @return string
*/
protected function getSource(): string
{
return $this->attributes['source'] ?? 'php';
}

/**
* Get Datadog Service from $attributes params.
*
* @param array $record
*
* @return string
*/
protected function getService(array $record): string
{
return $this->attributes['service'] ?? $record['channel'];
}

/**
* Get Datadog Hostname from $attributes params.
*
* @return string
*/
protected function getHostname(): string
{
return $this->attributes['hostname'] ?? $_SERVER['SERVER_NAME'];
}

/**
* Get Datadog Tags from $attributes params.
*
* @param array $record
*
* @return string
*/
protected function getTags(array $record): string
{
$defaultTag = 'level:' . $record['level_name'];

if (!isset($this->attributes['tags']) || !$this->attributes['tags']) {
return $defaultTag;
}

if (
(is_array($this->attributes['tags']) || is_object($this->attributes['tags']))
&& !empty($this->attributes['tags'])
) {
$imploded = implode(',', (array)$this->attributes['tags']);

return $imploded . ',' . $defaultTag;
}

return $defaultTag;
}

/**
* Returns the default formatter to use with this handler
*
* @return DatadogFormatter
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new DatadogFormatter();
}
}

0 comments on commit b9f5f51

Please sign in to comment.