2
0

ElasticaHandlerTest.php 7.8 KB

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