Log.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace Drupal\Core\Database;
  3. /**
  4. * Database query logger.
  5. *
  6. * We log queries in a separate object rather than in the connection object
  7. * because we want to be able to see all queries sent to a given database, not
  8. * database target. If we logged the queries in each connection object we
  9. * would not be able to track what queries went to which target.
  10. *
  11. * Every connection has one and only one logging object on it for all targets
  12. * and logging keys.
  13. */
  14. class Log {
  15. /**
  16. * Cache of logged queries. This will only be used if the query logger is enabled.
  17. *
  18. * The structure for the logging array is as follows:
  19. *
  20. * array(
  21. * $logging_key = array(
  22. * array('query' => '', 'args' => array(), 'caller' => '', 'target' => '', 'time' => 0),
  23. * array('query' => '', 'args' => array(), 'caller' => '', 'target' => '', 'time' => 0),
  24. * ),
  25. * );
  26. *
  27. * @var array
  28. */
  29. protected $queryLog = [];
  30. /**
  31. * The connection key for which this object is logging.
  32. *
  33. * @var string
  34. */
  35. protected $connectionKey = 'default';
  36. /**
  37. * Constructor.
  38. *
  39. * @param $key
  40. * The database connection key for which to enable logging.
  41. */
  42. public function __construct($key = 'default') {
  43. $this->connectionKey = $key;
  44. }
  45. /**
  46. * Begin logging queries to the specified connection and logging key.
  47. *
  48. * If the specified logging key is already running this method does nothing.
  49. *
  50. * @param $logging_key
  51. * The identification key for this log request. By specifying different
  52. * logging keys we are able to start and stop multiple logging runs
  53. * simultaneously without them colliding.
  54. */
  55. public function start($logging_key) {
  56. if (empty($this->queryLog[$logging_key])) {
  57. $this->clear($logging_key);
  58. }
  59. }
  60. /**
  61. * Retrieve the query log for the specified logging key so far.
  62. *
  63. * @param $logging_key
  64. * The logging key to fetch.
  65. * @return
  66. * An indexed array of all query records for this logging key.
  67. */
  68. public function get($logging_key) {
  69. return $this->queryLog[$logging_key];
  70. }
  71. /**
  72. * Empty the query log for the specified logging key.
  73. *
  74. * This method does not stop logging, it simply clears the log. To stop
  75. * logging, use the end() method.
  76. *
  77. * @param $logging_key
  78. * The logging key to empty.
  79. */
  80. public function clear($logging_key) {
  81. $this->queryLog[$logging_key] = [];
  82. }
  83. /**
  84. * Stop logging for the specified logging key.
  85. *
  86. * @param $logging_key
  87. * The logging key to stop.
  88. */
  89. public function end($logging_key) {
  90. unset($this->queryLog[$logging_key]);
  91. }
  92. /**
  93. * Log a query to all active logging keys.
  94. *
  95. * @param $statement
  96. * The prepared statement object to log.
  97. * @param $args
  98. * The arguments passed to the statement object.
  99. * @param $time
  100. * The time in milliseconds the query took to execute.
  101. */
  102. public function log(StatementInterface $statement, $args, $time) {
  103. foreach (array_keys($this->queryLog) as $key) {
  104. $this->queryLog[$key][] = [
  105. 'query' => $statement->getQueryString(),
  106. 'args' => $args,
  107. 'target' => $statement->dbh->getTarget(),
  108. 'caller' => $this->findCaller(),
  109. 'time' => $time,
  110. ];
  111. }
  112. }
  113. /**
  114. * Determine the routine that called this query.
  115. *
  116. * We define "the routine that called this query" as the first entry in
  117. * the call stack that is not inside the includes/Drupal/Database directory,
  118. * does not begin with db_ and does have a file (which excludes
  119. * call_user_func_array(), anonymous functions and similar). That makes the
  120. * climbing logic very simple, and handles the variable stack depth caused by
  121. * the query builders.
  122. *
  123. * See the @link http://php.net/debug_backtrace debug_backtrace() @endlink
  124. * function.
  125. *
  126. * @return
  127. * This method returns a stack trace entry similar to that generated by
  128. * debug_backtrace(). However, it flattens the trace entry and the trace
  129. * entry before it so that we get the function and args of the function that
  130. * called into the database system, not the function and args of the
  131. * database call itself.
  132. */
  133. public function findCaller() {
  134. $stack = debug_backtrace();
  135. for ($i = 0, $stack_count = count($stack); $i < $stack_count; ++$i) {
  136. // If the call was made from a function, 'class' will be empty. It's
  137. // just easier to give it a default value than to try and integrate
  138. // that into the if statement below.
  139. if (empty($stack[$i]['class'])) {
  140. $stack[$i]['class'] = '';
  141. }
  142. if (strpos($stack[$i]['class'], __NAMESPACE__) === FALSE && strpos($stack[$i + 1]['function'], 'db_') === FALSE && !empty($stack[$i]['file'])) {
  143. $stack[$i] += ['file' => '?', 'line' => '?', 'args' => []];
  144. return [
  145. 'file' => $stack[$i]['file'],
  146. 'line' => $stack[$i]['line'],
  147. 'function' => $stack[$i + 1]['function'],
  148. 'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL,
  149. 'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL,
  150. 'args' => $stack[$i + 1]['args'],
  151. ];
  152. }
  153. }
  154. }
  155. }