CubeHandler.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. /**
  14. * Logs to Cube.
  15. *
  16. * @link https://github.com/square/cube/wiki
  17. * @author Wan Chen <kami@kamisama.me>
  18. * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4
  19. */
  20. class CubeHandler extends AbstractProcessingHandler
  21. {
  22. /** @var resource|\Socket|null */
  23. private $udpConnection = null;
  24. /** @var resource|\CurlHandle|null */
  25. private $httpConnection = null;
  26. /** @var string */
  27. private $scheme;
  28. /** @var string */
  29. private $host;
  30. /** @var int */
  31. private $port;
  32. /** @var string[] */
  33. private $acceptedSchemes = ['http', 'udp'];
  34. /**
  35. * Create a Cube handler
  36. *
  37. * @throws \UnexpectedValueException when given url is not a valid url.
  38. * A valid url must consist of three parts : protocol://host:port
  39. * Only valid protocols used by Cube are http and udp
  40. */
  41. public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true)
  42. {
  43. $urlInfo = parse_url($url);
  44. if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
  45. throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
  46. }
  47. if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
  48. throw new \UnexpectedValueException(
  49. 'Invalid protocol (' . $urlInfo['scheme'] . ').'
  50. . ' Valid options are ' . implode(', ', $this->acceptedSchemes)
  51. );
  52. }
  53. $this->scheme = $urlInfo['scheme'];
  54. $this->host = $urlInfo['host'];
  55. $this->port = (int) $urlInfo['port'];
  56. parent::__construct($level, $bubble);
  57. }
  58. /**
  59. * Establish a connection to an UDP socket
  60. *
  61. * @throws \LogicException when unable to connect to the socket
  62. * @throws MissingExtensionException when there is no socket extension
  63. */
  64. protected function connectUdp(): void
  65. {
  66. if (!extension_loaded('sockets')) {
  67. throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
  68. }
  69. $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
  70. if (false === $udpConnection) {
  71. throw new \LogicException('Unable to create a socket');
  72. }
  73. $this->udpConnection = $udpConnection;
  74. if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
  75. throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
  76. }
  77. }
  78. /**
  79. * Establish a connection to an http server
  80. *
  81. * @throws \LogicException when unable to connect to the socket
  82. * @throws MissingExtensionException when no curl extension
  83. */
  84. protected function connectHttp(): void
  85. {
  86. if (!extension_loaded('curl')) {
  87. throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler');
  88. }
  89. $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
  90. if (false === $httpConnection) {
  91. throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
  92. }
  93. $this->httpConnection = $httpConnection;
  94. curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
  95. curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
  96. }
  97. /**
  98. * {@inheritDoc}
  99. */
  100. protected function write(array $record): void
  101. {
  102. $date = $record['datetime'];
  103. $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')];
  104. unset($record['datetime']);
  105. if (isset($record['context']['type'])) {
  106. $data['type'] = $record['context']['type'];
  107. unset($record['context']['type']);
  108. } else {
  109. $data['type'] = $record['channel'];
  110. }
  111. $data['data'] = $record['context'];
  112. $data['data']['level'] = $record['level'];
  113. if ($this->scheme === 'http') {
  114. $this->writeHttp(Utils::jsonEncode($data));
  115. } else {
  116. $this->writeUdp(Utils::jsonEncode($data));
  117. }
  118. }
  119. private function writeUdp(string $data): void
  120. {
  121. if (!$this->udpConnection) {
  122. $this->connectUdp();
  123. }
  124. socket_send($this->udpConnection, $data, strlen($data), 0);
  125. }
  126. private function writeHttp(string $data): void
  127. {
  128. if (!$this->httpConnection) {
  129. $this->connectHttp();
  130. }
  131. if (null === $this->httpConnection) {
  132. throw new \LogicException('No connection could be established');
  133. }
  134. curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
  135. curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [
  136. 'Content-Type: application/json',
  137. 'Content-Length: ' . strlen('['.$data.']'),
  138. ]);
  139. Curl\Util::execute($this->httpConnection, 5, false);
  140. }
  141. }