-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge of
ibuildingsnl
PR to address PSR-4 loading issues when using…
… monolog v2. Merge branch 'ibuildingsnl-master'
- Loading branch information
Showing
9 changed files
with
304 additions
and
467 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright [2019] New Relic Corporation. All rights reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* This file contains the abstract parent of the Formatter class for | ||
* the New Relic Monolog Enricher. This class implements all functionality | ||
* that is compatible with all Monolog API versions | ||
* | ||
* @author New Relic PHP <[email protected]> | ||
*/ | ||
|
||
namespace NewRelic\Monolog\Enricher; | ||
|
||
use Monolog\Formatter\JsonFormatter; | ||
use Monolog\Logger; | ||
|
||
/** | ||
* Formats record as a JSON object with transformations necessary for | ||
* ingestion by New Relic Logs | ||
*/ | ||
abstract class AbstractFormatter extends JsonFormatter | ||
{ | ||
/** | ||
* @param int $batchMode | ||
* @param bool $appendNewline | ||
*/ | ||
public function __construct( | ||
$batchMode = self::BATCH_MODE_NEWLINES, | ||
$appendNewline = true | ||
) { | ||
// BATCH_MODE_NEWLINES is required for batch compatibility with New | ||
// Relic log forwarder plugins, which handle batching records. When | ||
// using the New Relic Monolog handler along side a batching handler | ||
// such as the BufferHandler, BATCH_MODE_JSON is required to adhere | ||
// to the New Relic logs API bulk ingest format. | ||
parent::__construct($batchMode, $appendNewline); | ||
} | ||
|
||
|
||
/** | ||
* Moves New Relic context information from the | ||
* `$data['extra']['newrelic-context']` array to top level of record, | ||
* converts `datetime` object to `timestamp` top level element represented | ||
* as milliseconds since the UNIX epoch, and finally, normalizes the data | ||
* | ||
* @param mixed $data | ||
* @param int $depth | ||
* @return mixed | ||
*/ | ||
protected function normalize($data, $depth = 0) | ||
{ | ||
if ($depth == 0) { | ||
if (isset($data['extra']['newrelic-context'])) { | ||
$data = array_merge($data, $data['extra']['newrelic-context']); | ||
unset($data['extra']['newrelic-context']); | ||
} | ||
$data['timestamp'] = intval( | ||
$data['datetime']->format('U.u') * 1000 | ||
); | ||
} | ||
return parent::normalize($data, $depth); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright [2019] New Relic Corporation. All rights reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* This file contains the abstract parent of the Handler class for | ||
* the New Relic Monolog Enricher. This class implements all functionality | ||
* that is compatible with all Monolog API versions | ||
* | ||
* @author New Relic PHP <[email protected]> | ||
*/ | ||
|
||
namespace NewRelic\Monolog\Enricher; | ||
|
||
use Monolog\Formatter\FormatterInterface; | ||
use Monolog\Handler\Curl; | ||
use Monolog\Handler\AbstractProcessingHandler; | ||
use Monolog\Handler\HandlerInterface; | ||
use Monolog\Handler\MissingExtensionException; | ||
use Monolog\Logger; | ||
use Monolog\Util; | ||
|
||
abstract class AbstractHandler extends AbstractProcessingHandler | ||
{ | ||
protected $host = null; | ||
protected $endpoint = 'log/v1'; | ||
protected $licenseKey; | ||
protected $protocol = 'https://'; | ||
|
||
/** | ||
* @param string|int $level The minimum logging level to trigger handler | ||
* @param bool $bubble Whether messages should bubble up the stack. | ||
* | ||
* @throws MissingExtensionException If the curl extension is missing | ||
*/ | ||
public function __construct($level = Logger::DEBUG, $bubble = true) | ||
{ | ||
if (!extension_loaded('curl')) { | ||
throw new MissingExtensionException( | ||
'The curl extension is required to use this Handler' | ||
); | ||
} | ||
|
||
$this->licenseKey = ini_get('newrelic.license'); | ||
if (!$this->licenseKey) { | ||
$this->licenseKey = "NO_LICENSE_KEY_FOUND"; | ||
} | ||
|
||
parent::__construct($level, $bubble); | ||
} | ||
|
||
/** | ||
* Sets the New Relic license key. Defaults to the New Relic INI's | ||
* value for 'newrelic.license' if available. | ||
* | ||
* @param string $key | ||
*/ | ||
public function setLicenseKey($key) | ||
{ | ||
$this->licenseKey = $key; | ||
} | ||
|
||
/** | ||
* Sets the hostname of the New Relic Logging API. Defaults to the | ||
* US Prod endpoint (log-api.newrelic.com). Another useful value is | ||
* log-api.eu.newrelic.com for the EU production endpoint. | ||
* | ||
* @param string $host | ||
*/ | ||
public function setHost($host) | ||
{ | ||
$this->host = $host; | ||
} | ||
|
||
/** | ||
* Obtains a curl handler initialized to POST to the host specified by | ||
* $this->setHost() | ||
* | ||
* @return resource $ch curl handler | ||
*/ | ||
protected function getCurlHandler() | ||
{ | ||
$host = is_null($this->host) | ||
? self::getDefaultHost($this->licenseKey) | ||
: $this->host; | ||
|
||
$url = "{$this->protocol}{$host}/{$this->endpoint}"; | ||
$ch = curl_init(); | ||
curl_setopt($ch, CURLOPT_URL, $url); | ||
curl_setopt($ch, CURLOPT_POST, true); | ||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
return $ch; | ||
} | ||
|
||
/** | ||
* Augments JSON-formatted data with New Relic license key and other | ||
* necessary headers, and POSTs the log to the New Relic logging | ||
* endpoint via Curl | ||
* | ||
* @param string $data | ||
*/ | ||
protected function send($data) | ||
{ | ||
$ch = $this->getCurlHandler(); | ||
|
||
$headers = array( | ||
'Content-Type: application/json', | ||
'X-License-Key: ' . $this->licenseKey | ||
); | ||
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data); | ||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | ||
Curl\Util::execute($ch, 5, false); | ||
} | ||
|
||
/** | ||
* Augments a JSON-formatted array data with New Relic license key | ||
* and other necessary headers, and POSTs the log to the New Relic | ||
* logging endpoint via Curl | ||
* | ||
* @param string $data | ||
*/ | ||
protected function sendBatch($data) | ||
{ | ||
$ch = $this->getCurlHandler(); | ||
|
||
$headers = array( | ||
'Content-Type: application/json', | ||
'X-License-Key: ' . $this->licenseKey | ||
); | ||
|
||
$postData = '[{"logs":' . $data . '}]'; | ||
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data); | ||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | ||
Curl\Util::execute($ch, 5, false); | ||
} | ||
|
||
/** | ||
* Given a licence key, returns the default log API host for that region. | ||
* | ||
* @param string $licenseKey | ||
* @return string | ||
*/ | ||
protected static function getDefaultHost($licenseKey) | ||
{ | ||
if (!is_string($licenseKey)) { | ||
throw new \InvalidArgumentException( | ||
'Unknown license key of type ' . gettype($licenseKey) | ||
); | ||
} | ||
|
||
$matches = array(); | ||
if (preg_match('/^([a-z]{2,3})[0-9]{2}x/', $licenseKey, $matches)) { | ||
$region = ".{$matches[1]}"; | ||
} else { | ||
// US licence keys generally don't include region identifiers, so | ||
// we'll default to that. | ||
$region = ''; | ||
} | ||
|
||
return "log-api$region.newrelic.com"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,77 +4,54 @@ | |
* Copyright [2019] New Relic Corporation. All rights reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* This file contains the abstract parent of the Formatter class for | ||
* the New Relic Monolog Enricher. This class implements all functionality | ||
* that is compatible with all Monolog API versions | ||
* This file contains the Formatter class for the New Relic Monolog Enricher | ||
* on Monolog API v2 | ||
* | ||
* This class formats a Monolog record as a JSON object with a compatible | ||
* timestamp, and any New Relic context information moved to the top-level. | ||
* The resulting output is intended to be sent to New Relic Logs via a | ||
* compatible log forwarder with New Relic plugin installed (see this | ||
* project's README for links to available plugins). | ||
* | ||
* @author New Relic PHP <[email protected]> | ||
*/ | ||
|
||
namespace NewRelic\Monolog\Enricher; | ||
|
||
use Monolog\Formatter\JsonFormatter; | ||
use Monolog\Logger; | ||
|
||
/** | ||
* Formats record as a JSON object with transformations necessary for | ||
* ingestion by New Relic Logs | ||
*/ | ||
abstract class AbstractFormatter extends JsonFormatter | ||
class Formatter extends AbstractFormatter | ||
{ | ||
/** | ||
* @param int $batchMode | ||
* @param bool $appendNewline | ||
*/ | ||
public function __construct( | ||
$batchMode = self::BATCH_MODE_NEWLINES, | ||
$appendNewline = true | ||
) { | ||
// BATCH_MODE_NEWLINES is required for batch compatibility with New | ||
// Relic log forwarder plugins, which handle batching records. When | ||
// using the New Relic Monolog handler along side a batching handler | ||
// such as the BufferHandler, BATCH_MODE_JSON is required to adhere | ||
// to the New Relic logs API bulk ingest format. | ||
parent::__construct($batchMode, $appendNewline); | ||
} | ||
|
||
|
||
/** | ||
* Moves New Relic context information from the | ||
* `$data['extra']['newrelic-context']` array to top level of record, | ||
* converts `datetime` object to `timestamp` top level element represented | ||
* as milliseconds since the UNIX epoch, and finally, normalizes the data | ||
* Normalizes each record individually before JSON encoding the complete | ||
* batch of records as a JSON array. | ||
* | ||
* @param mixed $data | ||
* @param int $depth | ||
* @return mixed | ||
* @param array $records | ||
* @return string | ||
*/ | ||
protected function normalize($data, $depth = 0) | ||
protected function formatBatchJson(array $records): string | ||
{ | ||
if ($depth == 0) { | ||
if (isset($data['extra']['newrelic-context'])) { | ||
$data = array_merge($data, $data['extra']['newrelic-context']); | ||
unset($data['extra']['newrelic-context']); | ||
foreach ($records as $key => $record) { | ||
$normalized = $this->normalize($record); | ||
|
||
// Adhere to format of Monolog 2.x JSON format | ||
if ( | ||
isset($normalized['context']) | ||
&& $normalized['context'] === [] | ||
) { | ||
$normalized['context'] = new \stdClass(); | ||
} | ||
if ( | ||
isset($normalized['extra']) | ||
&& $normalized['extra'] === [] | ||
) { | ||
$normalized['extra'] = new \stdClass(); | ||
} | ||
$data['timestamp'] = intval( | ||
$data['datetime']->format('U.u') * 1000 | ||
); | ||
|
||
$records[$key] = $normalized; | ||
} | ||
return parent::normalize($data, $depth); | ||
return $this->toJson($records, true); | ||
} | ||
} | ||
|
||
// phpcs:disable | ||
/* | ||
* This extension to the Monolog framework supports the same PHP versions | ||
* as the New Relic PHP Agent (>=5.3). Older versions of PHP are only | ||
* compatible with Monolog v1, therefore, To accomodate Monolog v2's explicit | ||
* and required type annotations, some overridden methods must be implemented | ||
* both with compatible annotations for v2 and without for v1 | ||
*/ | ||
if (Logger::API == 2) { | ||
require_once dirname(__FILE__) . '/api2/Formatter.php'; | ||
} else { | ||
require_once dirname(__FILE__) . '/api1/Formatter.php'; | ||
} | ||
// phpcs:enable |
Oops, something went wrong.