Log.php
9.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Log;
defined('JPATH_PLATFORM') or die;
/**
* Joomla! Log Class
*
* This class hooks into the global log configuration settings to allow for user configured
* logging events to be sent to where the user wishes them to be sent. On high load sites
* Syslog is probably the best (pure PHP function), then the text file based loggers (CSV, W3c
* or plain Formattedtext) and finally MySQL offers the most features (e.g. rapid searching)
* but will incur a performance hit due to INSERT being issued.
*
* @since 1.7.0
*/
class Log
{
/**
* All log priorities.
*
* @var integer
* @since 1.7.0
*/
const ALL = 30719;
/**
* The system is unusable.
*
* @var integer
* @since 1.7.0
*/
const EMERGENCY = 1;
/**
* Action must be taken immediately.
*
* @var integer
* @since 1.7.0
*/
const ALERT = 2;
/**
* Critical conditions.
*
* @var integer
* @since 1.7.0
*/
const CRITICAL = 4;
/**
* Error conditions.
*
* @var integer
* @since 1.7.0
*/
const ERROR = 8;
/**
* Warning conditions.
*
* @var integer
* @since 1.7.0
*/
const WARNING = 16;
/**
* Normal, but significant condition.
*
* @var integer
* @since 1.7.0
*/
const NOTICE = 32;
/**
* Informational message.
*
* @var integer
* @since 1.7.0
*/
const INFO = 64;
/**
* Debugging message.
*
* @var integer
* @since 1.7.0
*/
const DEBUG = 128;
/**
* The global Log instance.
*
* @var Log
* @since 1.7.0
*/
protected static $instance;
/**
* Container for Logger configurations.
*
* @var array
* @since 1.7.0
*/
protected $configurations = array();
/**
* Container for Logger objects.
*
* @var Logger[]
* @since 1.7.0
*/
protected $loggers = array();
/**
* Lookup array for loggers.
*
* @var array
* @since 1.7.0
*/
protected $lookup = array();
/**
* Constructor.
*
* @since 1.7.0
*/
protected function __construct()
{
}
/**
* Method to add an entry to the log.
*
* @param mixed $entry The LogEntry object to add to the log or the message for a new LogEntry object.
* @param integer $priority Message priority.
* @param string $category Type of entry
* @param string $date Date of entry (defaults to now if not specified or blank)
* @param array $context An optional array with additional message context.
*
* @return void
*
* @since 1.7.0
*/
public static function add($entry, $priority = self::INFO, $category = '', $date = null, array $context = array())
{
// Automatically instantiate the singleton object if not already done.
if (empty(static::$instance))
{
static::setInstance(new Log);
}
// If the entry object isn't a LogEntry object let's make one.
if (!($entry instanceof LogEntry))
{
$entry = new LogEntry((string) $entry, $priority, $category, $date, $context);
}
static::$instance->addLogEntry($entry);
}
/**
* Add a logger to the Log instance. Loggers route log entries to the correct files/systems to be logged.
*
* @param array $options The object configuration array.
* @param integer $priorities Message priority
* @param array $categories Types of entry
* @param boolean $exclude If true, all categories will be logged except those in the $categories array
*
* @return void
*
* @since 1.7.0
*/
public static function addLogger(array $options, $priorities = self::ALL, $categories = array(), $exclude = false)
{
// Automatically instantiate the singleton object if not already done.
if (empty(static::$instance))
{
static::setInstance(new Log);
}
static::$instance->addLoggerInternal($options, $priorities, $categories, $exclude);
}
/**
* Add a logger to the Log instance. Loggers route log entries to the correct files/systems to be logged.
* This method allows you to extend Log completely.
*
* @param array $options The object configuration array.
* @param integer $priorities Message priority
* @param array $categories Types of entry
* @param boolean $exclude If true, all categories will be logged except those in the $categories array
*
* @return void
*
* @since 1.7.0
*/
protected function addLoggerInternal(array $options, $priorities = self::ALL, $categories = array(), $exclude = false)
{
// The default logger is the formatted text log file.
if (empty($options['logger']))
{
$options['logger'] = 'formattedtext';
}
$options['logger'] = strtolower($options['logger']);
// Special case - if a Closure object is sent as the callback (in case of CallbackLogger)
// Closure objects are not serializable so swap it out for a unique id first then back again later
if (isset($options['callback']))
{
if (is_a($options['callback'], 'closure'))
{
$callback = $options['callback'];
$options['callback'] = spl_object_hash($options['callback']);
}
elseif (is_array($options['callback']) && count($options['callback']) == 2 && is_object($options['callback'][0]))
{
$callback = $options['callback'];
$options['callback'] = spl_object_hash($options['callback'][0]) . '::' . $options['callback'][1];
}
}
// Generate a unique signature for the Log instance based on its options.
$signature = md5(serialize($options));
// Now that the options array has been serialized, swap the callback back in
if (isset($callback))
{
$options['callback'] = $callback;
}
// Register the configuration if it doesn't exist.
if (empty($this->configurations[$signature]))
{
$this->configurations[$signature] = $options;
}
$this->lookup[$signature] = (object) array(
'priorities' => $priorities,
'categories' => array_map('strtolower', (array) $categories),
'exclude' => (bool) $exclude,
);
}
/**
* Creates a delegated PSR-3 compatible logger from the current singleton instance. This method always returns a new delegated logger.
*
* @return DelegatingPsrLogger
*
* @since 3.8.0
*/
public static function createDelegatedLogger()
{
// Ensure a singleton instance has been created first
if (empty(static::$instance))
{
static::setInstance(new static);
}
return new DelegatingPsrLogger(static::$instance);
}
/**
* Returns a reference to the a Log object, only creating it if it doesn't already exist.
* Note: This is principally made available for testing and internal purposes.
*
* @param Log $instance The logging object instance to be used by the static methods.
*
* @return void
*
* @since 1.7.0
*/
public static function setInstance($instance)
{
if (($instance instanceof Log) || $instance === null)
{
static::$instance = & $instance;
}
}
/**
* Method to add an entry to the appropriate loggers.
*
* @param LogEntry $entry The LogEntry object to send to the loggers.
*
* @return void
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function addLogEntry(LogEntry $entry)
{
// Find all the appropriate loggers based on priority and category for the entry.
$loggers = $this->findLoggers($entry->priority, $entry->category);
foreach ((array) $loggers as $signature)
{
// Attempt to instantiate the logger object if it doesn't already exist.
if (empty($this->loggers[$signature]))
{
$class = __NAMESPACE__ . '\\Logger\\' . ucfirst($this->configurations[$signature]['logger']) . 'Logger';
if (!class_exists($class))
{
throw new \RuntimeException('Unable to create a Logger instance: ' . $class);
}
$this->loggers[$signature] = new $class($this->configurations[$signature]);
}
// Add the entry to the logger.
$this->loggers[$signature]->addEntry(clone $entry);
}
}
/**
* Method to find the loggers to use based on priority and category values.
*
* @param integer $priority Message priority.
* @param string $category Type of entry
*
* @return array The array of loggers to use for the given priority and category values.
*
* @since 1.7.0
*/
protected function findLoggers($priority, $category)
{
$loggers = array();
// Sanitize inputs.
$priority = (int) $priority;
$category = strtolower($category);
// Let's go iterate over the loggers and get all the ones we need.
foreach ((array) $this->lookup as $signature => $rules)
{
// Check to make sure the priority matches the logger.
if ($priority & $rules->priorities)
{
if ($rules->exclude)
{
// If either there are no set categories or the category (including the empty case) is not in the list of excluded categories, add this logger.
if (empty($rules->categories) || !in_array($category, $rules->categories))
{
$loggers[] = $signature;
}
}
else
{
// If either there are no set categories (meaning all) or the specific category is set, add this logger.
if (empty($rules->categories) || in_array($category, $rules->categories))
{
$loggers[] = $signature;
}
}
}
}
return $loggers;
}
}