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