2 namespace Drush\Drupal\Commands\core;
4 use Consolidation\OutputFormatters\StructuredData\PropertyList;
5 use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
6 use Drupal\Core\Database\Database;
7 use Drupal\Core\Logger\RfcLogLevel;
8 use Drush\Commands\DrushCommands;
9 use Drupal\Component\Utility\Unicode;
10 use Drupal\Component\Utility\Html;
11 use Drush\Drupal\DrupalUtil;
12 use Drush\Exceptions\UserAbortException;
14 class WatchdogCommands extends DrushCommands
18 * Show watchdog messages.
20 * @command watchdog:show
21 * @param $substring A substring to look search in error messages.
22 * @option count The number of messages to show. Defaults to 10.
23 * @option severity Restrict to messages of a given severity level.
24 * @option type Restrict to messages of a given type.
25 * @option extended Return extended information about each message.
26 * @usage drush watchdog-show
27 * Show a listing of most recent 10 messages.
28 * @usage drush watchdog:show "cron run succesful"
29 * Show a listing of most recent 10 messages containing the string "cron run succesful".
30 * @usage drush watchdog:show --count=46
31 * Show a listing of most recent 46 messages.
32 * @usage drush watchdog:show --severity=Notice
33 * Show a listing of most recent 10 messages with a severity of notice.
34 * @usage drush watchdog:show --type=php
35 * Show a listing of most recent 10 messages of type php
36 * @aliases wd-show,ws,watchdog-show
37 * @validate-module-enabled dblog
47 * @default-fields wid,date,type,severity,message
48 * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
50 public function show($substring = '', $options = ['format' => 'table', 'count' => 10, 'severity' => self::REQ, 'type' => self::REQ, 'extended' => false])
52 $where = $this->where($options['type'], $options['severity'], $substring);
53 $query = Database::getConnection()->select('watchdog', 'w')
54 ->range(0, $options['count'])
56 ->orderBy('wid', 'DESC');
57 if (!empty($where['where'])) {
58 $query->where($where['where'], $where['args']);
60 $rsc = $query->execute();
61 while ($result = $rsc->fetchObject()) {
62 $row = $this->formatResult($result, $options['extended']);
63 $table[$row->wid] = (array)$row;
66 $this->logger()->notice(dt('No log messages available.'));
68 return new RowsOfFields($table);
73 * Interactively filter the watchdog message listing.
75 * @command watchdog:list
76 * @param $substring A substring to look search in error messages.
77 * @option count The number of messages to show. Defaults to 10.
78 * @option extended Return extended information about each message.
79 * @option severity Restrict to messages of a given severity level.
80 * @option type Restrict to messages of a given type.
81 * @usage drush watchdog-list
82 * Prompt for message type or severity, then run watchdog-show.
83 * @aliases wd-list,watchdog-list
84 * @hidden-options type,severity
85 * @validate-module-enabled dblog
95 * @default-fields wid,date,type,severity,message
96 * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
98 public function watchdogList($substring = '', $options = ['format' => 'table', 'count' => 10, 'extended' => false])
100 return $this->show($substring, $options);
104 * @hook interact watchdog-list
105 * @throws \Drush\Exceptions\UserAbortException
107 public function interactList($input, $output)
110 $choices['-- types --'] = dt('== message types ==');
111 $types = $this->messageTypes();
112 foreach ($types as $key => $type) {
113 $choices[$key] = $type;
115 $choices['-- levels --'] = dt('== severity levels ==');
116 $severities = RfcLogLevel::getLevels();
118 foreach ($severities as $key => $value) {
119 $choices[$key] = $value;
121 $option = $this->io()->choice(dt('Select a message type or severity level'), $choices);
122 if (isset($types[$option])) {
123 $input->setOption('type', $types[$option]);
125 $input->setOption('severity', $option);
130 * Delete watchdog log records.
132 * @command watchdog:delete
133 * @param $substring Delete all log records with this text in the messages.
134 * @option severity Delete messages of a given severity level.
135 * @option type Delete messages of a given type.
136 * @usage drush watchdog:delete all
137 * Delete all messages.
138 * @usage drush watchdog:delete 64
139 * Delete messages with id 64.
140 * @usage drush watchdog:delete "cron run succesful"
141 * Delete messages containing the string "cron run succesful".
142 * @usage drush watchdog:delete --severity=notice
143 * Delete all messages with a severity of notice.
144 * @usage drush watchdog:delete --type=cron
145 * Delete all messages of type cron.
146 * @aliases wd-del,wd-delete,wd,watchdog-delete
147 * @validate-module-enabled dblog
150 public function delete($substring = '', $options = ['severity' => self::REQ, 'type' => self::REQ])
152 if ($substring == 'all') {
153 $this->output()->writeln(dt('All watchdog messages will be deleted.'));
154 if (!$this->io()->confirm(dt('Do you really want to continue?'))) {
155 throw new UserAbortException();
157 $ret = Database::getConnection()->truncate('watchdog')->execute();
158 $this->logger()->success(dt('All watchdog messages have been deleted.'));
159 } else if (is_numeric($substring)) {
160 $this->output()->writeln(dt('Watchdog message #!wid will be deleted.', ['!wid' => $substring]));
161 if (!$this->io()->confirm(dt('Do you want to continue?'))) {
162 throw new UserAbortException();
164 $affected_rows = Database::getConnection()->delete('watchdog')->condition('wid', $substring)->execute();
165 if ($affected_rows == 1) {
166 $this->logger()->success(dt('Watchdog message #!wid has been deleted.', ['!wid' => $substring]));
168 throw new \Exception(dt('Watchdog message #!wid does not exist.', ['!wid' => $substring]));
171 if ((!isset($substring))&&(!isset($options['type']))&&(!isset($options['severity']))) {
172 throw new \Exception(dt('No options provided.'));
174 $where = $this->where($options['type'], $options['severity'], $substring, 'OR');
175 $this->output()->writeln(dt('All messages with !where will be deleted.', ['!where' => preg_replace("/message LIKE %$substring%/", "message body containing '$substring'", strtr($where['where'], $where['args']))]));
176 if (!$this->io()->confirm(dt('Do you want to continue?'))) {
177 throw new UserAbortException();
179 $affected_rows = Database::getConnection()->delete('watchdog')
180 ->where($where['where'], $where['args'])
182 $this->logger()->success(dt('!affected_rows watchdog messages have been deleted.', ['!affected_rows' => $affected_rows]));
187 * Show one log record by ID.
189 * @command watchdog:show-one
190 * @param $id Watchdog Id
191 * @aliases wd-one,watchdog-show-one
192 * @validate-module-enabled dblog
194 * @return \Consolidation\OutputFormatters\StructuredData\PropertyList
196 public function showOne($id, $options = ['format' => 'yaml'])
198 $rsc = Database::getConnection()->select('watchdog', 'w')
200 ->condition('wid', (int)$id)
203 $result = $rsc->fetchObject();
205 throw new \Exception(dt('Watchdog message #!wid not found.', ['!wid' => $id]));
207 return new PropertyList($this->formatResult($result));
211 * Build a WHERE snippet based on given parameters.
214 * String. Valid watchdog type.
216 * Int or String for a valid watchdog severity message.
218 * String. Value to filter watchdog messages by.
220 * ('AND', 'OR'). Criteria for the WHERE snippet.
222 * An array with structure ('where' => string, 'args' => array())
224 protected function where($type = null, $severity = null, $filter = null, $criteria = 'AND')
229 $types = $this->messageTypes();
230 if (array_search($type, $types) === false) {
231 $msg = "Unrecognized message type: !type.\nRecognized types are: !types.";
232 throw new \Exception(dt($msg, ['!type' => $type, '!types' => implode(', ', $types)]));
234 $conditions[] = "type = :type";
235 $args[':type'] = $type;
237 if (isset($severity)) {
238 $severities = RfcLogLevel::getLevels();
239 if (isset($severities[$severity])) {
241 } elseif (($key = array_search($severity, $severities)) !== false) {
246 if ($level === false) {
247 foreach ($severities as $key => $value) {
248 $levels[] = "$value($key)";
250 $msg = "Unknown severity level: !severity.\nValid severity levels are: !levels.";
251 throw new \Exception(dt($msg, ['!severity' => $severity, '!levels' => implode(', ', $levels)]));
253 $conditions[] = 'severity = :severity';
254 $args[':severity'] = $level;
257 $conditions[] = "message LIKE :filter";
258 $args[':filter'] = '%'.$filter.'%';
261 $where = implode(" $criteria ", $conditions);
263 return ['where' => $where, 'args' => $args];
267 * Format a watchdog database row.
270 * Array. A database result object.
272 * Boolean. Return extended message details.
274 * Array. The result object with some attributes themed.
276 protected function formatResult($result, $extended = false)
279 $severities = RfcLogLevel::getLevels();
280 $result->severity = trim(DrupalUtil::drushRender($severities[$result->severity]));
283 $result->date = format_date($result->timestamp, 'custom', 'd/M H:i');
284 unset($result->timestamp);
287 $variables = $result->variables;
288 if (is_string($variables)) {
289 $variables = unserialize($variables);
291 if (is_array($variables)) {
292 $result->message = strtr($result->message, $variables);
294 unset($result->variables);
295 $message_length = 188;
297 // Print all the data available
299 // Possible empty values.
300 if (empty($result->link)) {
301 unset($result->link);
303 if (empty($result->referer)) {
304 unset($result->referer);
307 if ($account = user_load($result->uid)) {
308 $result->username = $account->name;
310 $result->username = dt('Anonymous');
313 $message_length = PHP_INT_MAX;
315 $result->message = Unicode::truncate(strip_tags(Html::decodeEntities($result->message)), $message_length, false, false);
321 * Helper function to obtain the message types based on drupal version.
324 * Array of watchdog message types.
326 public static function messageTypes()
328 return _dblog_get_message_types();