PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / prefixed / symfony / monolog-bridge / Handler / ElasticsearchLogstashHandler.php
matomo / app / vendor / prefixed / symfony / monolog-bridge / Handler Last commit date
FingersCrossed 1 year ago ChromePhpHandler.php 1 year ago ConsoleHandler.php 1 year ago ElasticsearchLogstashHandler.php 1 year ago FirePHPHandler.php 1 year ago MailerHandler.php 1 year ago NotifierHandler.php 1 year ago ServerLogHandler.php 2 months ago SwiftMailerHandler.php 1 year ago
ElasticsearchLogstashHandler.php
149 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace Matomo\Dependencies\Symfony\Bridge\Monolog\Handler;
12
13 use Matomo\Dependencies\Monolog\Formatter\FormatterInterface;
14 use Matomo\Dependencies\Monolog\Formatter\LogstashFormatter;
15 use Matomo\Dependencies\Monolog\Handler\AbstractHandler;
16 use Matomo\Dependencies\Monolog\Handler\FormattableHandlerTrait;
17 use Matomo\Dependencies\Monolog\Handler\ProcessableHandlerTrait;
18 use Matomo\Dependencies\Monolog\Logger;
19 use Symfony\Component\HttpClient\HttpClient;
20 use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
21 use Symfony\Contracts\HttpClient\HttpClientInterface;
22 use Symfony\Contracts\HttpClient\ResponseInterface;
23 /**
24 * Push logs directly to Elasticsearch and format them according to Logstash specification.
25 *
26 * This handler dials directly with the HTTP interface of Elasticsearch. This
27 * means it will slow down your application if Elasticsearch takes times to
28 * answer. Even if all HTTP calls are done asynchronously.
29 *
30 * In a development environment, it's fine to keep the default configuration:
31 * for each log, an HTTP request will be made to push the log to Elasticsearch.
32 *
33 * In a production environment, it's highly recommended to wrap this handler
34 * in a handler with buffering capabilities (like the FingersCrossedHandler, or
35 * BufferHandler) in order to call Elasticsearch only once with a bulk push. For
36 * even better performance and fault tolerance, a proper ELK (https://www.elastic.co/what-is/elk-stack)
37 * stack is recommended.
38 *
39 * @author Grégoire Pineau <lyrixx@lyrixx.info>
40 */
41 class ElasticsearchLogstashHandler extends AbstractHandler
42 {
43 use FormattableHandlerTrait;
44 use ProcessableHandlerTrait;
45 private $endpoint;
46 private $index;
47 private $client;
48 /**
49 * @var \SplObjectStorage<ResponseInterface, null>
50 */
51 private $responses;
52 private $elasticsearchVersion;
53 /**
54 * @param string|int $level The minimum logging level at which this handler will be triggered
55 */
56 public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', ?HttpClientInterface $client = null, $level = Logger::DEBUG, bool $bubble = \true, string $elasticsearchVersion = '1.0.0')
57 {
58 if (!interface_exists(HttpClientInterface::class)) {
59 throw new \LogicException(sprintf('The "%s" handler needs an HTTP client. Try running "composer require symfony/http-client".', __CLASS__));
60 }
61 parent::__construct($level, $bubble);
62 $this->endpoint = $endpoint;
63 $this->index = $index;
64 $this->client = $client ?: HttpClient::create(['timeout' => 1]);
65 $this->responses = new \SplObjectStorage();
66 $this->elasticsearchVersion = $elasticsearchVersion;
67 }
68 public function handle(array $record) : bool
69 {
70 if (!$this->isHandling($record)) {
71 return \false;
72 }
73 $this->sendToElasticsearch([$record]);
74 return !$this->bubble;
75 }
76 public function handleBatch(array $records) : void
77 {
78 $records = array_filter($records, [$this, 'isHandling']);
79 if ($records) {
80 $this->sendToElasticsearch($records);
81 }
82 }
83 protected function getDefaultFormatter() : FormatterInterface
84 {
85 // Monolog 1.X
86 if (\defined(LogstashFormatter::class . '::V1')) {
87 return new LogstashFormatter('application', null, null, 'ctxt_', LogstashFormatter::V1);
88 }
89 // Monolog 2.X
90 return new LogstashFormatter('application');
91 }
92 private function sendToElasticsearch(array $records)
93 {
94 $formatter = $this->getFormatter();
95 if (version_compare($this->elasticsearchVersion, '7', '>=')) {
96 $headers = json_encode(['index' => ['_index' => $this->index]]);
97 } else {
98 $headers = json_encode(['index' => ['_index' => $this->index, '_type' => '_doc']]);
99 }
100 $body = '';
101 foreach ($records as $record) {
102 foreach ($this->processors as $processor) {
103 $record = $processor($record);
104 }
105 $body .= $headers;
106 $body .= "\n";
107 $body .= $formatter->format($record);
108 $body .= "\n";
109 }
110 $response = $this->client->request('POST', $this->endpoint . '/_bulk', ['body' => $body, 'headers' => ['Content-Type' => 'application/json']]);
111 $this->responses->attach($response);
112 $this->wait(\false);
113 }
114 /**
115 * @return array
116 */
117 public function __sleep()
118 {
119 throw new \BadMethodCallException('Cannot serialize ' . __CLASS__);
120 }
121 public function __wakeup()
122 {
123 throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__);
124 }
125 public function __destruct()
126 {
127 $this->wait(\true);
128 }
129 private function wait(bool $blocking)
130 {
131 foreach ($this->client->stream($this->responses, $blocking ? null : 0.0) as $response => $chunk) {
132 try {
133 if ($chunk->isTimeout() && !$blocking) {
134 continue;
135 }
136 if (!$chunk->isFirst() && !$chunk->isLast()) {
137 continue;
138 }
139 if ($chunk->isLast()) {
140 $this->responses->detach($response);
141 }
142 } catch (ExceptionInterface $e) {
143 $this->responses->detach($response);
144 error_log(sprintf("Could not push logs to Elasticsearch:\n%s", (string) $e));
145 }
146 }
147 }
148 }
149