NewRelicHandler.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog\Handler;
  11. use Monolog\Logger;
  12. use Monolog\Utils;
  13. use Monolog\Formatter\NormalizerFormatter;
  14. use Monolog\Formatter\FormatterInterface;
  15. /**
  16. * Class to record a log on a NewRelic application.
  17. * Enabling New Relic High Security mode may prevent capture of useful information.
  18. *
  19. * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted']
  20. *
  21. * @see https://docs.newrelic.com/docs/agents/php-agent
  22. * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
  23. */
  24. class NewRelicHandler extends AbstractProcessingHandler
  25. {
  26. /**
  27. * Name of the New Relic application that will receive logs from this handler.
  28. *
  29. * @var string|null
  30. */
  31. protected $appName;
  32. /**
  33. * Name of the current transaction
  34. *
  35. * @var string|null
  36. */
  37. protected $transactionName;
  38. /**
  39. * Some context and extra data is passed into the handler as arrays of values. Do we send them as is
  40. * (useful if we are using the API), or explode them for display on the NewRelic RPM website?
  41. *
  42. * @var bool
  43. */
  44. protected $explodeArrays;
  45. /**
  46. * {@inheritDoc}
  47. *
  48. * @param string|int $level The minimum logging level at which this handler will be triggered.
  49. * @param bool $bubble Whether the messages that are handled can bubble up the stack or not.
  50. * @param string|null $appName
  51. * @param bool $explodeArrays
  52. * @param string|null $transactionName
  53. */
  54. public function __construct(
  55. $level = Logger::ERROR,
  56. bool $bubble = true,
  57. ?string $appName = null,
  58. bool $explodeArrays = false,
  59. ?string $transactionName = null
  60. ) {
  61. parent::__construct($level, $bubble);
  62. $this->appName = $appName;
  63. $this->explodeArrays = $explodeArrays;
  64. $this->transactionName = $transactionName;
  65. }
  66. /**
  67. * {@inheritDoc}
  68. */
  69. protected function write(array $record): void
  70. {
  71. if (!$this->isNewRelicEnabled()) {
  72. throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
  73. }
  74. if ($appName = $this->getAppName($record['context'])) {
  75. $this->setNewRelicAppName($appName);
  76. }
  77. if ($transactionName = $this->getTransactionName($record['context'])) {
  78. $this->setNewRelicTransactionName($transactionName);
  79. unset($record['formatted']['context']['transaction_name']);
  80. }
  81. if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
  82. newrelic_notice_error($record['message'], $record['context']['exception']);
  83. unset($record['formatted']['context']['exception']);
  84. } else {
  85. newrelic_notice_error($record['message']);
  86. }
  87. if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) {
  88. foreach ($record['formatted']['context'] as $key => $parameter) {
  89. if (is_array($parameter) && $this->explodeArrays) {
  90. foreach ($parameter as $paramKey => $paramValue) {
  91. $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);
  92. }
  93. } else {
  94. $this->setNewRelicParameter('context_' . $key, $parameter);
  95. }
  96. }
  97. }
  98. if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) {
  99. foreach ($record['formatted']['extra'] as $key => $parameter) {
  100. if (is_array($parameter) && $this->explodeArrays) {
  101. foreach ($parameter as $paramKey => $paramValue) {
  102. $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);
  103. }
  104. } else {
  105. $this->setNewRelicParameter('extra_' . $key, $parameter);
  106. }
  107. }
  108. }
  109. }
  110. /**
  111. * Checks whether the NewRelic extension is enabled in the system.
  112. *
  113. * @return bool
  114. */
  115. protected function isNewRelicEnabled(): bool
  116. {
  117. return extension_loaded('newrelic');
  118. }
  119. /**
  120. * Returns the appname where this log should be sent. Each log can override the default appname, set in this
  121. * handler's constructor, by providing the appname in it's context.
  122. */
  123. protected function getAppName(array $context): ?string
  124. {
  125. if (isset($context['appname'])) {
  126. return $context['appname'];
  127. }
  128. return $this->appName;
  129. }
  130. /**
  131. * Returns the name of the current transaction. Each log can override the default transaction name, set in this
  132. * handler's constructor, by providing the transaction_name in it's context
  133. */
  134. protected function getTransactionName(array $context): ?string
  135. {
  136. if (isset($context['transaction_name'])) {
  137. return $context['transaction_name'];
  138. }
  139. return $this->transactionName;
  140. }
  141. /**
  142. * Sets the NewRelic application that should receive this log.
  143. */
  144. protected function setNewRelicAppName(string $appName): void
  145. {
  146. newrelic_set_appname($appName);
  147. }
  148. /**
  149. * Overwrites the name of the current transaction
  150. */
  151. protected function setNewRelicTransactionName(string $transactionName): void
  152. {
  153. newrelic_name_transaction($transactionName);
  154. }
  155. /**
  156. * @param string $key
  157. * @param mixed $value
  158. */
  159. protected function setNewRelicParameter(string $key, $value): void
  160. {
  161. if (null === $value || is_scalar($value)) {
  162. newrelic_add_custom_parameter($key, $value);
  163. } else {
  164. newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true));
  165. }
  166. }
  167. /**
  168. * {@inheritDoc}
  169. */
  170. protected function getDefaultFormatter(): FormatterInterface
  171. {
  172. return new NormalizerFormatter();
  173. }
  174. }