Completed
Push — 8.x-2.x ( 0f1fa4...cba9bd )
by Frédéric G.
03:05
created

Logger::templateCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Drupal\mongodb_watchdog;
4
5
use Drupal\Component\Render\MarkupInterface;
6
use Drupal\Component\Utility\SafeMarkup;
7
use Drupal\Component\Utility\Unicode;
8
use Drupal\Component\Utility\Xss;
9
use Drupal\Core\Logger\LogMessageParserInterface;
10
use MongoDB\Database;
11
use MongoDB\Driver\Exception\InvalidArgumentException;
12
use Psr\Log\AbstractLogger;
13
14
/**
15
 * Class Logger is a PSR/3 Logger using a MongoDB data store.
16
 *
17
 * @package Drupal\mongodb_watchdog
18
 */
19
class Logger extends AbstractLogger {
20
21
  const TEMPLATE_COLLECTION = 'watchdog';
22
  const EVENT_COLLECTION_PREFIX = 'watchdog_event_';
23
  const EVENT_COLLECTIONS_PATTERN = '^watchdog_event_[[:xdigit:]]{32}$';
24
25
  /**
26
   * The logger storage.
27
   *
28
   * @var \MongoDB\Database
29
   */
30
  protected $database;
31
32
  /**
33
   * The message's placeholders parser.
34
   *
35
   * @var \Drupal\Core\Logger\LogMessageParserInterface
36
   */
37
  protected $parser;
38
39
  /**
40
   * Constructs a Logger object.
41
   *
42
   * @param \MongoDB\Database $database
43
   *   The database object.
44
   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
45
   *   The parser to use when extracting message variables.
46
   */
47
  public function __construct(Database $database, LogMessageParserInterface $parser) {
48
    $this->database = $database;
49
    $this->parser = $parser;
50
  }
51
52
  /**
53
   * Fill in the log_entry function, file, and line.
54
   *
55
   * @param array $log_entry
56
   *   An event information to be logger.
57
   * @param array $backtrace
58
   *   A call stack.
59
   */
60
  function enhanceLogEntry(&$log_entry, $backtrace) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
introduced by
Type hint "array" missing for $log_entry
Loading history...
introduced by
Type hint "array" missing for $backtrace
Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for enhanceLogEntry.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
61
    // Create list of functions to ignore in backtrace.
62
    static $ignored = array(
63
      'call_user_func_array' => 1,
64
      '_drupal_log_error' => 1,
65
      '_drupal_error_handler' => 1,
66
      '_drupal_error_handler_real' => 1,
67
      // 'theme_render_template' => 1,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
68
      'Drupal\mongodb_watchdog\Logger::log' => 1,
69
      'Drupal\Core\Logger\LoggerChannel::log' => 1,
70
      'Drupal\Core\Logger\LoggerChannel::alert' => 1,
71
      'Drupal\Core\Logger\LoggerChannel::critical' => 1,
72
      'Drupal\Core\Logger\LoggerChannel::debug' => 1,
73
      'Drupal\Core\Logger\LoggerChannel::emergency' => 1,
74
      'Drupal\Core\Logger\LoggerChannel::error' => 1,
75
      'Drupal\Core\Logger\LoggerChannel::info' => 1,
76
      'Drupal\Core\Logger\LoggerChannel::notice' => 1,
77
      'Drupal\Core\Logger\LoggerChannel::warning' => 1,
78
    );
79
80
    foreach ($backtrace as $bt) {
81
      if (isset($bt['function'])) {
82
        $function = empty($bt['class']) ? $bt['function'] : $bt['class'] . '::' . $bt['function'];
83
        if (empty($ignored[$function])) {
84
          $log_entry['%function'] = $function;
85
          /* Some part of the stack, like the line or file info, may be missing.
86
           * @see http://stackoverflow.com/questions/4581969/why-is-debug-backtrace-not-including-line-number-sometimes
0 ignored issues
show
introduced by
Line exceeds 80 characters; contains 119 characters
Loading history...
87
           * No need to fetch the line using reflection: it would be redundant
88
           * with the name of the function.
89
           */
90
          $log_entry['%line'] = isset($bt['line']) ? $bt['line'] : NULL;
91
          if (empty($bt['file'])) {
92
            $reflected_method = new \ReflectionMethod($function);
93
            $bt['file'] = $reflected_method->getFileName();
94
          }
95
96
          $log_entry['%file'] = $bt['file'];
97
          break;
98
        }
99
        elseif ($bt['function'] == '_drupal_exception_handler') {
100
          $e = $bt['args'][0];
101
          $this->enhanceLogEntry($log_entry, $e->getTrace());
102
        }
103
      }
104
    }
105
  }
106
107
  /**
108
   * {@inheritdoc}
109
   */
