Completed
Push — 15-request_grouping ( 503575...2e9404 )
by Frédéric G.
03:21
created

Logger::log()   C

Complexity

Conditions 7
Paths 25

Size

Total Lines 79
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 3
Metric Value
cc 7
eloc 52
c 8
b 0
f 3
nc 25
nop 3
dl 0
loc 79
rs 6.5649

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Drupal\mongodb_watchdog;
4
5
use Drupal\Component\Render\MarkupInterface;
6
use Drupal\Component\Utility\Unicode;
7
use Drupal\Component\Utility\Xss;
8
use Drupal\Core\Config\ConfigFactoryInterface;
9
use Drupal\Core\Logger\LogMessageParserInterface;
10
use MongoDB\Database;
11
use MongoDB\Driver\Exception\InvalidArgumentException;
12
use Psr\Log\AbstractLogger;
13
use Symfony\Component\HttpFoundation\RequestStack;
14
15
/**
16
 * Class Logger is a PSR/3 Logger using a MongoDB data store.
17
 *
18
 * @package Drupal\mongodb_watchdog
19
 */
20
class Logger extends AbstractLogger {
21
  const CONFIG_NAME = 'mongodb_watchdog.settings';
22
23
  const TEMPLATE_COLLECTION = 'watchdog';
24
  const EVENT_COLLECTION_PREFIX = 'watchdog_event_';
25
  const EVENT_COLLECTIONS_PATTERN = '^watchdog_event_[[:xdigit:]]{32}$';
26
27
  const LEGACY_TYPE_MAP = [
28
    'typeMap' => [
29
      'array' => 'array',
30
      'document' => 'array',
31
      'root' => 'array',
32
    ],
33
  ];
34
35
  /**
36
   * The logger storage.
37
   *
38
   * @var \MongoDB\Database
39
   */
40
  protected $database;
41
42
  /**
43
   * The limit for the capped event collections.
44
   *
45
   * @var int
46
   */
47
  protected $items;
48
49
  /**
50
   * The minimum logging level.
51
   *
52
   * @var int
53
   */
54
  protected $limit;
55
56
  /**
57
   * The message's placeholders parser.
58
   *
59
   * @var \Drupal\Core\Logger\LogMessageParserInterface
60
   */
61
  protected $parser;
62
63
  /**
64
   * Logger constructor.
65
   *
66
   * @param \MongoDB\Database $database
67
   *   The database object.
68
   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
69
   *   The parser to use when extracting message variables.
70
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
71
   *   The core config_factory service.
72
   * @param \Symfony\Component\HttpFoundation\RequestStack $stack
73
   *   The core request_stack service.
74
   */
75
  public function __construct(Database $database, LogMessageParserInterface $parser, ConfigFactoryInterface $config_factory, RequestStack $stack) {
76
    $this->database = $database;
77
    $this->parser = $parser;
78
    $this->requestStack = $stack;
79
80
    $config = $config_factory->get(static::CONFIG_NAME);
81
    $this->limit = $config->get('limit');
82
    $this->items = $config->get('items');
83
    $this->requestTracking = $config->get('request_tracking');
84
  }
85
86
  /**
87
   * Fill in the log_entry function, file, and line.
88
   *
89
   * @param array $log_entry
90
   *   An event information to be logger.
91
   * @param array $backtrace
92
   *   A call stack.
93
   */
94
  protected function enhanceLogEntry(array &$log_entry, array $backtrace) {
95
    // Create list of functions to ignore in backtrace.
96
    static $ignored = array(
97
      'call_user_func_array' => 1,
98
      '_drupal_log_error' => 1,
99
      '_drupal_error_handler' => 1,
100
      '_drupal_error_handler_real' => 1,
101
      'Drupal\mongodb_watchdog\Logger::log' => 1,
102
      'Drupal\Core\Logger\LoggerChannel::log' => 1,
103
      'Drupal\Core\Logger\LoggerChannel::alert' => 1,
104
      'Drupal\Core\Logger\LoggerChannel::critical' => 1,
105
      'Drupal\Core\Logger\LoggerChannel::debug' => 1,
106
      'Drupal\Core\Logger\LoggerChannel::emergency' => 1,
107
      'Drupal\Core\Logger\LoggerChannel::error' => 1,
108
      'Drupal\Core\Logger\LoggerChannel::info' => 1,
109
      'Drupal\Core\Logger\LoggerChannel::notice' => 1,
110
      'Drupal\Core\Logger\LoggerChannel::warning' => 1,
111
    );
112
113
    foreach ($backtrace as $bt) {
114
      if (isset($bt['function'])) {
115
        $function = empty($bt['class']) ? $bt['function'] : $bt['class'] . '::' . $bt['function'];
116
        if (empty($ignored[$function])) {
117
          $log_entry['%function'] = $function;
118
          /* Some part of the stack, like the line or file info, may be missing.
119
           *
120
           * @see http://goo.gl/8s75df
121
           *
122
           * No need to fetch the line using reflection: it would be redundant
123
           * with the name of the function.
124
           */
125
          $log_entry['%line'] = isset($bt['line']) ? $bt['line'] : NULL;
126
          if (empty($bt['file'])) {
127
            $reflected_method = new \ReflectionMethod($function);
128
            $bt['file'] = $reflected_method->getFileName();
129
          }
130
131
          $log_entry['%file'] = $bt['file'];
132
          break;
133
        }
134
        elseif ($bt['function'] == '_drupal_exception_handler') {
135
          $e = $bt['args'][0];
136
          $this->enhanceLogEntry($log_entry, $e->getTrace());
137
        }
138
      }
139
    }
140
  }
141
142
  /**
143
   * {@inheritdoc}
144
   */
145
  public function log($level, $template, array $context = []) {
146
    if ($level > $this->limit) {
147
      return;
148
    }
149
150
    // Convert PSR3-style messages to SafeMarkup::format() style, so they can be
151
    // translated too in runtime.
152
    $message_placeholders = $this->parser->parseMessagePlaceholders($template, $context);
153
154
    // If code location information is all present, as for errors/exceptions,
155
    // then use it to build the message template id.
156
    $type = $context['channel'];
157
    $location_info = [
158
      '%type' => 1,
159
      '@message' => 1,
160
      '%function' => 1,
161
      '%file' => 1,
162
      '%line' => 1,
163
    ];
164
    if (!empty(array_diff_key($location_info, $message_placeholders))) {
165
      $this->enhanceLogEntry($message_placeholders, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10));
166
    }
167
    $file = $message_placeholders['%file'];
168
    $line = $message_placeholders['%line'];
169
    $function = $message_placeholders['%function'];
170
    $key = "${type}:${level}:${file}:${line}:${function}";
171
    $template_id = md5($key);
172
173
    $selector = ['_id' => $template_id];
174
    $update = [
175
      '_id' => $template_id,
176
      'type' => Unicode::substr($context['channel'], 0, 64),
177
      'message' => $template,
178
      'severity' => $level,
179
    ];
180
    $options = ['upsert' => TRUE];
181
    $template_result = $this->database
182
      ->selectCollection(static::TEMPLATE_COLLECTION)
183
      ->replaceOne($selector, $update, $options);
184
185
    $event_collection = $this->eventCollection($template_id);
186
    if ($template_result->getUpsertedCount()) {
187
      // Capped collections are actually size-based, not count-based, so "items"
188
      // is only a maximum, assuming event documents weigh 1kB, but the actual
189
      // number of items stored may be lower if items are heavier.
190
      // We do not use 'autoindexid' for greater speed, because:
191
      // - it does not work on replica sets,
192
      // - it is deprecated in MongoDB 3.2 and going away in 3.4.
193
      $options = [
194
        'capped' => TRUE,
195
        'size' => $this->items * 1024,
196
        'max' => $this->items,
197
      ];
198
      $this->database->createCollection($event_collection->getCollectionName(), $options);
199
    }
200
201
    foreach ($message_placeholders as &$placeholder) {
202
      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...
203
        $placeholder = Xss::filterAdmin($placeholder);
204
      }
205
    }
