CubeHandler.php 4.7 KB

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