110
  public function log($level, $template, array $context = []) {
111
    // Convert PSR3-style messages to SafeMarkup::format() style, so they can be
112
    // translated too in runtime.
113
    $message_placeholders = $this->parser->parseMessagePlaceholders($template, $context);
114
115
    // If code location information is all present, as for errors/exceptions,
116
    // then use it to build the message template id.
117
    $type = $context['channel'];
118
    $location_info = [
119
      '%type' => 1,
120
      '@message' => 1,
121
      '%function' => 1,
122
      '%file' => 1,
123
      '%line' => 1,
124
    ];
125
    if (!empty(array_diff_key($location_info, $message_placeholders))) {
126
      $this->enhanceLogEntry($message_placeholders, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10));
127
    }
128
    $file = $message_placeholders['%file'];
129
    $line = $message_placeholders['%line'];
130
    $function = $message_placeholders['%function'];
131
    $key = "${type}:${level}:${file}:${line}:${function}";
132
    $template_id = md5($key);
133
134
    $selector = [ '_id' => $template_id ];
135
    $update = [
136
      '_id' => $template_id,
137
      'type' => Unicode::substr($context['channel'], 0, 64),
138
      'message' => $template,
139
      'severity' => $level,
140
    ];
141
    $options = [ 'upsert' => TRUE ];
142
    $template_result = $this->database
143
      ->selectCollection(static::TEMPLATE_COLLECTION)
144
      ->replaceOne($selector, $update, $options);
145
    $template_result->getUpsertedId();
146
147
    $event_collection = $this->eventCollection($template_id);
148
    foreach ($message_placeholders as &$placeholder) {
149
      if ($placeholder instanceof MarkupInterface) {
0 ignored issues
show
Bug introduced by
The class Drupal\Component\Render\MarkupInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
150
        $placeholder = Xss::filterAdmin($placeholder);
151
      }
152
    }
153
    $event = [
154
      'hostname' => Unicode::substr($context['ip'], 0, 128),
155
      'link' => $context['link'],
156
      'location' => $context['request_uri'],
157
      'referer' => $context['referer'],
158
      'timestamp' => $context['timestamp'],
159
      'user' => ['uid' => $context['uid']],
160
      'variables' => $message_placeholders,
161
    ];
162
    $event_collection->insertOne($event);
163
  }
164
165
  /**
166
   * List the event collections.
167
   *
168
   * @return \MongoDB\Collection[]
169
   *   The collections with a name matching the event pattern.
170
   */
171
  public function eventCollections() {
172
    echo static::EVENT_COLLECTIONS_PATTERN;
173
    $options = [
174
      'filter' => [
175
        'name' => ['$regex' => static::EVENT_COLLECTIONS_PATTERN],
176
      ],
177
    ];
178
    $result = iterator_to_array($this->database->listCollections($options));
179
    return $result;
180
  }
181
182
  /**
183
   * Return a collection, given its template id.
184
   *
185
   * @param string $template_id
186
   *   The string representation of a template \MongoId.
187
   *
188
   * @return \MongoDB\Collection
189
   *   A collection object for the specified template id.
190
   */
191
  public function eventCollection($template_id) {
192
    $collection_name = static::EVENT_COLLECTION_PREFIX . $template_id;
193
    if (!preg_match('/' . static::EVENT_COLLECTIONS_PATTERN . '/', $collection_name)) {
194
      throw new InvalidArgumentException(t('Invalid watchdog template id `@id`.', [
195
        '@id' => $collection_name,
196
      ]));
197
    }
198
    $collection = $this->database->selectCollection($collection_name);
199
    return $collection;
200
  }
201
202
  /**
203
   * Ensure indexes are set on the collections.
204
   *
205
   * First index is on <line, timestamp> instead of <function, line, timestamp>,
206
   * because we write to this collection a lot, and the smaller index on two
207
   * numbers should be much faster to create than one with a string included.
208
   */
209
  public function ensureIndexes() {
210
    $templates = $this->database->selectCollection(static::TEMPLATE_COLLECTION);
211
    $indexes = [
212
      // Index for adding/updating increments.
213
      [
214
        'name' => 'for-increments',
215
        'key' => ['line' => 1, 'timestamp' => -1],
216
      ],
217
218
      // Index for admin page without filters.
219
      [
220
        'name' => 'admin-no-filters',
221
        'key' => ['timestamp' => -1],
222
      ],
223
224
      // Index for admin page filtering by type.
225
      [
226
        'name' => 'admin-by-type',
227
        'key' => ['type' => 1, 'timestamp' => -1],
228
      ],
229
230
      // Index for admin page filtering by severity.
231
      [
232
        'name' => 'admin-by-severity',
233
        'key' => ['severity' => 1, 'timestamp' => -1],
234
      ],
235
236
      // Index for admin page filtering by type and severity.
237
      [
238
        'name' => 'admin-by-both',
239
        'key' => ['type' => 1, 'severity' => 1, 'timestamp' => -1],
240
      ],
241
    ];
242
    $templates->createIndexes($indexes);
243
  }
244
245
  /**
246
   * Return the event templates collection.
247
   *
248
   * @return \MongoDB\Collection
249
   *   The collection.
250
   */
251
  public function templateCollection() {
252
    return $this->database->selectCollection(static::TEMPLATE_COLLECTION);
253
  }
254
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
255