1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('ext/function');
  9 
 10 // ..........................................................
 11 // CONSTANTS
 12 //
 13 
 14 // Implementation note:  We use two spaces after four-letter prefixes and one
 15 // after five-letter prefixes so things align in monospaced consoles.
 16 
 17 /**
 18   If {@link SC.Logger.format} is true, this delimiter will be put between arguments.
 19 
 20   @type String
 21 */
 22 SC.LOGGER_LOG_DELIMITER = ", ";
 23 
 24 /**
 25   If {@link SC.Logger.error} falls back onto {@link SC.Logger.log}, this will be
 26   prepended to the output.
 27 
 28   @type String
 29 */
 30 SC.LOGGER_LOG_ERROR = "ERROR: ";
 31 
 32 /**
 33   If {@link SC.Logger.info} falls back onto {@link SC.Logger.log}, this will be
 34   prepended to the output.
 35 
 36   @type String
 37 */
 38 SC.LOGGER_LOG_INFO = "INFO:  ";
 39 
 40 /**
 41   If {@link SC.Logger.warn} falls back onto {@link SC.Logger.log}, this will be
 42   prepended to the output.
 43 
 44   @type String
 45 */
 46 SC.LOGGER_LOG_WARN = "WARN:  ";
 47 
 48 /**
 49   If {@link SC.Logger.debug} falls back onto {@link SC.Logger.log}, this will be
 50   prepended to the output.
 51 
 52   @type String
 53 */
 54 SC.LOGGER_LOG_DEBUG = "DEBUG: ";
 55 
 56 /**
 57   If {@link SC.Logger.group} falls back onto {@link SC.Logger.log}, this will
 58   be prepended to the output.
 59 
 60   @type String
 61 */
 62 SC.LOGGER_LOG_GROUP_HEADER = "** %@";       // The variable is the group title
 63 
 64 /**
 65   If the reporter does not support group(), then we’ll add our own indentation
 66   to our output.  This constant represents one level of indentation.
 67 
 68   @type String
 69 */
 70 SC.LOGGER_LOG_GROUP_INDENTATION = "    ";
 71 
 72 /**
 73   When reporting recorded log messages, the timestamp is included with this
 74   prefix.
 75 
 76   @type String
 77 */
 78 SC.LOGGER_RECORDED_LOG_TIMESTAMP_PREFIX = "%@:  ";
 79 
 80 
 81 SC.LOGGER_LEVEL_DEBUG = 'debug';
 82 SC.LOGGER_LEVEL_INFO  = 'info';
 83 SC.LOGGER_LEVEL_WARN  = 'warn';
 84 SC.LOGGER_LEVEL_ERROR = 'error';
 85 SC.LOGGER_LEVEL_NONE  = 'none';
 86 
 87 
 88 
 89 /** @class
 90 
 91   Object to allow for safe logging actions, such as using the browser console.
 92   In addition to being output to the console, logs can be optionally recorded
 93   in memory, to be accessed by your application as appropriate.
 94 
 95   This class also adds in the concept of a “current log level”, which allows
 96   your application to potentially determine a subset of logging messages to
 97   output and/or record.  The order of levels is:
 98 
 99     -  debug        SC.LOGGER_LEVEL_DEBUG
100     -  info         SC.LOGGER_LEVEL_INFO
101     -  warn         SC.LOGGER_LEVEL_WARN
102     -  error        SC.LOGGER_LEVEL_ERROR
103 
104   All messages at the level or “above” will be output/recorded.  So, for
105   example, if you set the level to 'info', all 'info', 'warn', and 'error'
106   messages will be output/recorded, but no 'debug' messages will be.  Also,
107   there are two separate log levels:  one for output, and one for recording.
108   You may wish to only output, say, 'warn' and above, but record everything
109   from 'debug' on up.  (You can also limit the number log messages to record.)
110 
111   This mechanism allows your application to avoid needless output (which has a
112   non-zero cost in many browsers) in the general case, but turning up the log
113   level when necessary for debugging.  Note that there can still be a
114   performance cost for preparing log messages (calling {@link String.fmt},
115   etc.), so it’s still a good idea to be selective about what log messages are
116   output even to 'debug', especially in hot code.
117 
118   Similarly, you should be aware that if you wish to log objects without
119   stringification — using the {@link SC.Logger.debugWithoutFmt} variants — and
120   you enable recording, the “recorded messages” array will hold onto a
121   reference to the arguments, potentially increasing the amount of memory
122   used.
123 
124   As a convenience, this class also adds some shorthand methods to SC:
125 
126     -  SC.debug()   ==>   SC.Logger.debug()
127     -  SC.info()    ==>   SC.Logger.info()
128     -  SC.warn()    ==>   SC.Logger.warn()
129     -  SC.error()   ==>   SC.Logger.error()
130 
131   …although note that no shorthand versions exist for the less-common
132   functions, such as defining groups.
133 
134   The FireFox plugin Firebug was used as a function reference. Please see
135   [Firebug Logging Reference](http://getfirebug.com/logging.html)
136   for further information.
137 
138   @author Colin Campbell
139   @author Benedikt Böhm
140   @author William Kakes
141   @extends SC.Object
142   @since SproutCore 1.0
143   @see <a href="http://getfirebug.com/logging.html">Firebug Logging Reference</a>
144 */
145 SC.Logger = SC.Object.create(
146 	/** @scope SC.Logger.prototype */{
147 
148   // ..........................................................
149   // PROPERTIES
150   //
151 
152   /**
153     An optional prefix that will be prepended to all log messages, but not any
154     group titles.
155 
156     @type String
157   */
158   messagePrefix: null,
159 
160 
161   /**
162     An optional prefix that will be prepended to all log messages that are
163     output to the browser console, but not those that are recorded.  If you
164     specify both this and a 'messagePrefix', both will be output, and only the
165     'messagePrefix' will be recorded.
166 
167     @type String
168   */
169   outputMessagePrefix: null,
170 
171 
172   /**
173     An optional prefix that will be prepended to all log messages that are
174     recorded, but not those that are output to the browser console.  If you
175     specify both this and a 'messagePrefix', both will be recorded, and only the
176     'messagePrefix' will be output to the browser console.
177 
178     @type String
179   */
180   recordedMessagePrefix: null,
181 
182 
183   /**
184     The current log level determining what is output to the reporter object
185     (usually your browser’s console).  Valid values are:
186 
187       -  SC.LOGGER_LEVEL_DEBUG
188       -  SC.LOGGER_LEVEL_INFO
189       -  SC.LOGGER_LEVEL_WARN
190       -  SC.LOGGER_LEVEL_ERROR
191       -  SC.LOGGER_LEVEL_NONE
192 
193     If you do not specify this value, it will default to SC.LOGGER_LEVEL_DEBUG
194     when running in development mode and SC.LOGGER_LEVEL_INFO when running in
195     production mode.
196 
197     @property: {Constant}
198   */
199   logOutputLevel: null,        // If null, set appropriately during init()
200 
201 
202   /**
203     The current log level determining what is recorded to the
204     'recordedLogMessages' buffer.  Valid values are the same as with
205     'logOutputLevel':
206 
207       -  SC.LOGGER_LEVEL_DEBUG
208       -  SC.LOGGER_LEVEL_INFO
209       -  SC.LOGGER_LEVEL_WARN
210       -  SC.LOGGER_LEVEL_ERROR
211       -  SC.LOGGER_LEVEL_NONE
212 
213     If you do not specify this value, it will default to SC.LOGGER_LEVEL_NONE.
214 
215     @type Constant
216   */
217   logRecordingLevel: SC.LOGGER_LEVEL_NONE,
218 
219 
220   /**
221     All recorded log messages.  You generally should not need to interact with
222     this array, as most commonly-used functionality can be achieved via the
223     {@link SC.Logger.outputRecordedLogMessages} and
224     {@link SC.Logger.stringifyRecordedLogMessages} methods.
225 
226     This array will be lazily created when the first message is recorded.
227 
228     Format:
229 
230     For efficiency, each entry in the array is a simple hash rather than a
231     full SC.Object instance.  Furthermore, to minimize memory usage, niceties
232     like “type of entry: message” are avoided; if you need to parse this
233     structure, you can determine which type of entry you’re looking at by
234     checking for the 'message' and 'indentation' fields.
235 <pre>
236     Log entry:
237     {
238       type:               {Constant}     (SC.LOGGER_LEVEL_DEBUG, etc.)
239       message:            {String | Boolean}
240       originalArguments:  {Arguments}    // optional
241       timestamp:          {Date}
242     }
243 
244     Group entry (either beginning or end of):
245     {
246       type:         {Constant}     SC.LOGGER_LEVEL_DEBUG, etc.
247       indentation:  {Number}       The value is the new group indentation level
248       beginGroup:   {Boolean}      Whether this entry is the beginning of a new group (as opposed to the end)
249       title:        {String}       Optional for new groups, and never present for end-of-group
250       timestamp:    {Date}
251     }
252 </pre>
253 
254     @type Array
255   */
256   recordedLogMessages: null,
257 
258 
259   /**
260     If the recording level is set such that messages will be recorded, this is
261     the maximum number of messages that will be saved in the
262     'recordedLogMessages' array.  Any further recorded messages will push
263     older messages out of the array, so the most recent messages will be
264     saved.
265 
266     @type Number
267   */
268   recordedLogMessagesMaximumLength: 500,
269 
270 
271   /**
272     If the recording level is set such that messages will be recorded, this is
273     the minimum number of messages that will be saved whenever the recordings
274     are pruned.  (They are pruned whenever you hit the maximum length, as
275     specified via the 'recordedLogMessagesMaximumLength' property.  This
276     mechanism avoids thrashing the array for each log message once the
277     maximum is reached.)  When pruning, the most recent messages will be saved.
278 
279     @type Number
280   */
281   recordedLogMessagesPruningMinimumLength: 100,
282 
283 
284   /**
285     Whether or not to enable debug logging.  This property exists for
286     backwards compatibility with previous versions of SC.Logger.  In newer
287     code, you should instead set the appropriate output/recording log levels.
288 
289     If this property is set to YES, it will set 'logOutputLevel' to
290     SC.LOGGER_LEVEL_DEBUG.  Otherwise, it will have no effect.
291 
292     @deprecated Set the log level instead.
293     @property: {Boolean}
294   */
295   debugEnabled: NO,
296 
297 
298   /**
299     Computed property that checks for the existence of the reporter object.
300 
301     @type Boolean
302   */
303   exists: function() {
304     return !SC.none(this.get('reporter'));
305   }.property('reporter').cacheable(),
306 
307 
308   /**
309     If console.log does not exist, SC.Logger will use window.alert instead
310     when {@link SC.Logger.log} is invoked.
311 
312     Note that this property has no effect for messages initiated via the
313     debug/info/warn/error methods, on the assumption that it is better to
314     simply utilize the message recording mechanism than put up a bunch of
315     alerts when there is no browser console.
316 
317     @type Boolean
318   */
319   fallBackOnAlert: NO,
320 
321 
322   /**
323     The reporter is the object which implements the actual logging functions.
324 
325     @default The browser’s console
326     @type Object
327   */
328   reporter: console,
329 
330 
331 
332 
333   // ..........................................................
334   // METHODS
335   //
336 
337   /**
338     Logs a debug message to the console and potentially to the recorded
339     array, provided the respective log levels are set appropriately.
340 
341     The first argument must be a string, and if there are any additional
342     arguments, it is assumed to be a format string.  Thus, you can (and
343     should) use it like:
344 
345         SC.Logger.debug("%@:  My debug message", this);       // good
346 
347     …and not:
348 
349         SC.Logger.debug("%@:  My debug message".fmt(this));        // bad
350 
351     The former method can be more efficient because if the log levels are set
352     in such a way that the debug() invocation will be ignored, then the
353     String.fmt() call will never actually be performed.
354 
355     @param {String}              A message or a format string
356     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string
357   */
358   debug: function(message, optionalFormatArgs) {
359     // Implementation note:  To avoid having to put the SC.debug() shorthand
360     // variant inside a function wrapper, we'll avoid 'this'.
361     SC.Logger._handleMessage(SC.LOGGER_LEVEL_DEBUG, YES, message, arguments);
362   },
363 
364 
365   /**
366     Logs a debug message to the console and potentially to the recorded
367     array, provided the respective log levels are set appropriately.
368 
369     Unlike simply debug(), this method does not try to apply String.fmt() to
370     the arguments, and instead passes them directly to the reporter (and
371     stringifies them if recording).  This can be useful if the browser formats
372     a type in a manner more useful to you than you can achieve with
373     String.fmt().
374 
375     @param {String|Array|Function|Object}
376   */
377   debugWithoutFmt: function() {
378     this._handleMessage(SC.LOGGER_LEVEL_DEBUG, NO, null, arguments);
379   },
380 
381 
382   /**
383     Begins a new group in the console and/or in the recorded array provided
384     the respective log levels are set to output/record 'debug' messages.
385     Every message after this call (at any log level) will be indented for
386     readability until a matching {@link SC.Logger.debugGroupEnd} is invoked,
387     and you can create as many levels as you want.
388 
389     Assuming you are using 'debug' messages elsewhere, it is preferable to
390     group them using this method over simply {@link SC.Logger.group} — the log
391     levels could be set such that the 'debug' messages are never seen, and you
392     wouldn’t want an empty/needless group!
393 
394     You can optionally provide a title for the group.  If there are any
395     additional arguments, the first argument is assumed to be a format string.
396     Thus, you can (and should) use it like:
397 
398           SC.Logger.debugGroup("%@:  My debug group", this);       // good
399 
400     …and not:
401 
402           SC.Logger.debugGroup("%@:  My debug group".fmt(this));   // bad
403 
404     The former method can be more efficient because if the log levels are set
405     in such a way that the debug() invocation will be ignored, then the
406     String.fmt() call will never actually be performed.
407 
408     @param {String}  (optional)  A title or format string to display above the group
409     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string as the title
410   */
411   debugGroup: function(message, optionalFormatArgs) {
412     // Implementation note:  To avoid having to put the SC.debugGroup()
413     // shorthand variant inside a function wrapper, we'll avoid 'this'.
414     SC.Logger._handleGroup(SC.LOGGER_LEVEL_DEBUG, message, arguments);
415   },
416 
417 
418   /**
419     Ends a group initiated with {@link SC.Logger.debugGroup}, provided the
420     respective output/recording log levels are set appropriately.
421 
422     @see SC.Logger.debugGroup
423   */
424   debugGroupEnd: function() {
425     // Implementation note:  To avoid having to put the SC.debugGroupEnd()
426     // shorthand variant inside a function wrapper, we'll avoid 'this'.
427     SC.Logger._handleGroupEnd(SC.LOGGER_LEVEL_DEBUG);
428   },
429 
430 
431 
432   /**
433     Logs an informational message to the console and potentially to the
434     recorded array, provided the respective log levels are set appropriately.
435 
436     The first argument must be a string, and if there are any additional
437     arguments, it is assumed to be a format string.  Thus, you can (and
438     should) use it like:
439 
440           SC.Logger.info("%@:  My info message", this);       // good
441 
442     …and not:
443 
444           SC.Logger.info("%@:  My info message".fmt(this));   // bad
445 
446     The former method can be more efficient because if the log levels are set
447     in such a way that the info() invocation will be ignored, then the
448     String.fmt() call will never actually be performed.
449 
450     @param {String}              A message or a format string
451     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string
452   */
453   info: function(message, optionalFormatArgs) {
454     // Implementation note:  To avoid having to put the SC.info() shorthand
455     // variant inside a function wrapper, we'll avoid 'this'.
456     SC.Logger._handleMessage(SC.LOGGER_LEVEL_INFO, YES, message, arguments);
457   },
458 
459 
460   /**
461     Logs an information message to the console and potentially to the recorded
462     array, provided the respective log levels are set appropriately.
463 
464     Unlike simply info(), this method does not try to apply String.fmt() to
465     the arguments, and instead passes them directly to the reporter (and
466     stringifies them if recording).  This can be useful if the browser formats
467     a type in a manner more useful to you than you can achieve with
468     String.fmt().
469 
470     @param {String|Array|Function|Object}
471   */
472   infoWithoutFmt: function() {
473     this._handleMessage(SC.LOGGER_LEVEL_INFO, NO, null, arguments);
474   },
475 
476 
477   /**
478     Begins a new group in the console and/or in the recorded array provided
479     the respective log levels are set to output/record 'info' messages.
480     Every message after this call (at any log level) will be indented for
481     readability until a matching {@link SC.Logger.infoGroupEnd} is invoked,
482     and you can create as many levels as you want.
483 
484     Assuming you are using 'info' messages elsewhere, it is preferable to
485     group them using this method over simply {@link SC.Logger.group} — the log
486     levels could be set such that the 'info' messages are never seen, and you
487     wouldn’t want an empty/needless group!
488 
489     You can optionally provide a title for the group.  If there are any
490     additional arguments, the first argument is assumed to be a format string.
491     Thus, you can (and should) use it like:
492 
493           SC.Logger.infoGroup("%@:  My info group", this);       // good
494 
495     …and not:
496 
497           SC.Logger.infoGroup("%@:  My info group".fmt(this));   // bad
498 
499     The former method can be more efficient because if the log levels are set
500     in such a way that the info() invocation will be ignored, then the
501     String.fmt() call will never actually be performed.
502 
503     @param {String}  (optional)  A title or format string to display above the group
504     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string as the title
505   */
506   infoGroup: function(message, optionalFormatArgs) {
507     // Implementation note:  To avoid having to put the SC.infoGroup()
508     // shorthand variant inside a function wrapper, we'll avoid 'this'.
509     SC.Logger._handleGroup(SC.LOGGER_LEVEL_INFO, message, arguments);
510   },
511 
512 
513   /**
514     Ends a group initiated with {@link SC.Logger.infoGroup}, provided the
515     respective output/recording log levels are set appropriately.
516 
517     @see SC.Logger.infoGroup
518   */
519   infoGroupEnd: function() {
520     // Implementation note:  To avoid having to put the SC.infoGroupEnd()
521     // shorthand variant inside a function wrapper, we'll avoid 'this'.
522     SC.Logger._handleGroupEnd(SC.LOGGER_LEVEL_INFO);
523   },
524 
525 
526 
527   /**
528     Logs a warning message to the console and potentially to the recorded
529     array, provided the respective log levels are set appropriately.
530 
531     The first argument must be a string, and if there are any additional
532     arguments, it is assumed to be a format string.  Thus, you can (and
533     should) use it like:
534 
535           SC.Logger.warn("%@:  My warning message", this);       // good
536 
537     …and not:
538 
539           SC.Logger.warn("%@:  My warning message".fmt(this));   // bad
540 
541     The former method can be more efficient because if the log levels are set
542     in such a way that the warn() invocation will be ignored, then the
543     String.fmt() call will never actually be performed.
544 
545     @param {String}              A message or a format string
546     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string
547   */
548   warn: function(message, optionalFormatArgs) {
549     // Implementation note:  To avoid having to put the SC.warn() shorthand
550     // variant inside a function wrapper, we'll avoid 'this'.
551     SC.Logger._handleMessage(SC.LOGGER_LEVEL_WARN, YES, message, arguments);
552 
553   },
554 
555 
556   /**
557     Logs a warning message to the console and potentially to the recorded
558     array, provided the respective log levels are set appropriately.
559 
560     Unlike simply warn(), this method does not try to apply String.fmt() to
561     the arguments, and instead passes them directly to the reporter (and
562     stringifies them if recording).  This can be useful if the browser formats
563     a type in a manner more useful to you than you can achieve with
564     String.fmt().
565 
566     @param {String|Array|Function|Object}
567   */
568   warnWithoutFmt: function() {
569     this._handleMessage(SC.LOGGER_LEVEL_WARN, NO, null, arguments);
570   },
571 
572 
573   /**
574     Begins a new group in the console and/or in the recorded array provided
575     the respective log levels are set to output/record 'warn' messages.
576     Every message after this call (at any log level) will be indented for
577     readability until a matching {@link SC.Logger.warnGroupEnd} is invoked,
578     and you can create as many levels as you want.
579 
580     Assuming you are using 'warn' messages elsewhere, it is preferable to
581     group them using this method over simply {@link SC.Logger.group} — the log
582     levels could be set such that the 'warn' messages are never seen, and you
583     wouldn’t want an empty/needless group!
584 
585     You can optionally provide a title for the group.  If there are any
586     additional arguments, the first argument is assumed to be a format string.
587     Thus, you can (and should) use it like:
588 
589           SC.Logger.warnGroup("%@:  My warn group", this);       // good
590 
591     …and not:
592 
593           SC.Logger.warnGroup("%@:  My warn group".fmt(this));   // bad
594 
595     The former method can be more efficient because if the log levels are set
596     in such a way that the warn() invocation will be ignored, then the
597     String.fmt() call will never actually be performed.
598 
599     @param {String}  (optional)  A title or format string to display above the group
600     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string as the title
601   */
602   warnGroup: function(message, optionalFormatArgs) {
603     // Implementation note:  To avoid having to put the SC.warnGroup()
604     // shorthand variant inside a function wrapper, we'll avoid 'this'.
605     SC.Logger._handleGroup(SC.LOGGER_LEVEL_WARN, message, arguments);
606   },
607 
608 
609   /**
610     Ends a group initiated with {@link SC.Logger.warnGroup}, provided the
611     respective output/recording log levels are set appropriately.
612 
613     @see SC.Logger.warnGroup
614   */
615   warnGroupEnd: function() {
616     // Implementation note:  To avoid having to put the SC.warnGroupEnd()
617     // shorthand variant inside a function wrapper, we'll avoid 'this'.
618     SC.Logger._handleGroupEnd(SC.LOGGER_LEVEL_WARN);
619   },
620 
621 
622   /**
623     Logs an error message to the console and potentially to the recorded
624     array, provided the respective log levels are set appropriately.
625 
626     The first argument must be a string, and if there are any additional
627     arguments, it is assumed to be a format string.  Thus, you can (and
628     should) use it like:
629 
630           SC.Logger.error("%@:  My error message", this);       // good
631 
632     …and not:
633 
634           SC.Logger.warn("%@:  My error message".fmt(this));    // bad
635 
636     The former method can be more efficient because if the log levels are set
637     in such a way that the warn() invocation will be ignored, then the
638     String.fmt() call will never actually be performed.
639 
640     @param {String}              A message or a format string
641     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string
642   */
643   error: function(message, optionalFormatArgs) {
644     // Implementation note:  To avoid having to put the SC.error() shorthand
645     // variant inside a function wrapper, we'll avoid 'this'.
646     SC.Logger._handleMessage(SC.LOGGER_LEVEL_ERROR, YES, message, arguments);
647   },
648 
649 
650   /**
651     Logs an error message to the console and potentially to the recorded
652     array, provided the respective log levels are set appropriately.
653 
654     Unlike simply error(), this method does not try to apply String.fmt() to
655     the arguments, and instead passes them directly to the reporter (and
656     stringifies them if recording).  This can be useful if the browser formats
657     a type in a manner more useful to you than you can achieve with
658     String.fmt().
659 
660     @param {String|Array|Function|Object}
661   */
662   errorWithoutFmt: function() {
663     this._handleMessage(SC.LOGGER_LEVEL_ERROR, NO, null, arguments);
664   },
665 
666 
667   /**
668     Begins a new group in the console and/or in the recorded array provided
669     the respective log levels are set to output/record 'error' messages.
670     Every message after this call (at any log level) will be indented for
671     readability until a matching {@link SC.Logger.errorGroupEnd} is invoked,
672     and you can create as many levels as you want.
673 
674     Assuming you are using 'error' messages elsewhere, it is preferable to
675     group them using this method over simply {@link SC.Logger.group} — the log
676     levels could be set such that the 'error' messages are never seen, and you
677     wouldn’t want an empty/needless group!
678 
679     You can optionally provide a title for the group.  If there are any
680     additional arguments, the first argument is assumed to be a format string.
681     Thus, you can (and should) use it like:
682 
683           SC.Logger.errorGroup("%@:  My error group", this);       // good
684 
685     …and not:
686 
687           SC.Logger.errorGroup("%@:  My error group".fmt(this));   // bad
688 
689     The former method can be more efficient because if the log levels are set
690     in such a way that the error() invocation will be ignored, then the
691     String.fmt() call will never actually be performed.
692 
693     @param {String}  (optional)  A title or format string to display above the group
694     @param {…}       (optional)  Other arguments to pass to String.fmt() when using a format string as the title
695   */
696   errorGroup: function(message, optionalFormatArgs) {
697     // Implementation note:  To avoid having to put the SC.errorGroup()
698     // shorthand variant inside a function wrapper, we'll avoid 'this'.
699     SC.Logger._handleGroup(SC.LOGGER_LEVEL_ERROR, message, arguments);
700   },
701 
702 
703   /**
704     Ends a group initiated with {@link SC.Logger.errorGroup}, provided the
705     respective output/recording log levels are set appropriately.
706 
707     @see SC.Logger.errorGroup
708   */
709   errorGroupEnd: function() {
710     // Implementation note:  To avoid having to put the SC.errorGroupEnd()
711     // shorthand variant inside a function wrapper, we'll avoid 'this'.
712     SC.Logger._handleGroupEnd(SC.LOGGER_LEVEL_ERROR);
713   },
714 
715 
716 
717   /**
718     This method will output all recorded log messages to the reporter.  This
719     provides a convenient way to see the messages “on-demand” without having
720     to have them always output.  The timestamp of each message will be
721     included as a prefix if you specify 'includeTimestamps' as YES, although
722     in some browsers the native group indenting can make the timestamp
723     formatting less than ideal.
724 
725     @param {Boolean}  (optional)  Whether to include timestamps in the output
726   */
727   outputRecordedLogMessages: function(includeTimestamps) {
728     // If we have no reporter, there's nothing we can do.
729     if (!this.get('exists')) return;
730 
731     var reporter        = this.get('reporter'),
732         entries         = this.get('recordedLogMessages'),
733         indentation     = 0,
734         timestampFormat = SC.LOGGER_RECORDED_LOG_TIMESTAMP_PREFIX,
735         i, iLen, entry, type, timestampStr, message, originalArguments,
736         output, title, newIndentation, disparity, j, jLen;
737 
738     if (entries) {
739       for (i = 0, iLen = entries.length;  i < iLen;  ++i) {
740         entry        = entries[i];
741         type         = entry.type;
742 
743         if (includeTimestamps) {
744           timestampStr = timestampFormat.fmt(entry.timestamp.toUTCString());
745         }
746 
747         // Is this a message or a group directive?
748         message = entry.message;
749         if (message) {
750           // It's a message entry.  Were the original arguments stored?  If
751           // so, we need to use those instead of the message.
752           originalArguments = entry.originalArguments;
753           this._outputMessage(type, timestampStr, indentation, message, originalArguments);
754         }
755         else {
756           // It's a group directive.  Update our indentation appropriately.
757           newIndentation = entry.indentation;
758           title          = entry.title;
759           disparity      = newIndentation - indentation;
760 
761           // If the reporter implements group() and the indentation level
762           // changes by more than 1, that implies that some earlier “begin
763           // group” / “end group” directives were pruned from the beginning of
764           // the buffer and we need to insert empty groups to compensate.
765           if (reporter.group) {
766             if (Math.abs(disparity) > 1) {
767               for (j = 0, jLen = (disparity - 1);  j < jLen;  ++j) {
768                 if (disparity > 0) {
769                   reporter.group();
770                 }
771                 else {
772                   reporter.groupEnd();
773                 }
774               }
775             }
776 
777             if (disparity > 0) {
778               output = timestampStr ? timestampStr : "";
779               output += title;
780               reporter.group(output);
781             }
782             else {
783               reporter.groupEnd();
784             }
785           }
786           else {
787             // The reporter doesn't implement group()?  Then simulate it using
788             // log(), assuming it implements that.
789             if (disparity > 0) {
790               // We're beginning a group.  Output the header at an indentation
791               // that is one smaller.
792               this._outputGroup(type, timestampStr, newIndentation - 1, title);
793             }
794             // else {}  (There is no need to simulate a group ending.)
795           }
796 
797           // Update our indentation.
798           indentation = newIndentation;
799         }
800       }
801     }
802   },
803 
804 
805   /**
806     This method will return a string representation of all recorded log
807     messages to the reporter, which can be convenient for saving logs and so
808     forth.  The timestamp of each message will be included in the string.
809 
810     If there are no recorded log messages, an empty string will be returned
811     (as opposed to null).
812 
813     @returns {String}
814   */
815   stringifyRecordedLogMessages: function() {
816     var ret           = "",
817         entries       = this.get('recordedLogMessages'),
818         indentation   = 0,
819         timestampFormat = SC.LOGGER_RECORDED_LOG_TIMESTAMP_PREFIX,
820         prefixMapping = this._LOG_FALLBACK_PREFIX_MAPPING,
821         groupHeader   = SC.LOGGER_LOG_GROUP_HEADER,
822         i, iLen, entry, type, message, originalArguments, prefix, line,
823         title, newIndentation, disparity;
824 
825     if (entries) {
826       for (i = 0, iLen = entries.length;  i < iLen;  ++i) {
827         entry = entries[i];
828         type  = entry.type;
829 
830         // First determine the prefix.
831         prefix = timestampFormat.fmt(entry.timestamp.toUTCString());
832         prefix += prefixMapping[type] || "";
833 
834         // Is this a message or a group directive?
835         message = entry.message;
836         if (message) {
837           // It's a message entry.  Were arguments used, or did we format a
838           // message?  If arguments were used, we need to stringify those
839           // instead of using the message.
840           originalArguments = entry.originalArguments;
841           line =  prefix + this._indentation(indentation);
842           line += originalArguments ? this._argumentsToString(originalArguments) : message;
843         }
844         else {
845           // It's a group directive, so we need to update our indentation
846           // appropriately.  Also, if it's the beginning of the group and it
847           // has a title, then we need to include an appropriate header.
848           newIndentation = entry.indentation;
849           title          = entry.title;
850           disparity      = newIndentation - indentation;
851           if (disparity > 0) {
852             // We're beginning a group.  Output the header at an indentation
853             // that is one smaller.
854             line = prefix + this._indentation(indentation) + groupHeader.fmt(title);
855           }
856 
857           // Update our indentation.
858           indentation = newIndentation;
859         }
860 
861         // Add the line to our string.
862         ret += line + "\n";
863       }
864     }
865     return ret;
866   },
867 
868 
869 
870   /**
871     Log output to the console, but only if it exists.
872 
873     IMPORTANT:  Unlike debug(), info(), warn(), and error(), messages sent to
874     this method do not consult the log level and will always be output.
875     Similarly, they will never be recorded.
876 
877     In general, you should avoid this method and instead choose the
878     appropriate categorization for your message, choosing the appropriate
879     method.
880 
881     @param {String|Array|Function|Object}
882     @returns {Boolean} Whether or not anything was logged
883   */
884   log: function() {
885     var reporter     = this.get('reporter'),
886         message      = arguments[0],
887         prefix       = this.get('messagePrefix'),
888         outputPrefix = this.get('outputMessagePrefix'),
889         ret          = NO;
890 
891     // If the first argument is a string and a prefix was specified, use it.
892     if (message  &&  SC.typeOf(message) === SC.T_STRING) {
893       if (prefix  ||  outputPrefix) {
894         if (prefix)       message = prefix       + message;
895         if (outputPrefix) message = outputPrefix + message;
896         arguments[0] = message;
897       }
898     }
899 
900     // Log through the reporter.
901     if (this.get('exists')) {
902       if (typeof reporter.log === "function") {
903         reporter.log.apply(reporter, arguments);
904         ret = YES;
905       }
906       else if (reporter.log) {
907         // IE8 implements console.log but reports the type of console.log as
908         // "object", so we cannot use apply().  Because of this, the best we
909         // can do is call it directly with an array of our arguments.
910         reporter.log(this._argumentsToArray(arguments));
911         ret = YES;
912       }
913     }
914 
915     // log through alert
916     if (!ret  &&  this.get('fallBackOnAlert')) {
917       // include support for overriding the alert through the reporter
918       // if it has come this far, it's likely this will fail
919       if (this.get('exists')  &&  (typeof reporter.alert === "function")) {
920         reporter.alert(arguments);
921         ret = YES;
922       }
923       else {
924         alert(arguments);
925         ret = YES;
926       }
927     }
928     return ret;
929   },
930 
931 
932   /**
933     Every log after this call until {@link SC.Logger.groupEnd} is called
934     will be indented for readability.  You can create as many levels
935     as you want.
936 
937     IMPORTANT:  Unlike debugGroup(), infoGroup(), warnGroup(), and
938     errorGroup(), this method do not consult the log level and will always
939     result in output when the reporter supports it.  Similarly, group messages
940     logged via this method will never be recorded.
941 
942     @param {String}  (optional)  An optional title to display above the group
943   */
944   group: function(title) {
945     var reporter = this.get('reporter');
946 
947     if (this.get('exists')  &&  (typeof reporter.group === "function")) {
948       reporter.group(title);
949     }
950   },
951 
952   /**
953     Ends a group declared with {@link SC.Logger.group}.
954 
955     @see SC.Logger.group
956   */
957   groupEnd: function() {
958     var reporter = this.get('reporter');
959 
960     if (this.get('exists')  &&  (typeof reporter.groupEnd === "function")) {
961       reporter.groupEnd();
962     }
963   },
964 
965 
966 
967   /**
968     Outputs the properties of an object.
969 
970     Logs the object using {@link SC.Logger.log} if the reporter.dir function
971     does not exist.
972 
973     @param {Object}
974   */
975   dir: function() {
976     var reporter = this.get('reporter');
977 
978     if (this.get('exists')  &&  (typeof reporter.dir === "function")) {
979       // Firebug's console.dir doesn't support multiple objects here
980       // but maybe custom reporters will
981       reporter.dir.apply(reporter, arguments);
982     }
983     else {
984       this.log.apply(this, arguments);
985     }
986   },
987 
988 
989   /**
990     Prints an XML outline for any HTML or XML object.
991 
992     Logs the object using {@link SC.Logger.log} if reporter.dirxml function
993     does not exist.
994 
995     @param {Object}
996   */
997   dirxml: function() {
998     var reporter = this.get('reporter');
999 
1000     if (this.get('exists')  &&  (typeof reporter.dirxml === "function")) {
1001       // Firebug's console.dirxml doesn't support multiple objects here
1002       // but maybe custom reporters will
1003       reporter.dirxml.apply(reporter, arguments);
1004     }
1005     else {
1006       this.log.apply(this, arguments);
1007     }
1008   },
1009 
1010 
1011 
1012   /**
1013     Begins the JavaScript profiler, if it exists. Call {@link SC.Logger.profileEnd}
1014     to end the profiling process and receive a report.
1015 
1016     @param {String}     (optional)  A title to associate with the profile
1017     @returns {Boolean} YES if reporter.profile exists, NO otherwise
1018   */
1019   profile: function(title) {
1020     var reporter = this.get('reporter');
1021 
1022     if (this.get('exists')  &&  (typeof reporter.profile === "function")) {
1023       reporter.profile(title);
1024       return YES;
1025     }
1026     return NO;
1027   },
1028 
1029   /**
1030     Ends the JavaScript profiler, if it exists.  If you specify a title, the
1031     profile with that title will be ended.
1032 
1033     @param {String}     (optional)  A title to associate with the profile
1034     @returns {Boolean} YES if reporter.profileEnd exists, NO otherwise
1035     @see SC.Logger.profile
1036   */
1037   profileEnd: function(title) {
1038     var reporter = this.get('reporter');
1039 
1040     if (this.get('exists')  &&  (typeof reporter.profileEnd === "function")) {
1041       reporter.profileEnd(title);
1042       return YES;
1043     }
1044     return NO;
1045   },
1046 
1047 
1048   /**
1049     Measure the time between when this function is called and
1050     {@link SC.Logger.timeEnd} is called.
1051 
1052     @param {String}     The name of the profile to begin
1053     @returns {Boolean} YES if reporter.time exists, NO otherwise
1054     @see SC.Logger.timeEnd
1055   */
1056   time: function(name) {
1057     var reporter = this.get('reporter');
1058 
1059     if (this.get('exists')  &&  (typeof reporter.time === "function")) {
1060       reporter.time(name);
1061       return YES;
1062     }
1063     return NO;
1064   },
1065 
1066   /**
1067     Ends the profile specified.
1068 
1069     @param {String}     The name of the profile to end
1070     @returns {Boolean}  YES if reporter.timeEnd exists, NO otherwise
1071     @see SC.Logger.time
1072   */
1073   timeEnd: function(name) {
1074     var reporter = this.get('reporter');
1075 
1076     if (this.get('exists')  &&  (typeof reporter.timeEnd === "function")) {
1077       reporter.timeEnd(name);
1078       return YES;
1079     }
1080     return NO;
1081   },
1082 
1083 
1084   /**
1085     Prints a stack-trace.
1086 
1087     @returns {Boolean} YES if reporter.trace exists, NO otherwise
1088   */
1089   trace: function() {
1090     var reporter = this.get('reporter');
1091 
1092     if (this.get('exists')  &&  (typeof reporter.trace === "function")) {
1093       reporter.trace();
1094       return YES;
1095     }
1096     return NO;
1097   },
1098 
1099 
1100 
1101 
1102   // ..........................................................
1103   // INTERNAL SUPPORT
1104   //
1105 
1106   init: function() {
1107     sc_super();
1108 
1109     // Set a reasonable default value if none has been set.
1110     if (!this.get('logOutputLevel')) {
1111       if (SC.buildMode === "debug") {
1112         this.set('logOutputLevel', SC.LOGGER_LEVEL_DEBUG);
1113       }
1114       else {
1115         this.set('logOutputLevel', SC.LOGGER_LEVEL_INFO);
1116       }
1117     }
1118 
1119     this.debugEnabledDidChange();
1120   },
1121 
1122 
1123   /** @private
1124     For backwards compatibility with the older 'debugEnabled' property, set
1125     our log output level to SC.LOGGER_LEVEL_DEBUG if 'debugEnabled' is set to
1126     YES.
1127   */
1128   debugEnabledDidChange: function() {
1129     if (this.get('debugEnabled')) {
1130       this.set('logOutputLevel', SC.LOGGER_LEVEL_DEBUG);
1131     }
1132   }.observes('debugEnabled'),
1133 
1134 
1135 
1136   /** @private
1137     Outputs and/or records the specified message of the specified type if the
1138     respective current log levels allow for it.  Assuming
1139     'automaticallyFormat' is specified, then String.fmt() will be called
1140     automatically on the message, but only if at least one of the log levels
1141     is such that the result will be used.
1142 
1143     @param {String}               type                 Expected to be SC.LOGGER_LEVEL_DEBUG, etc.
1144     @param {Boolean}              automaticallyFormat  Whether or not to treat 'message' as a format string if there are additional arguments
1145     @param {String}               message              Expected to a string format (for String.fmt()) if there are other arguments
1146     @param {String}   (optional)  originalArguments    All arguments passed into debug(), etc. (which includes 'message'; for efficiency, we don’t copy it)
1147   */
1148   _handleMessage: function(type, automaticallyFormat, message, originalArguments) {
1149     // Are we configured to show this type?
1150     var shouldOutput = this._shouldOutputType(type),
1151         shouldRecord = this._shouldRecordType(type),
1152         hasOtherArguments, i, len, args, output, entry, prefix,
1153         outputPrefix, recordedPrefix;
1154 
1155     // If we're neither going to output nor record the message, then stop now.
1156     if (!(shouldOutput || shouldRecord)) return;
1157 
1158     // Do we have arguments other than 'message'?  (Remember that
1159     // 'originalArguments' contains the message here, too, hence the > 1.)
1160     hasOtherArguments = (originalArguments  &&  originalArguments.length > 1);
1161 
1162     // If we're automatically formatting and there is no message (or it is
1163     // not a string), then don't automatically format after all.
1164     if (automaticallyFormat  &&  (SC.none(message)  ||  (typeof message !== "string"))) {
1165       automaticallyFormat = NO;
1166     }
1167 
1168     // If we should automatically format, and the client specified other
1169     // arguments in addition to the message, then we'll call .fmt() assuming
1170     // that the message is a format string.
1171     if (automaticallyFormat) {
1172       if (hasOtherArguments) {
1173         args = [];
1174         for (i = 1, len = originalArguments.length;  i < len;  ++i) {
1175           args.push(originalArguments[i]);
1176         }
1177         message = message.fmt.apply(message, args);
1178       }
1179     }
1180 
1181     // If a message prefix was specified, use it.
1182     prefix = this.get('messagePrefix');
1183     if (prefix) message = prefix + message;
1184 
1185     if (shouldOutput) {
1186       outputPrefix = this.get('outputMessagePrefix');
1187 
1188       // We only want to pass the original arguments to _outputMessage() if we
1189       // didn't format the message ourselves.
1190       args = automaticallyFormat ? null : originalArguments;
1191       this._outputMessage(type, null, this._outputIndentationLevel, (outputPrefix ? outputPrefix + message : message), args);
1192     }
1193 
1194     // If we're recording the log, append the message now.
1195     if (shouldRecord) {
1196       recordedPrefix = this.get('recordedMessagePrefix');
1197 
1198       entry = {
1199         type:      type,
1200         message:   message ? (recordedPrefix ? recordedPrefix + message : message) : YES,
1201         timestamp: new Date()
1202       };
1203 
1204       // If we didn't automatically format, and we have other arguments, then
1205       // be sure to record them, too.
1206       if (!automaticallyFormat  &&  hasOtherArguments) {
1207         entry.originalArguments = originalArguments;
1208       }
1209 
1210       this._addRecordedMessageEntry(entry);
1211     }
1212   },
1213 
1214 
1215   /** @private
1216     Outputs and/or records a group with the (optional) specified title
1217     assuming the respective current log levels allow for it.  This will output
1218     the title (if there is one) and indent all further messages (of any type)
1219     until _handleGroupEnd() is invoked.
1220 
1221     If additional arguments beyond a title are passed in, then String.fmt()
1222     will be called automatically on the title, but only if at least one of the
1223     log levels is such that the result will be used.
1224 
1225     @param {String}              type                 Expected to be SC.LOGGER_LEVEL_DEBUG, etc.
1226     @param {String}  (optional)  title                Expected to a string format (for String.fmt()) if there are other arguments
1227     @param {String}  (optional)  originalArguments    All arguments passed into debug(), etc. (which includes 'title'; for efficiency, we don’t copy it)
1228   */
1229   _handleGroup: function(type, title, originalArguments) {
1230     // Are we configured to show this type?
1231     var shouldOutput = this._shouldOutputType(type),
1232         shouldRecord = this._shouldRecordType(type),
1233         hasOtherArguments, i, len, args, arg, reporter, func, header, output,
1234         indentation, entry;
1235 
1236     // If we're neither going to output nor record the group, then stop now.
1237     if (!(shouldOutput || shouldRecord)) return;
1238 
1239     // Do we have arguments other than 'title'?  (Remember that
1240     // 'originalArguments' contains the title here, too, hence the > 1.)
1241     hasOtherArguments = (originalArguments  &&  originalArguments.length > 1);
1242 
1243     // If the client specified a title as well other arguments, then we'll
1244     // call .fmt() assuming that the title is a format string.
1245     if (title  &&  hasOtherArguments) {
1246       args = [];
1247       for (i = 1, len = originalArguments.length;  i < len;  ++i) {
1248         args.push(originalArguments[i]);
1249       }
1250       title = title.fmt.apply(title, args);
1251     }
1252 
1253     if (shouldOutput) {
1254       this._outputGroup(type, null, this._outputIndentationLevel, title);
1255 
1256       // Increase our indentation level to accommodate the group.
1257       this._outputIndentationLevel++;
1258     }
1259 
1260     // If we're recording the group, append the entry now.
1261     if (shouldRecord) {
1262       // Increase our indentation level to accommodate the group.
1263       indentation = ++this._recordingIndentationLevel;
1264 
1265       entry = {
1266         type:         type,
1267         indentation:  indentation,
1268         beginGroup:   YES,
1269         title:        title,
1270         timestamp:    new Date()
1271       };
1272 
1273       this._addRecordedMessageEntry(entry);
1274     }
1275   },
1276 
1277 
1278   /** @private
1279     Outputs and/or records a “group end” assuming the respective current log
1280     levels allow for it.  This will remove one level of indentation from all
1281     further messages (of any type).
1282 
1283     @param {String}              type                 Expected to be SC.LOGGER_LEVEL_DEBUG, etc.
1284   */
1285   _handleGroupEnd: function(type) {
1286     // Are we configured to show this type?
1287     var shouldOutput = this._shouldOutputType(type),
1288         shouldRecord = this._shouldRecordType(type),
1289         reporter, func, indentation, entry;
1290 
1291     // If we're neither going to output nor record the "group end", then stop
1292     // now.
1293     if (!(shouldOutput || shouldRecord)) return;
1294 
1295     if (shouldOutput) {
1296       // Decrease our indentation level to accommodate the group.
1297       this._outputIndentationLevel--;
1298 
1299       if (this.get('exists')) {
1300         // Do we have reporter.groupEnd defined as a function?  If not, we
1301         // simply won't output anything.
1302         reporter = this.get('reporter');
1303         func     = reporter.groupEnd;
1304         if (func) {
1305           func.call(reporter);
1306         }
1307       }
1308     }
1309 
1310     // If we're recording the “group end”, append the entry now.
1311     if (shouldRecord) {
1312       // Decrease our indentation level to accommodate the group.
1313       indentation = --this._recordingIndentationLevel;
1314 
1315       entry = {
1316         type:         type,
1317         indentation:  indentation,
1318         timestamp:    new Date()
1319       };
1320 
1321       this._addRecordedMessageEntry(entry);
1322     }
1323   },
1324 
1325 
1326   /** @private
1327     Returns whether a message of the specified type ('debug', etc.) should be
1328     output to the reporter based on the current value of 'logOutputLevel'.
1329 
1330     @param {Constant}  type
1331     @returns {Boolean}
1332   */
1333   _shouldOutputType: function(type) {
1334     var logLevelMapping = this._LOG_LEVEL_MAPPING,
1335         level           = logLevelMapping[type]                        ||  0,
1336         currentLevel    = logLevelMapping[this.get('logOutputLevel')]  ||  0;
1337 
1338     return (level <= currentLevel);
1339   },
1340 
1341 
1342   /** @private
1343     Returns whether a message of the specified type ('debug', etc.) should be
1344     recorded based on the current value of 'logRecordingLevel'.
1345 
1346     @param {Constant}  type
1347     @returns {Boolean}
1348   */
1349   _shouldRecordType: function(type) {
1350     // This is the same code as in _shouldOutputType(), but inlined to
1351     // avoid yet another function call.
1352     var logLevelMapping = this._LOG_LEVEL_MAPPING,
1353         level           = logLevelMapping[type]                           ||  0,
1354         currentLevel  = logLevelMapping[this.get('logRecordingLevel')]  ||  0;
1355 
1356     return (level <= currentLevel);
1357   },
1358 
1359 
1360   /** @private
1361     Outputs the specified message to the current reporter.  If the reporter
1362     does not handle the specified type of message, it will fall back to using
1363     log() if possible.
1364 
1365     @param {Constant}               type
1366     @param {String}                 timestampStr       An optional timestamp prefix for the line, or null for none
1367     @param {Number}                 indentation        The current indentation level
1368     @param {String}                 message
1369     @param {Arguments}  (optional)  originalArguments  If specified, the assumption is that the message was not automatically formatted
1370   */
1371   _outputMessage: function(type, timestampStr, indentation, message, originalArguments) {
1372     if (!this.get('exists')) return;
1373 
1374     // Do we have reporter[type] defined as a function?  If not, we'll fall
1375     // back to reporter.log if that exists.
1376     var reporter = this.get('reporter'),
1377         output, shouldIndent, func, prefix, args, arg;
1378 
1379     // If the reporter doesn't support group(), then we need to manually
1380     // include indentation for the group.  (It it does, we'll assume that
1381     // we're currently at the correct group level.)
1382     shouldIndent = !reporter.group;
1383 
1384     // Note:  Normally we wouldn't do the hash dereference twice, but
1385     //        storing the result like this:
1386     //
1387     //          var nativeFunction = console[type];
1388     //          nativeFunction(output);
1389     //
1390     //        …doesn't work in Safari 4, and:
1391     //
1392     //          nativeFunction.call(console, output);
1393     //
1394     //        …doesn't work in IE8 because the console.* methods are
1395     //       reported as being objects.
1396     func = reporter[type];
1397     if (func) {
1398       // If we formatted, just include the message.  Otherwise, include all
1399       // the original arguments.
1400       if (!originalArguments) {
1401         output = timestampStr ? timestampStr : "";
1402         if (shouldIndent) output += this._indentation(indentation);
1403         output += message;
1404         reporter[type](output);
1405       }
1406       else {
1407         // We have arguments?  Then pass them along to the reporter function
1408         // so that it can format them appropriately.  We'll use the timestamp
1409         // string (if there is one) and the indentation as the first
1410         // arguments.
1411         args = this._argumentsToArray(originalArguments);
1412         prefix = "";
1413         if (timestampStr) prefix = timestampStr;
1414         if (shouldIndent) prefix += this._indentation(indentation);
1415         if (prefix) args.splice(0, 0, prefix);
1416 
1417         if (func.apply) {
1418           func.apply(reporter, args);
1419         }
1420         else {
1421           // In IE8, passing the arguments as an array isn't ideal, but it's
1422           // pretty much all we can do because we can't call apply().
1423           reporter[type](args);
1424         }
1425       }
1426     }
1427     else {
1428       // The reporter doesn't support the requested function?  If it at least
1429       // support log(), fall back to that.
1430       if (reporter.log) {
1431         prefix = "";
1432         if (timestampStr) prefix = timestampStr;
1433         prefix += this._LOG_FALLBACK_PREFIX_MAPPING[type] || "";
1434         if (shouldIndent) prefix += this._indentation(indentation);
1435 
1436         // If we formatted, just include the message.  Otherwise, include
1437         // all the original arguments.
1438         if (!originalArguments) {
1439           reporter.log(prefix + message);
1440         }
1441         else {
1442           args = this._argumentsToArray(originalArguments);
1443           if (prefix) args.splice(0, 0, prefix);
1444           reporter.log(args);
1445         }
1446       }
1447     }
1448   },
1449 
1450 
1451   /** @private
1452     Outputs the specified “begin group” directive to the current reporter.  If
1453     the reporter does not handle the group() method, it will fall back to
1454     simulating using log() if possible.
1455 
1456     @param {Constant}               type
1457     @param {String}                 timestampStr  An optional timestamp prefix for the line, or null for none
1458     @param {Number}                 indentation   The current indentation level, not including what the group will set it to
1459     @param {String}     (optional)  title
1460   */
1461   _outputGroup: function(type, timestampStr, indentation, title) {
1462     if (!this.get('exists')) return;
1463 
1464     // Do we have reporter.group defined as a function?  If not, we'll fall
1465     // back to reporter.log if that exists.  (Thankfully, we can avoid the IE8
1466     // special-casing we have in _outputMessage() because IE8 doesn't support
1467     // console.group(), anyway.)
1468     var reporter = this.get('reporter'),
1469         func     = reporter.group,
1470         output;
1471 
1472     if (func) {
1473       output = timestampStr ? timestampStr : "";
1474       output += title;
1475       func.call(reporter, output);
1476     }
1477     else if (reporter.log) {
1478       // The reporter doesn't support group()?  Then simulate with log().
1479       // (We'll live with the duplicitous dereference rather than using
1480       // apply() to work around the IE8 issue described in _outputMessage().)
1481       output = "";
1482       if (timestampStr) output = timestampStr;
1483       output += this._LOG_FALLBACK_PREFIX_MAPPING[type] || "";
1484       output += this._indentation(indentation);
1485       output += SC.LOGGER_LOG_GROUP_HEADER.fmt(title);
1486       reporter.log(output);
1487     }
1488   },
1489 
1490 
1491   /** @private
1492     This method will add the specified entry to the recorded log messages
1493     array and also prune array as necessary according to the current values of
1494     'recordedLogMessagesMaximumLength' and
1495     'recordedLogMessagesPruningMinimumLength'.
1496   */
1497   _addRecordedMessageEntry: function(entry) {
1498     var recordedMessages = this.get('recordedLogMessages'),
1499         len;
1500 
1501     // Lazily create the array.
1502     if (!recordedMessages) {
1503       recordedMessages = [];
1504       this.set('recordedLogMessages', recordedMessages);
1505     }
1506 
1507     recordedMessages.push(entry);
1508 
1509     // Have we exceeded the maximum size?  If so, do some pruning.
1510     len = recordedMessages.length;
1511     if (len > this.get('recordedLogMessagesMaximumLength')) {
1512       recordedMessages.splice(0, (len - this.get('recordedLogMessagesPruningMinimumLength')));
1513     }
1514 
1515     // Notify that the array content changed.
1516     recordedMessages.enumerableContentDidChange();
1517   },
1518 
1519 
1520 
1521   /** @private
1522     The arguments function property doesn't support Array#unshift. This helper
1523     copies the elements of arguments to a blank array.
1524 
1525     @param {Array} arguments The arguments property of a function
1526     @returns {Array} An array containing the elements of arguments parameter
1527   */
1528   _argumentsToArray: function(args) {
1529     var ret = [],
1530         i, len;
1531 
1532     if (args) {
1533       for (i = 0, len = args.length;  i < len;  ++i) {
1534         ret[i] = args[i];
1535       }
1536     }
1537     return ret;
1538   },
1539 
1540 
1541   /** @private
1542     Formats the arguments array of a function by creating a string with
1543     SC.LOGGER_LOG_DELIMITER between the elements.
1544   */
1545   _argumentsToString: function() {
1546     var ret       = "",
1547         delimiter = SC.LOGGER_LOG_DELIMITER,
1548         i, len;
1549 
1550     for (i = 0, len = (arguments.length - 1);  i < len;  ++i) {
1551       ret += arguments[i] + delimiter;
1552     }
1553     ret += arguments[len];
1554     return ret;
1555   },
1556 
1557 
1558   /** @private
1559     Returns a string containing the appropriate indentation for the specified
1560     indentation level.
1561 
1562     @param {Number}  The indentation level
1563     @returns {String}
1564   */
1565   _indentation: function(level) {
1566     if (!level  ||  level < 0) {
1567       level = 0;
1568     }
1569 
1570     var ret    = "",
1571         indent = SC.LOGGER_LOG_GROUP_INDENTATION,
1572         i;
1573 
1574     for (i = 0;  i < level;  ++i) {
1575       ret += indent;
1576     }
1577     return ret;
1578   },
1579 
1580 
1581 
1582   /** @private
1583     The current “for output” indentation level.  The reporter (browser
1584     console) is expected to keep track of this for us for output, but we need
1585     to do our own bookkeeping if the browser doesn’t support console.group.
1586     This is incremented by _debugGroup() and friends, and decremented by
1587     _debugGroupEnd() and friends.
1588   */
1589   _outputIndentationLevel: 0,
1590 
1591 
1592   /** @private
1593     The current “for recording” indentation level.  This can be different than
1594     the “for output” indentation level if the respective log levels are set
1595     differently.  This is incremented by _debugGroup() and friends, and
1596     decremented by _debugGroupEnd() and friends.
1597   */
1598   _recordingIndentationLevel: 0,
1599 
1600 
1601   /** @private
1602     A mapping of the log level constants (SC.LOGGER_LEVEL_DEBUG, etc.) to
1603     their priority.  This makes it easy to determine which levels are “higher”
1604     than the current level.
1605 
1606     Implementation note:  We’re hardcoding the values of the constants defined
1607     earlier here for a tiny bit of efficiency (we can create the hash all at
1608     once rather than having to push in keys).
1609   */
1610   _LOG_LEVEL_MAPPING: { debug: 4, info: 3, warn: 2, error: 1, none: 0 },
1611 
1612 
1613   /** @private
1614     If the current reporter does not support a particular type of log message
1615     (for example, some older browsers’ consoles support console.log but not
1616     console.debug), we’ll use the specified prefixes.
1617 
1618     Implementation note:  We’re hardcoding the values of the constants defined
1619     earlier here for a tiny bit of efficiency (we can create the hash all at
1620     once rather than having to push in keys).
1621   */
1622   _LOG_FALLBACK_PREFIX_MAPPING: {
1623     debug:  SC.LOGGER_LOG_DEBUG,
1624     info:   SC.LOGGER_LOG_INFO,
1625     warn:   SC.LOGGER_LOG_WARN,
1626     error:  SC.LOGGER_LOG_ERROR
1627   }
1628 
1629 });
1630 
1631 
1632 // Add convenient shorthands methods to SC.
1633 SC.debug = SC.Logger.debug;
1634 SC.info  = SC.Logger.info;
1635 SC.warn  = SC.Logger.warn;
1636 SC.error = SC.Logger.error;
1637