ElasticaHandlerTest.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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\Formatter\ElasticaFormatter;
  12. use Monolog\Formatter\NormalizerFormatter;
  13. use Monolog\Test\TestCase;
  14. use Monolog\Level;
  15. use Elastica\Client;
  16. use Elastica\Request;
  17. use Elastica\Response;
  18. use PHPUnit\Framework\Attributes\DataProvider;
  19. /**
  20. * @group Elastica
  21. */
  22. class ElasticaHandlerTest extends TestCase
  23. {
  24. /**
  25. * @var Client mock
  26. */
  27. protected Client $client;
  28. /**
  29. * @var array Default handler options
  30. */
  31. protected array $options = [
  32. 'index' => 'my_index',
  33. 'type' => 'doc_type',
  34. ];
  35. public function setUp(): void
  36. {
  37. // Elastica lib required
  38. if (!class_exists("Elastica\Client")) {
  39. $this->markTestSkipped("ruflin/elastica not installed");
  40. }
  41. // base mock Elastica Client object
  42. $this->client = $this->getMockBuilder('Elastica\Client')
  43. ->onlyMethods(['addDocuments'])
  44. ->disableOriginalConstructor()
  45. ->getMock();
  46. }
  47. public function tearDown(): void
  48. {
  49. parent::tearDown();
  50. unset($this->client);
  51. }
  52. /**
  53. * @covers Monolog\Handler\ElasticaHandler::write
  54. * @covers Monolog\Handler\ElasticaHandler::handleBatch
  55. * @covers Monolog\Handler\ElasticaHandler::bulkSend
  56. * @covers Monolog\Handler\ElasticaHandler::getDefaultFormatter
  57. */
  58. public function testHandle()
  59. {
  60. // log message
  61. $msg = $this->getRecord(Level::Error, 'log', context: ['foo' => 7, 'bar', 'class' => new \stdClass], datetime: new \DateTimeImmutable("@0"));
  62. // format expected result
  63. $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']);
  64. $expected = [$formatter->format($msg)];
  65. // setup ES client mock
  66. $this->client->expects($this->any())
  67. ->method('addDocuments')
  68. ->with($expected);
  69. // perform tests
  70. $handler = new ElasticaHandler($this->client, $this->options);
  71. $handler->handle($msg);
  72. $handler->handleBatch([$msg]);
  73. }
  74. /**
  75. * @covers Monolog\Handler\ElasticaHandler::setFormatter
  76. */
  77. public function testSetFormatter()
  78. {
  79. $handler = new ElasticaHandler($this->client);
  80. $formatter = new ElasticaFormatter('index_new', 'type_new');
  81. $handler->setFormatter($formatter);
  82. $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter());
  83. $this->assertEquals('index_new', $handler->getFormatter()->getIndex());
  84. $this->assertEquals('type_new', $handler->getFormatter()->getType());
  85. }
  86. /**
  87. * @covers Monolog\Handler\ElasticaHandler::setFormatter
  88. */
  89. public function testSetFormatterInvalid()
  90. {
  91. $handler = new ElasticaHandler($this->client);
  92. $formatter = new NormalizerFormatter();
  93. $this->expectException(\InvalidArgumentException::class);
  94. $this->expectExceptionMessage('ElasticaHandler is only compatible with ElasticaFormatter');
  95. $handler->setFormatter($formatter);
  96. }
  97. /**
  98. * @covers Monolog\Handler\ElasticaHandler::__construct
  99. * @covers Monolog\Handler\ElasticaHandler::getOptions
  100. */
  101. public function testOptions()
  102. {
  103. $expected = [
  104. 'index' => $this->options['index'],
  105. 'type' => $this->options['type'],
  106. 'ignore_error' => false,
  107. ];
  108. $handler = new ElasticaHandler($this->client, $this->options);
  109. $this->assertEquals($expected, $handler->getOptions());
  110. }
  111. /**
  112. * @covers Monolog\Handler\ElasticaHandler::bulkSend
  113. */
  114. #[DataProvider('providerTestConnectionErrors')]
  115. public function testConnectionErrors($ignore, $expectedError)
  116. {
  117. $clientOpts = ['host' => '127.0.0.1', 'port' => 1];
  118. $client = new Client($clientOpts);
  119. $handlerOpts = ['ignore_error' => $ignore];
  120. $handler = new ElasticaHandler($client, $handlerOpts);
  121. if ($expectedError) {
  122. $this->expectException($expectedError[0]);
  123. $this->expectExceptionMessage($expectedError[1]);
  124. $handler->handle($this->getRecord());
  125. } else {
  126. $this->assertFalse($handler->handle($this->getRecord()));
  127. }
  128. }
  129. public static function providerTestConnectionErrors(): array
  130. {
  131. return [
  132. [false, ['RuntimeException', 'Error sending messages to Elasticsearch']],
  133. [true, false],
  134. ];
  135. }
  136. /**
  137. * Integration test using localhost Elastic Search server version 7+
  138. *
  139. * @covers Monolog\Handler\ElasticaHandler::__construct
  140. * @covers Monolog\Handler\ElasticaHandler::handleBatch
  141. * @covers Monolog\Handler\ElasticaHandler::bulkSend
  142. * @covers Monolog\Handler\ElasticaHandler::getDefaultFormatter
  143. */
  144. public function testHandleIntegrationNewESVersion()
  145. {
  146. $msg = $this->getRecord(Level::Error, 'log', context: ['foo' => 7, 'bar', 'class' => new \stdClass], datetime: new \DateTimeImmutable("@0"));
  147. $expected = (array) $msg;
  148. $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601);
  149. $expected['context'] = [
  150. 'class' => '[object] (stdClass: {})',
  151. 'foo' => 7,
  152. 0 => 'bar',
  153. ];
  154. $clientOpts = ['url' => 'http://elastic:changeme@127.0.0.1:9200'];
  155. $client = new Client($clientOpts);
  156. $handler = new ElasticaHandler($client, $this->options);
  157. try {
  158. $handler->handleBatch([$msg]);
  159. } catch (\RuntimeException $e) {
  160. $this->markTestSkipped("Cannot connect to Elastic Search server on localhost");
  161. }
  162. // check document id from ES server response
  163. $documentId = $this->getCreatedDocId($client->getLastResponse());
  164. $this->assertNotEmpty($documentId, 'No elastic document id received');
  165. // retrieve document source from ES and validate
  166. $document = $this->getDocSourceFromElastic(
  167. $client,
  168. $this->options['index'],
  169. null,
  170. $documentId
  171. );
  172. $this->assertEquals($expected, $document);
  173. // remove test index from ES
  174. $client->request("/{$this->options['index']}", Request::DELETE);
  175. }
  176. /**
  177. * Return last created document id from ES response
  178. * @param Response $response Elastica Response object
  179. */
  180. protected function getCreatedDocId(Response $response): ?string
  181. {
  182. $data = $response->getData();
  183. if (!empty($data['items'][0]['index']['_id'])) {
  184. return $data['items'][0]['index']['_id'];
  185. }
  186. var_dump('Unexpected response: ', $data);
  187. return null;
  188. }
  189. /**
  190. * Retrieve document by id from Elasticsearch
  191. * @param Client $client Elastica client
  192. * @param ?string $type
  193. */
  194. protected function getDocSourceFromElastic(Client $client, string $index, $type, string $documentId): array
  195. {
  196. if ($type === null) {
  197. $path = "/{$index}/_doc/{$documentId}";
  198. } else {
  199. $path = "/{$index}/{$type}/{$documentId}";
  200. }
  201. $resp = $client->request($path, Request::GET);
  202. $data = $resp->getData();
  203. if (!empty($data['_source'])) {
  204. return $data['_source'];
  205. }
  206. return [];
  207. }
  208. }