LogTarget.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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\debug;
  8. use Yii;
  9. use yii\base\InvalidConfigException;
  10. use yii\helpers\FileHelper;
  11. use yii\log\Target;
  12. /**
  13. * The debug LogTarget is used to store logs for later use in the debugger tool
  14. *
  15. * @author Qiang Xue <qiang.xue@gmail.com>
  16. * @since 2.0
  17. */
  18. class LogTarget extends Target
  19. {
  20. /**
  21. * @var Module
  22. */
  23. public $module;
  24. public $tag;
  25. /**
  26. * @param \yii\debug\Module $module
  27. * @param array $config
  28. */
  29. public function __construct($module, $config = [])
  30. {
  31. parent::__construct($config);
  32. $this->module = $module;
  33. $this->tag = uniqid();
  34. }
  35. /**
  36. * Exports log messages to a specific destination.
  37. * Child classes must implement this method.
  38. */
  39. public function export()
  40. {
  41. $path = $this->module->dataPath;
  42. FileHelper::createDirectory($path, $this->module->dirMode);
  43. $summary = $this->collectSummary();
  44. $dataFile = "$path/{$this->tag}.data";
  45. $data = [];
  46. $exceptions = [];
  47. foreach ($this->module->panels as $id => $panel) {
  48. try {
  49. $data[$id] = serialize($panel->save());
  50. } catch (\Exception $exception) {
  51. $exceptions[$id] = new FlattenException($exception);
  52. }
  53. }
  54. $data['summary'] = $summary;
  55. $data['exceptions'] = $exceptions;
  56. file_put_contents($dataFile, serialize($data));
  57. if ($this->module->fileMode !== null) {
  58. @chmod($dataFile, $this->module->fileMode);
  59. }
  60. $indexFile = "$path/index.data";
  61. $this->updateIndexFile($indexFile, $summary);
  62. }
  63. /**
  64. * Updates index file with summary log data
  65. *
  66. * @param string $indexFile path to index file
  67. * @param array $summary summary log data
  68. * @throws \yii\base\InvalidConfigException
  69. */
  70. private function updateIndexFile($indexFile, $summary)
  71. {
  72. touch($indexFile);
  73. if (($fp = @fopen($indexFile, 'r+')) === false) {
  74. throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
  75. }
  76. @flock($fp, LOCK_EX);
  77. $manifest = '';
  78. while (($buffer = fgets($fp)) !== false) {
  79. $manifest .= $buffer;
  80. }
  81. if (!feof($fp) || empty($manifest)) {
  82. // error while reading index data, ignore and create new
  83. $manifest = [];
  84. } else {
  85. $manifest = unserialize($manifest);
  86. }
  87. $manifest[$this->tag] = $summary;
  88. $this->gc($manifest);
  89. ftruncate($fp, 0);
  90. rewind($fp);
  91. fwrite($fp, serialize($manifest));
  92. @flock($fp, LOCK_UN);
  93. @fclose($fp);
  94. if ($this->module->fileMode !== null) {
  95. @chmod($indexFile, $this->module->fileMode);
  96. }
  97. }
  98. /**
  99. * Processes the given log messages.
  100. * This method will filter the given messages with [[levels]] and [[categories]].
  101. * And if requested, it will also export the filtering result to specific medium (e.g. email).
  102. * @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure
  103. * of each message.
  104. * @param bool $final whether this method is called at the end of the current application
  105. */
  106. public function collect($messages, $final)
  107. {
  108. $this->messages = array_merge($this->messages, $messages);
  109. if ($final) {
  110. $this->export();
  111. }
  112. }
  113. /**
  114. * Removes obsolete data files
  115. * @param array $manifest
  116. */
  117. protected function gc(&$manifest)
  118. {
  119. if (count($manifest) > $this->module->historySize + 10) {
  120. $n = count($manifest) - $this->module->historySize;
  121. foreach (array_keys($manifest) as $tag) {
  122. $file = $this->module->dataPath . "/$tag.data";
  123. @unlink($file);
  124. unset($manifest[$tag]);
  125. if (--$n <= 0) {
  126. break;
  127. }
  128. }
  129. }
  130. }
  131. /**
  132. * Collects summary data of current request.
  133. * @return array
  134. */
  135. protected function collectSummary()
  136. {
  137. if (Yii::$app === null) {
  138. return '';
  139. }
  140. $request = Yii::$app->getRequest();
  141. $response = Yii::$app->getResponse();
  142. $summary = [
  143. 'tag' => $this->tag,
  144. 'url' => $request->getAbsoluteUrl(),
  145. 'ajax' => (int) $request->getIsAjax(),
  146. 'method' => $request->getMethod(),
  147. 'ip' => $request->getUserIP(),
  148. 'time' => $_SERVER['REQUEST_TIME_FLOAT'],
  149. 'statusCode' => $response->statusCode,
  150. 'sqlCount' => $this->getSqlTotalCount(),
  151. ];
  152. if (isset($this->module->panels['mail'])) {
  153. $summary['mailCount'] = count($this->module->panels['mail']->getMessages());
  154. }
  155. return $summary;
  156. }
  157. /**
  158. * Returns total sql count executed in current request. If database panel is not configured
  159. * returns 0.
  160. * @return int
  161. */
  162. protected function getSqlTotalCount()
  163. {
  164. if (!isset($this->module->panels['db'])) {
  165. return 0;
  166. }
  167. $profileLogs = $this->module->panels['db']->getProfileLogs();
  168. # / 2 because messages are in couple (begin/end)
  169. return count($profileLogs) / 2;
  170. }
  171. }