206
    $event = [
207
      'hostname' => Unicode::substr($context['ip'], 0, 128),
208
      'link' => $context['link'],
209
      'location' => $context['request_uri'],
210
      'referer' => $context['referer'],
211
      'timestamp' => $context['timestamp'],
212
      'user' => ['uid' => $context['uid']],
213
      'variables' => $message_placeholders,
214
    ];
215
    if ($this->requestTracking) {
216
      // Fetch the current request on each event to support subrequest nesting.
217
      $event['requestTracking_id'] = $this->requestStack
218
        ->getCurrentRequest()
219
        ->server
220
        ->get('UNIQUE_ID');
221
    }
222
    $event_collection->insertOne($event);
223
  }
224
225
  /**
226
   * List the event collections.
227
   *
228
   * @return \MongoDB\Collection[]
229
   *   The collections with a name matching the event pattern.
230
   */
231
  public function eventCollections() {
232
    echo static::EVENT_COLLECTIONS_PATTERN;
233
    $options = [
234
      'filter' => [
235
        'name' => ['$regex' => static::EVENT_COLLECTIONS_PATTERN],
236
      ],
237
    ];
238
    $result = iterator_to_array($this->database->listCollections($options));
239
    return $result;
240
  }
241
242
  /**
243
   * Return a collection, given its template id.
244
   *
245
   * @param string $template_id
246
   *   The string representation of a template \MongoId.
247
   *
248
   * @return \MongoDB\Collection
249
   *   A collection object for the specified template id.
250
   */
