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