ElasticsearchTarget.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\elasticsearch;
  8. use Yii;
  9. use yii\base\InvalidConfigException;
  10. use yii\di\Instance;
  11. use yii\helpers\ArrayHelper;
  12. use yii\helpers\Json;
  13. use yii\helpers\VarDumper;
  14. use yii\log\Logger;
  15. use yii\log\Target;
  16. /**
  17. * ElasticsearchTarget stores log messages in a Elasticsearch index.
  18. *
  19. * @author Eugene Terentev <eugene@terentev.net>
  20. * @since 2.0.5
  21. */
  22. class ElasticsearchTarget extends Target
  23. {
  24. /**
  25. * @var string Elasticsearch index name.
  26. */
  27. public $index = 'yii';
  28. /**
  29. * @var string Elasticsearch type name.
  30. */
  31. public $type = 'log';
  32. /**
  33. * @var Connection|array|string the Elasticsearch connection object or the application component ID
  34. * of the Elasticsearch connection.
  35. */
  36. public $db = 'elasticsearch';
  37. /**
  38. * @var array $options URL options.
  39. */
  40. public $options = [];
  41. /**
  42. * @var boolean If true, context will be logged as a separate message after all other messages.
  43. */
  44. public $logContext = true;
  45. /**
  46. * @var boolean If true, context will be included in every message.
  47. * This is convenient if you log application errors and analyze them with tools like Kibana.
  48. */
  49. public $includeContext = false;
  50. /**
  51. * @var boolean If true, context message will cached once it's been created. Makes sense to use with [[includeContext]].
  52. */
  53. public $cacheContext = false;
  54. /**
  55. * @var string Context message cache (can be used multiple times if context is appended to every message)
  56. */
  57. protected $_contextMessage = null;
  58. /**
  59. * This method will initialize the [[elasticsearch]] property to make sure it refers to a valid Elasticsearch connection.
  60. * @throws InvalidConfigException if [[elasticsearch]] is invalid.
  61. */
  62. public function init()
  63. {
  64. parent::init();
  65. $this->db = Instance::ensure($this->db, Connection::className());
  66. }
  67. /**
  68. * @inheritdoc
  69. */
  70. public function export()
  71. {
  72. $messages = array_map([$this, 'prepareMessage'], $this->messages);
  73. $body = implode("\n", $messages) . "\n";
  74. $this->db->post([$this->index, $this->type, '_bulk'], $this->options, $body);
  75. }
  76. /**
  77. * If [[includeContext]] property is false, returns context message normally.
  78. * If [[includeContext]] is true, returns an empty string (so that context message in [[collect]] is not generated),
  79. * expecting that context will be appended to every message in [[prepareMessage]].
  80. * @return array the context information
  81. */
  82. protected function getContextMessage()
  83. {
  84. if (null === $this->_contextMessage || !$this->cacheContext) {
  85. $this->_contextMessage = ArrayHelper::filter($GLOBALS, $this->logVars);
  86. }
  87. return $this->_contextMessage;
  88. }
  89. /**
  90. * Processes the given log messages.
  91. * This method will filter the given messages with [[levels]] and [[categories]].
  92. * And if requested, it will also export the filtering result to specific medium (e.g. email).
  93. * Depending on the [[includeContext]] attribute, a context message will be either created or ignored.
  94. * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
  95. * of each message.
  96. * @param bool $final whether this method is called at the end of the current application
  97. */
  98. public function collect($messages, $final)
  99. {
  100. $this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
  101. $count = count($this->messages);
  102. if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
  103. if (!$this->includeContext && $this->logContext) {
  104. $context = $this->getContextMessage();
  105. if (!empty($context)) {
  106. $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
  107. }
  108. }
  109. // set exportInterval to 0 to avoid triggering export again while exporting
  110. $oldExportInterval = $this->exportInterval;
  111. $this->exportInterval = 0;
  112. $this->export();
  113. $this->exportInterval = $oldExportInterval;
  114. $this->messages = [];
  115. }
  116. }
  117. /**
  118. * Prepares a log message.
  119. * @param array $message The log message to be formatted.
  120. * @return string
  121. */
  122. public function prepareMessage($message)
  123. {
  124. list($text, $level, $category, $timestamp) = $message;
  125. $result = [
  126. 'category' => $category,
  127. 'level' => Logger::getLevelName($level),
  128. '@timestamp' => date('c', $timestamp),
  129. ];
  130. if (isset($message[4])) {
  131. $result['trace'] = $message[4];
  132. }
  133. if (!is_string($text)) {
  134. // exceptions may not be serializable if in the call stack somewhere is a Closure
  135. if ($text instanceof \Throwable || $text instanceof \Exception) {
  136. $text = (string) $text;
  137. } else {
  138. $text = VarDumper::export($text);
  139. }
  140. }
  141. $result['message'] = $text;
  142. if ($this->includeContext) {
  143. $result['context'] = $this->getContextMessage();
  144. }
  145. $message = implode("\n", [
  146. Json::encode([
  147. 'index' => new \stdClass()
  148. ]),
  149. Json::encode($result)
  150. ]);
  151. return $message;
  152. }
  153. }