251
  public function eventCollection($template_id) {
252
    $collection_name = static::EVENT_COLLECTION_PREFIX . $template_id;
253
    if (!preg_match('/' . static::EVENT_COLLECTIONS_PATTERN . '/', $collection_name)) {
254
      throw new InvalidArgumentException(t('Invalid watchdog template id `@id`.', [
255
        '@id' => $collection_name,
256
      ]));
257
    }
258
    $collection = $this->database->selectCollection($collection_name);
259
    return $collection;
260
  }
261
262
  /**
263
   * Ensure indexes are set on the collections.
264
   *
265
   * First index is on <line, timestamp> instead of <function, line, timestamp>,
266
   * because we write to this collection a lot, and the smaller index on two
267
   * numbers should be much faster to create than one with a string included.
268
   */
269
  public function ensureIndexes() {
270
    $templates = $this->database->selectCollection(static::TEMPLATE_COLLECTION);
271
    $indexes = [
272
      // Index for adding/updating increments.
273
      [
274
        'name' => 'for-increments',
275
        'key' => ['line' => 1, 'timestamp' => -1],
276
      ],
277
278
      // Index for admin page without filters.
279
      [
280
        'name' => 'admin-no-filters',
281
        'key' => ['timestamp' => -1],
282
      ],
283
284
      // Index for admin page filtering by type.
285
      [
286
        'name' => 'admin-by-type',
287
        'key' => ['type' => 1, 'timestamp' => -1],
288
      ],
289
290
      // Index for admin page filtering by severity.
291
      [
292
        'name' => 'admin-by-severity',
293
        'key' => ['severity' => 1, 'timestamp' => -1],
294
      ],
295
296
      // Index for admin page filtering by type and severity.
297
      [
298
        'name' => 'admin-by-both',
299
        'key' => ['type' => 1, 'severity' => 1, 'timestamp' => -1],
300
      ],
301
    ];
302
    $templates->createIndexes($indexes);
303
  }
304
305
  /**
306
   * Return the event templates collection.
307
   *
308
   * @return \MongoDB\Collection
309
   *   The collection.
310
   */
311
  public function templateCollection() {
312
    return $this->database->selectCollection(static::TEMPLATE_COLLECTION);
313
  }
314
315
}
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...
316