184 行
5.1KB

  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. foreach ($this->module->panels as $id => $panel) {
  47. $data[$id] = $panel->save();
  48. }
  49. $data['summary'] = $summary;
  50. file_put_contents($dataFile, serialize($data));
  51. if ($this->module->fileMode !== null) {
  52. @chmod($dataFile, $this->module->fileMode);
  53. }
  54. $indexFile = "$path/index.data";
  55. $this->updateIndexFile($indexFile, $summary);
  56. }
  57. /**
  58. * Updates index file with summary log data
  59. *
  60. * @param string $indexFile path to index file
  61. * @param array $summary summary log data
  62. * @throws \yii\base\InvalidConfigException
  63. */
  64. private function updateIndexFile($indexFile, $summary)
  65. {
  66. touch($indexFile);
  67. if (($fp = @fopen($indexFile, 'r+')) === false) {
  68. throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
  69. }
  70. @flock($fp, LOCK_EX);
  71. $manifest = '';
  72. while (($buffer = fgets($fp)) !== false) {
  73. $manifest .= $buffer;
  74. }
  75. if (!feof($fp) || empty($manifest)) {
  76. // error while reading index data, ignore and create new
  77. $manifest = [];
  78. } else {
  79. $manifest = unserialize($manifest);
  80. }
  81. $manifest[$this->tag] = $summary;
  82. $this->gc($manifest);
  83. ftruncate($fp, 0);
  84. rewind($fp);
  85. fwrite($fp, serialize($manifest));
  86. @flock($fp, LOCK_UN);
  87. @fclose($fp);
  88. if ($this->module->fileMode !== null) {
  89. @chmod($indexFile, $this->module->fileMode);
  90. }
  91. }
  92. /**
  93. * Processes the given log messages.
  94. * This method will filter the given messages with [[levels]] and [[categories]].
  95. * And if requested, it will also export the filtering result to specific medium (e.g. email).
  96. * @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure
  97. * of each message.
  98. * @param bool $final whether this method is called at the end of the current application
  99. */
  100. public function collect($messages, $final)
  101. {
  102. $this->messages = array_merge($this->messages, $messages);
  103. if ($final) {
  104. $this->export();
  105. }
  106. }
  107. protected function gc(&$manifest)
  108. {
  109. if (count($manifest) > $this->module->historySize + 10) {
  110. $n = count($manifest) - $this->module->historySize;
  111. foreach (array_keys($manifest) as $tag) {
  112. $file = $this->module->dataPath . "/$tag.data";
  113. @unlink($file);
  114. unset($manifest[$tag]);
  115. if (--$n <= 0) {
  116. break;
  117. }
  118. }
  119. }
  120. }
  121. /**
  122. * Collects summary data of current request.
  123. * @return array
  124. */
  125. protected function collectSummary()
  126. {
  127. if (Yii::$app === null) {
  128. return '';
  129. }
  130. $request = Yii::$app->getRequest();
  131. $response = Yii::$app->getResponse();
  132. $summary = [
  133. 'tag' => $this->tag,
  134. 'url' => $request->getAbsoluteUrl(),
  135. 'ajax' => (int) $request->getIsAjax(),
  136. 'method' => $request->getMethod(),
  137. 'ip' => $request->getUserIP(),
  138. 'time' => time(),
  139. 'statusCode' => $response->statusCode,
  140. 'sqlCount' => $this->getSqlTotalCount(),
  141. ];
  142. if (isset($this->module->panels['mail'])) {
  143. $summary['mailCount'] = count($this->module->panels['mail']->getMessages());
  144. }
  145. return $summary;
  146. }
  147. /**
  148. * Returns total sql count executed in current request. If database panel is not configured
  149. * returns 0.
  150. * @return int
  151. */
  152. protected function getSqlTotalCount()
  153. {
  154. if (!isset($this->module->panels['db'])) {
  155. return 0;
  156. }
  157. $profileLogs = $this->module->panels['db']->getProfileLogs();
  158. # / 2 because messages are in couple (begin/end)
  159. return count($profileLogs) / 2;
  160. }
  161. }