LogTarget.php 5.8 KB

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