ElasticaHandlerTest.php 9.4 KB

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