ElasticaHandlerTest.php 8.9 KB

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