/** * @name CeL log function * @fileoverview * 本檔案包含了記錄用 functions。 * * @since 2009/11/17 * @see * Firebug Lite, * Venkman JavaScript Debugger project page */ // http://blogs.msdn.com/b/webdevtools/archive/2007/03/02/jscript-intellisense-in-orcas.aspx /// /** * TODO: https://developers.google.com/web/tools/chrome-devtools/console/console-write#styling_console_output_with_css console.log("%c", 將 CSS 樣式規則應用到第二個參數指定的輸出字符串) emergency/urgent situation alert 會盡量以網頁上方/頂部黄色的導航條/警告條展示 「不再顯示」功能 .format() 將 div format 成 log panel。 分群, http://developer.yahoo.com/yui/examples/uploader/uploader-simple-button.html */ /** * to include: include code_for_including
var SL = new Debug.log('debug_panel'), sl = function() { SL.log.apply(SL, arguments); }, error = function() { SL.error.apply(SL, arguments); }, warn = function() { SL.warn.apply(SL, arguments); }; http://www.comsharp.com/GetKnowledge/zh-CN/TeamBlogTimothyPage_K742.aspx if possible, use Firebug Lite instead. http://benalman.com/projects/javascript-debug-console-log/
*/ 'use strict'; // -------------------------------------------------------------------------------------------- // 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。 typeof CeL === 'function' && CeL.run({ // module name name : 'application.debug.log', // Object.is() require : 'data.code.compatibility.', // 設定不匯出的子函式。 no_extend : 'this,do_log,extend', // 為了方便格式化程式碼,因此將 module 函式主體另外抽出。 code : module_code, finish : finish }); function module_code(library_namespace) { // WScript.Echo(this); var // class private ----------------------------------- // class name, 需要用到這個都不是好方法。 // cn = 'Debug.log', /** * private storage pool * * @ignore */ p = [], // has_performance_now, // log_data = function(message, options) { // 由於 .set_method() 會用到 .debug(), // 若在 log 的 core 中用上 .set_method() 會循環呼叫,造成 stack overflow。 // ** NG: library_namespace.set_method(this, options); if (library_namespace.is_Object(options)) Object.assign(this, options); this.date = new Date(); if (has_performance_now) this.time = performance.now(); this.message = message; return this; }, /** * default write/show log function * * @ignore * @param {string}id * element id */ write_log = function(id) { // console.log(id); var o, m, c, _p = p[id], _t = _p.instance, /** * buffer * * @inner * @ignore */ b = _p.buf, B = _p.board, F = _p.do_function, level; if (_p.clean) _t.clear(), _p.clean = 0; if (!B && !F) return; while (b.length) { // 預防 multi-threading 時重複顯示。 m = b.shift(); if (F) F(m); // IE8: 'constructor' 是 null 或不是一個物件 try { c = m.constructor; if (false) alert((m.constructor === log_data) + '\n' + m.constructor + '\n' + m); } catch (e) { } if (c === log_data) { if (!isNaN(m.level) && m.level < library_namespace.set_debug()) continue; c = m.level in _t.className_set ? m.level : 0; o = m.add_class; // 添加各種標記。 m = [ _t.message_prefix(c), _t.show_time(m.date, m.time), m.message ]; c = _t.className_set[c]; if (o) c += ' ' + o; } else { // add default style set if (c = _t.message_prefix('log')) m = [ c, m ]; c = _t.className_set.log || 0; } _p.lbuf.push(m); if (B // && typeof document === 'object' ) { o = _p.instance.log_tag; if (o) { o = document.createElement(o); if (c) o.className = c; new_node(m, o); } else { o = document.createTextNode(m); } // TODO: pause B.appendChild(o); while (B.childNodes.length > _p.max_logs) { B.removeChild(B.firstChild); } } } if (false) { if (_t.auto_hide) B.style.display = B.innerHTML ? 'block' : 'none'; } // TODO: 有時無法捲到最新。 if (B && _t.auto_scroll) B.scrollTop = B.scrollHeight - B.clientHeight; }, /** * save log. * * @ignore * @param m * message * @param {string} * id element id * @param force * force to clean the message area */ do_save_log = function(m, id, force) { // console.log(m); var _p = p[id], _t = _p.instance, // log file handler f = _p.logF, s = _t.save_log; if (!s || typeof s === 'function' && !s(m, l)) return; if (m) _p.sbuf .push(m = (_t.save_date && typeof gDate === 'function' ? _t.save_line_separator + gDate() + _t.save_line_separator : '') + m); if (force || _t.flush || _p.sbufL > _t.save_limit) try { if (f || _t.log_file && (f = _p.logF = fso.OpenTextFile(_t.log_file, /* ForAppending */8, /* create */true, _t.log_encoding))) f.Write(_p.sbuf.join(_t.save_line_separator)), _p.sbuf = [], _p.sbufL = 0, _t.error_message = 0; } catch (e) { // error(e); _t.error_message = e; } else if (m) _p.sbufL += m.length; }, using_DOM_new_node = false, // 使 log 能用到 new_node 的功能。 // @see function_placeholder() @ module.js new_node = function(o, layer) { // console.log(o); if (library_namespace.is_Function(library_namespace.new_node)) { // alert('開始利用 library 之 new_node。'); using_DOM_new_node = true; return (new_node = library_namespace.new_node)(o, layer); } var list = []; // workaround: 簡易版 new_node(). (function add(o) { var node, tag, child; if (Array.isArray(o)) for (node = 0; node < o.length; node++) add(o[node]); else if (library_namespace.is_Object(o)) { if (o.$) { tag = o.$; list.push('<' + tag); delete o.$; } for (node in o) { if (tag) list.push(' ' + node + '="' + ('' + o[node]).replace(/"/g, '"') + '"'); else { tag = node; list.push('<' + tag); child = o[node] || null; } } if (child === null) list.push(' />'); else { list.push('>'); add(child); list.push(''); } } else list.push(o); })(o); layer.innerHTML = list.join(''); return using_DOM_new_node; }, show_date = function(date) { var h = date.getHours(), m = date.getMinutes(), s = date.getSeconds(), ms = date .getMilliseconds(); return (h || m || s ? (h || m ? (h ? h + ':' : '') + m + ':' : '') + s : '') + '.' + (ms > 99 ? '' : ms > 9 ? '0' : '00') + ms; }, has_caller, // instance constructor --------------------------- // (document object) /** * _ = this TODO: set class in each input input array show file path & directory functional 可從 FSO operation.hta 移植。 count c.f.: GLog dependency: */ /** * initial a log tool's instance/object * * @class log function * @_see usage: _module_.extend * @since 2008/8/20 23:9:48 * @requires gDate(),line_separator,fso * * @constructor * @_name _module_ * @param {String|object * HTMLElement} obj log target: message area element or id * @param {Object} * [className_set] class name set */ _// JSDT:_tmp;_module_ = function(obj, className_set) { // Initial instance object. You can set it yourself. /** * log 時 warning/error message 之 className * * @_name _module_.prototype.className_set */ this.className_set = className_set || { /** * @_description 當呼叫 {@link _module_.prototype.log} 時使用的 className, * DEFAULT className. * @_name _module_.prototype.className_set.log */ log : 'debug_log', /** * @_description 當呼叫 {@link _module_.prototype.warn} 時使用的 className * @_name _module_.prototype.className_set.warn */ warn : 'debug_warn', /** * @_description 當呼叫 {@link _module_.prototype.error} 時使用的 className * @_name _module_.prototype.className_set.error */ error : 'debug_error', /** * @_description 當顯示時間時使用的 className * @_name _module_.prototype.className_set.time */ time : 'debug_time', /** * @_description 當呼叫 {@link _module_.prototype.set_board} 時設定 log * panel 使用的 className * @_name _module_.prototype.className_set.panel */ panel : 'debug_panel' }; this.class_hide = {}; var prefix = { /** * @_description 當呼叫 {@link _module_.prototype.log} 時使用的 prefix, * DEFAULT prefix. * @_name _module_.prototype.message_prefix.log */ log : '', /** * @_description 當呼叫 {@link _module_.prototype.warn} 時使用的 prefix * @_name _module_.prototype.message_prefix.warn */ warn : '', /** * @_description 表示當呼叫 {@link _module_.prototype.error}, 是錯誤 error * message 時使用的 prefix * @_name _module_.prototype.message_prefix.error */ error : '!! Error !! ' }; /** * log 時 warning/error message 之 prefix。 * * @_name _module_.prototype.message_prefix */ this.message_prefix = function(level) { return level in prefix ? prefix[level] : ''; }; this.id = p.length; p.push({ instance : this, /** write buffer */ buf : [], /** save buffer when we need to save the messages */ sbuf : [], /** length of save buffer */ sbufL : 0, /** now logged buffer */ lbuf : [] }); this.set_board(obj); }; try { has_performance_now = performance.now() > 0; } catch (e) { } try { has_caller = function(a) { 'use strict'; return arguments.callee.caller !== undefined; }; has_caller = (function() { return has_caller(); })(); } catch (e) { has_caller = false; } // class public interface --------------------------- _// JSDT:_module_ . /** * do the log action * * @_memberOf _module_ * @private */ do_log = function(id) { /** * 這段應該只在 module namespace 重複定義時才會發生 var I = p[id]; if (!I) { alert('.do_log: not exist: [' + id + ']'); return; } I = I.instance; */ var I = p[id].instance; if (I.do_log) I.do_log(); }; _// JSDT:_module_ . /** * 對各種不同 error object 作應對,獲得可理解的 error message。 * * @param e * error object * @param line_separator * line separator * @param caller * function caller * @_memberOf _module_ * @see http://msdn.microsoft.com/en-us/library/ms976144.aspx The facility * code establishes who originated the error. For example, all internal * script engine errors generated by the JScript engine have a facility * code of "A". * http://msdn.microsoft.com/en-us/library/ms690088(VS.85).aspx * @see http://msdn.microsoft.com/en-us/library/t9zk6eay.aspx * http://msdn.microsoft.com/en-us/library/microsoft.jscript.errorobject.aspx * Specifies the name of the type of the error. Possible values include * Error, EvalError, RangeError, ReferenceError, SyntaxError, * TypeError, and URIError. */ get_error_message = function get_error_message(e, line_separator, caller) { if (!line_separator) line_separator = _.prototype.save_line_separator; if (!caller || typeof caller !== 'string') { if (typeof caller !== 'function' && has_caller) caller = get_error_message.caller; if (caller === null) caller = 'from the top level'; else if (typeof caller === 'function') caller = '@' + (library_namespace.get_function_name(caller) || caller); else caller = '@' + library_namespace.Class; } // from popErr() // type var T = library_namespace.is_type(e), // message m = T === 'Error' ? 'Error ' + caller + ': ' /** * http://msdn.microsoft.com/en-us/library/cc231198(PROT.10).aspx Winerror.h: error code definitions for the Win32 API functions (e.number & 0xFFFF): See 錯誤代碼 /錯誤提示碼 System Error Codes http://social.msdn.microsoft.com/Search/zh-TW/?Query=%22System+Error+Codes%22+740&AddEnglish=1 http://msdn.microsoft.com/en-us/library/aa394559(VS.85).aspx net helpmsg (e.number & 0xFFFF) */ + (e.number & 0xFFFF) + (e.name ? ' [' + e.name + '] ' : ' ') + '(facility code ' + (e.number >> 16 & 0x1FFF) + '): ' + line_separator + (e.message || '').replace(/\r?\n/g, '
') // .message 為主,.description 是舊的。 + (!e.description || e.description === e.message ? '' : line_separator + line_separator + ('' + e.description).replace(/\r?\n/g, '
')) : T === 'DOMException' ? // http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-17189187 '[' + T + '] ' + e.code + ': ' + e.message // : !e || T === 'string' ? e // : '[' + T + '] ' + (e.message || e); if (library_namespace.is_debug(2) && typeof e === 'object' && e) for (T in e) try { // Firefox has (new Error).stack // http://eriwen.com/javascript/js-stack-trace/ m += '
' + T + ': ' + (typeof e[T] === 'string' && T === 'stack' ? e[T] .replace(/[\r\n]+$/, '') .replace(/(@)([a-z\-]+:\/\/.+)(:)(\d+)$/gm, '$1$2$3$4') .replace(/\n/g, '
- ') : typeof e[T] === 'string' && T === 'fileName' ? '' + e[T] + '' : e[T]); } catch (e) { // TODO: handle exception } // m += ' (' + arguments.callee.caller + ')'; return m; }; _// JSDT:_module_ . /** * get node description * * @param node * HTML node * @_memberOf _module_ */ node_description = function(node, flag) { // console.log(node); if (typeof node === 'string') node = document.getElementById(node); if (!node) return; var description = ''; if (node.id) description += '#' + node.id; if (node.className) description += '.' + node.className; if (node.tagName) description = '<' + node.tagName + description + '>'; if (!description && node.innerHTML) { description = node.innerHTML; if (description.length > 40) description = description.slice(0, 40); description = description.replace(/ // status logger var SL = new _module_('log'), sl = SL[1], warn = SL[2], error = SL[3]; sl(msg); sl(msg, clear); // general log function_set = new _module_.extend('panel', {}); // 1. function_set = new CeL.code.log.extend('panel', {}); logger = function_set[1]; // 2. log_only = (new CeL.code.log.extend('panel', {}))[1]; * @_memberOf _module_ * @since 2009/8/24 20:15:31 */ extend = function(obj, className_set) { if (false) { CeL.Log = new CeL.code.log( function(m) { var F = typeof JSalert === 'function' ? JSalert : typeof alert === 'function' ? alert : WScript.Echo; F(typeof m === 'object' ? '[' + m.level + '] ' + m.message : m); }); } /** * new instance * * @_type _module_ * @inner * @ignore */ var log_controller = new _// JSDT:_module_ (obj || _.default_log_target, className_set); // TODO: do not use arguments return [ log_controller, function() { // console.log(arguments); log_controller.log.apply(log_controller, arguments); }, function() { log_controller.warn.apply(log_controller, arguments); }, function() { log_controller.error.apply(log_controller, arguments); } ]; }; /** * _.option_open=function(p){ }; _.option_file=function(p){ }; _.option_folder=function(p){ }; */ // class constructor --------------------------- _// JSDT:_module_ .prototype = { // instance public interface ------------------- /** * 當執行寫檔案或任何錯誤發生時之錯誤訊息。
* while error occurred.. should read only * * @_name _module_.prototype.error_message */ error_message : '', /** * 超過這長度才 save。<=0 表示 autoflash,非數字則不紀錄。 * * @_name _module_.prototype.save_limit * @type Number */ save_limit : 4000, /** * 在 log 結束時執行,相當於 VB 中 DoEvent() 或 。 * * @_name _module_.prototype.do_event */ do_event : library_namespace.DoNoting || null, /** * log 時使用之 tagName, 可用 div / span 等。若不設定會用 document.createTextNode * * @_name _module_.prototype.log_tag */ log_tag : 'div', /** * boolean or function(message, log level) return save or not * * @_name _module_.prototype.save_log * @type Boolean */ save_log : false, /** * save log to this file path * * @_name _module_.prototype.log_file * @type Boolean */ log_file : false, /** * auto save log. 若未設定,記得在 onunload 時 .save() * * @_name _module_.prototype.flush * @type Boolean */ flush : false, /** * 在 save log 時 add date * * @_name _module_.prototype.save_date * @type Boolean */ save_date : true, /** * 在 save log 時的換行 * * @_name _module_.prototype.save_line_separator * @type string */ save_line_separator : library_namespace.env.line_separator || '\r\n', /** * 在 save log 時的 encoding * * @_name _module_.prototype.log_encoding */ log_encoding : -1,// -1: TristateTrue /** * 自動捲動 * * @_name _module_.prototype.auto_scroll * @type Boolean */ auto_scroll : true, /** * 沒有內容時自動隱藏 * * @deprecated TODO * @_name _module_.prototype.auto_hide * @type Boolean */ auto_hide : false, /** * 等待多久才顯示 log。若為 0 則直接顯示。
* e.g., 即時顯示,不延遲顯示: CeL.Log.interval = 0;
* (WScript 沒有 setTimeout) * * @_name _module_.prototype.interval */ interval : typeof setTimeout === 'undefined' ? 0 : 1, /** * log function (no delay) * * @_name _module_.prototype.do_log */ do_log : function(level) { if (false) if (p[this.id].th) clearTimeout(p[this.id].th); // reset timeout handler p[this.id].th = 0; // TODO: 提升效率. if ('controller' in this) this.set_controller(); write_log(this.id); }, /** * class instance 預設作 log 之 function * * @param {String} * message message * @param {Boolean}clean * clean message area * @param {Object}options * 選擇性項目. { level : log level, 記錄複雜度. } * @return * @_name _module_.prototype.log */ log : function(message, clean, options) { // console.log(message); var t = this, _p = p[t.id], level, force_save; if (library_namespace.is_Object(options)) { level = options.level; force_save = options.save; } else if (options) { force_save = level = options; (options = {}).level = level; } /** * var message_head = (arguments.callee.caller + '') .match(/function\do_save_log([^\(]+)/); if (message_head) message_head = message_head[1] + ' '; */ do_save_log(message, t.id, force_save); // window.status = message; if (options) { message = new log_data(message, options); } if (clean) { // clean log next time _p.clean = 1, _p.buf = [ message ]; } else { _p.buf.push(message); } if (!(t.interval > 0)) t.do_log(); else if (!_p.th) // no window.setTimeout @ node.js if (typeof setTimeout === 'undefined') t.interval = 0, t.do_log(); else // _p.th = setTimeout(cn + '.do_log(' + t.id + ');', // t.interval); _p.th = setTimeout(function() { _.do_log(t.id); }, t.interval); if (t.do_event) t.do_event(); }, /* * TODO: other methods: INFO,DEBUG,WARNING,ERROR,FATAL,UNKNOWN */ /** * save message * * @_name _module_.prototype.save */ save : function() { do_save_log('', this.id, 1/* force */); }, /** * ** important ** 這邊不能作 object 之 initialization,否則因為 object 只會 copy reference,因此 new 時東西會一樣。initialization 得在 _() 中作! */ // className_set : {}, /** * log a warning / caution / alert / 警告. * * @_name _module_.prototype.warn */ warn : function(m, clean) { this.log(m, clean, 'warn'); }, /** * deal with error message * * @_name _module_.prototype.error */ error : function error(e, clean) { var caller = ''; if (has_caller) { caller = '' + error.caller; if (caller.indexOf('.error.apply(') !== -1) // ** 判斷 call from _.extend. TODO: 應該避免! caller = caller.caller; } this.log(Array.isArray(e) || library_namespace.is_Object(e) ? e : _ .get_error_message(e, this.save_line_separator, caller), clean, 'error'); }, timezone_offset : /* msPerMinute */60000 * (new Date) .getTimezoneOffset(), /** * 在 log 中依照格式顯示時間。 * * @param {Date}date * @returns {String} 依照格式顯示成之時間。 * @_name _module_.prototype.show_time * @since 2012/3/16 22:36:46 */ show_time : function show_time(date, time) { var add_s, _diff_ms, // date_stamp = (date.getMonth() + 1) + '/' + date.getDate() + ' ' + show_date(date), // diff_ms = has_performance_now && this.last_show ? time - this.last_show : (_diff_ms = date - (this.last_show || this.timezone_offset)); if (diff_ms > 0) if (diff_ms < 60000) { add_s = diff_ms >= 1000 && (diff_ms /= 1000); diff_ms = diff_ms.to_fixed ? String(diff_ms.to_fixed(3)) .replace(/^0/, '') // : diff_ms.toFixed ? diff_ms.toFixed(3) : (diff_ms | 0); if (add_s) diff_ms += 's'; } else diff_ms = show_date(new Date(diff_ms + this.timezone_offset)); this.last_show = has_performance_now ? time : date; // 不用 CSS.quotes: 在舊版 browser 上可能無效,但本 module 須在舊版上亦正常作動。 return '[' + diff_ms + '] '; }, /** * 當記錄太長時,限制記錄數目在 max_logs。超過這個數目就會把之前的最舊的紀錄消除掉。 * * @param {Natural}max_logs * 最大記錄數目 */ set_max_logs : function(max_logs) { var _t = this, _p = p[_t.id]; max_logs = Math.floor(max_logs); // accept NaN _p.max_logs = max_logs < 0 ? 0 : max_logs; }, /** * 設定寫入到哪
* set log board for each instance (document object) * * @_name _module_.prototype.set_board */ set_board : function(o) { var _t = this, _p = p[_t.id]; if (o) if (typeof o === 'function') _p.do_function = o; else { if (typeof o !== 'object' && typeof document === 'object') o = document.getElementById(o); if (o // TODO // && library_namespace.is_HTML_obj(o) ) { _p.board = o; _t.set_controller(); if (_t = _t.className_set.panel) o.className += ' ' + _t; delete _p.do_function; } } return _p.board; }, // TODO: 若之後才 include 'interact.DOM',則 controller 沒辦法顯示出來 @ Chrome/25。 set_controller : function(c) { var b = p[this.id].board; if (b && (c || (c = this.controller)) && (c = new_node(c, [ b, 0 ])) !== using_DOM_new_node) { if ('controller' in this) delete this.controller; // c.style.height = '1em'; // c.style.height = ''; } }, /** * 獲取當前 buffer 中的 log。 * * @_name _module_.prototype.get_log */ get_log : function() { return p[this.id].lbuf; }, /** * show/hide log board. 切換可見狀態。 * * @_name _module_.prototype.toggle */ toggle : function(s) { return library_namespace.toggle_display(p[this.id].board, s) !== 'none'; }, /** * clear log board. TODO: use .remove_all_child(). * * @_name _module_.prototype.clear_board */ clear_board : function(b) { b.innerHTML = ''; }, /** * 清除全部訊息 clear message * * @_name _module_.prototype.clear */ clear : function() { var _p = p[this.id]; if (_p.board) { this.clear_board(_p.board); } _p.lbuf = []; } }; return (_// JSDT:_module_ ); } // ----------------------------------------------------------------------------------------------------------------------------------------------------------// function finish(name_space) { // 為 module log 所作的初始化工作。 var module_name = this.id; // 確認 cssRules 之後才作 delete,否則就得按順序先增者後減。因為刪掉 [2] 之後,後面全部皆會遞補,[3] 會變成 [2]。 // TODO: 一般化。 function search_CSS_rule(style_sheet, selector) { var rules = style_sheet.cssRules || style_sheet.rules, i = 0, l = rules.length; for (; i < l; i++) if (selector === rules[i].selectorText) return i; } // WScript.Echo(n.extend); if (false) code_for_including[generateCode.dLK] = '*var Debug={log:code_for_including()};'; // include resources of module. CeL.run(CeL.get_module_path(module_name, 'log.css')); // 為本 library 用 if (!CeL.Log) { var i, l, log_controller = name_space.extend(), has_caller, // 偵錯等級, debug level, log level. log_icon = { /** * MEMO (U+1F4DD).
* http://codepoints.net/U+1F4DD http://wiki.livedoor.jp/qvarie/ */ log : '📝', /** * emphasized text
* U+2383 EMPHASIS SYMBOL
* http://codepoints.net/U+2383 */ em : '⎃', /** * 資訊,消息,報告,通知,情報
* WARNING SIGN (U+26A0) @ Miscellaneous Symbols. */ warn : '⚠', /** * error / fault
* U+2620 SKULL AND CROSSBONES */ error : '☠', /** * U+2139 INFORMATION SOURCE
* http://en.wiktionary.org/wiki/%E2%84%B9 */ info : 'ℹ', /** * U+1F41B BUG */ debug : '🐛', /** * U+1F463 footprints * https://unicode.org/emoji/charts/full-emoji-list.html */ trace : '👣' }, // base path of icon icon_path = CeL.get_module_path(module_name, 'icon/'); try { has_caller = function(a) { 'use strict'; return arguments.callee.caller !== undefined; }; has_caller = (function() { return has_caller(); })(); } catch (e) { has_caller = false; } // console.log('override: CeL.Log = ' + log_controller[0]); CeL.Log = log_controller[0]; // console.log('setup CeL.Log.className_set'); Object.assign(CeL.Log.className_set, { info : 'debug_info', em : 'debug_em', debug : 'debug_debug' }); // log 支援 gettext. CeL.Log.message_prefix = function(level) { if (level in log_icon) { return { img : null, 'class' : 'debug_icon', src : icon_path + level + '.png', alt : '[' + log_icon[level] + ']', title : log_icon[level] + ' ' // gettext_config:{"id":"log-type-fatal","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-error","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-warn","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-em","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-info","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-log","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-debug","mark_type":"combination_message_id"} // gettext_config:{"id":"log-type-trace","mark_type":"combination_message_id"} + CeL.gettext('log-type-' + level) }; } return ''; }; // TODO: copy result, paste code. var controller = [ ':', { // U+239A CLEAR SCREEN SYMBOL a : '⎚', href : '#', title : "clear / 清除所有訊息", onclick : function() { CeL.Log.clear(); return false; } }, { // toggle / switch // U+1F50C ELECTRIC PLUG a : '🔌', href : '#', title : "切換訊息面板\nshow/hidden log panel", onclick : function() { CeL.set_class(this, 'debug_hide', { remove : CeL.Log.toggle() }); return false; } }, { span : '↑', title : "提升偵錯等級", S : 'cursor:pointer;font-size:.7em;', onselect : function() { return false; }, onclick : function() { CeL.set_debug(CeL.is_debug() + 1); CeL.debug('提升偵錯等級至 ' + CeL.is_debug(), 1, 'Log.controller'); return false; } }, { span : '↓', title : "降低偵錯等級", S : 'cursor:pointer;font-size:.7em;', onselect : function() { return false; }, onclick : function() { CeL.set_debug(CeL.is_debug() - 1); CeL.debug('降低偵錯等級至 ' + CeL.is_debug(), 0, 'Log.controller'); return false; } }, { span : '↓', title : "取消 debug", S : 'cursor:pointer;font-size:.7em;text-decoration:underline;', onselect : function() { return false; }, onclick : function() { CeL.set_debug(0); return false; } }, { br : null } ]; l = { debug : 0, log : 0, info : 'information', em : 'emphasis', warn : 'warning', error : 'error' }; for (i in l) { controller.push(' ', { a : log_icon[i], href : '#', title : 'toggle [' + i + ']\n切換 ' + (l[i] || i) + ' 訊息', onclick : function() { var tag = this.title.match(/\[([^\]]+)\]/); if (tag) CeL.set_class(this, 'debug_hide', { remove : CeL.toggle_log(tag[1]) }); return false; } }); } // 增加 group 以便在多項輸入時亦可 toggle 或排版。 CeL.Log.controller = { div : [ { a : 'log', href : '#', title : 'log 控制項', onclick : function() { var parentNode = this.parentNode; if (parentNode.force_show) { // DOM 不可使用 delete @ IE9 // delete parentNode.force_show; parentNode.force_show = false; } else { CeL.toggle_display(this.nextSibling, // parentNode.force_show = true); } return false; } }, { span : controller, C : 'debug_controller' } ], // TODO: 即使僅是移動 mouse 進入 child,也會執行多次。 onmouseover : function() { CeL.toggle_display(this.firstChild.nextSibling, 1); }, onmouseout : function() { if (!this.force_show) { CeL.toggle_display(this.firstChild.nextSibling, 0); } }, C : 'debug_controller_panel' }; // 在 CeL.log 被重新設定前先 cache 一下。 var log_buffer = CeL.log && CeL.log.buffer; // -------------------------------------------------------------------------------------------- // front ends of log function /** * 警告: 在 node.js v0.10.25, v0.11.16 中,不使用 var 的模式設定 function,會造成:
* In strict mode code, functions can only be declared at top level or * immediately within another function. * * 在 node.js v4.2.1 中可以順利 pass。 */ var log_front_end_fatal = // fatal: the most serious 致命錯誤。 function log_front_end_fatal(message, error_to_throw) { if (CeL.is_WWW()) try { console.trace(error_to_throw); // 模擬 throw 以 get .stack throw CeL.is_type(error_to_throw, 'Error') ? error_to_throw : new Error(error_to_throw || 'Fatal error'); } catch (e) { // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack CeL.error(e.stack ? message + '
stack:
' + (typeof e.stack === 'string' ? e.stack.replace( /\n/g, '
') : e.stack) + '
' : message); if (typeof console === 'object' && console.trace) { // Will show stacks console.trace(e); } } else CeL.error(message); if (typeof error_to_throw === 'undefined') // 預設會 throw message. error_to_throw = message; if (error_to_throw) { if (CeL.platform.nodejs && error_to_throw !== message) // node.js 中,throw Error 可能無法顯示 local encoding,因此在此先顯示一次。 console.error(error_to_throw); throw CeL.is_type(error_to_throw, 'Error') ? error_to_throw : new Error(error_to_throw); } } var log_front_end_debug = // 增加 debug 訊息。 function log_front_end_debug(message, level, caller, clean) { if (false) { alert(CeL.is_debug() + ',' + l + '(' + (l === undefined) + '),' + message); } if (!CeL.is_debug(level)) { return; } if (typeof message === 'function') { // for .debug(function(){return some_function(..);}, 3); message = 'function: [' + message + ']
return: [' + message() + ']'; } if (!caller && has_caller) { // TODO: do not use arguments caller = caller !== arguments.callee && CeL.get_function_name(arguments.callee.caller); if (false) { CeL.log(CeL.is_type(arguments.callee.caller)); CeL.log(Array.isArray(caller)); CeL.log(caller + ': ' + arguments.callee.caller); CeL.warn(CeL.debug); } } if (caller) { message = CeL.is_WWW() ? [ { // (caller.charAt(0) === '.' ? CeL.Class + caller : // caller) span : caller, 'class' : 'debug_caller' }, ': ', message ] : CeL.to_SGR([ '', 'fg=yellow', caller + ': ', '-fg', message ]); } CeL.Log.log(message, clean, { level : 'debug', add_class : 'debug_' + (level || CeL.is_debug()) }); } var log_front_end_info = // function log_front_end_info(message, clean) { // information CeL.Log.log.call(CeL.Log, message, clean, 'info'); // CeL.log.apply(CeL, arguments); }; var log_front_end_toggle_log = // 切換(顯示/隱藏)個別訊息。 function log_front_end_toggle_log(type, show) { if (!type) type = 'debug'; var hiding = type in CeL.Log.class_hide; if (typeof show === 'undefined' || show && hiding || !show && !hiding) try { // need switch. var style_sheet = document.styleSheets[0], selector = '.' + CeL.Log.className_set[type], CSS_index = hiding ? search_CSS_rule( style_sheet, selector) : undefined; if (isNaN(CSS_index)) { // assign a new index. CSS_index = style_sheet.cssRules && style_sheet.cssRules.length || // IE6 style_sheet.rules && style_sheet.rules.length || 0; CeL.debug('insert CSS index: ' + CSS_index, 2, 'toggle_log'); var style = 'display:none'; style_sheet.insertRule ? /** * firefox, IE 必須輸入 index.
* insertRule - MDN */ style_sheet.insertRule(selector + '{' + style + ';}', CSS_index) : /** * IE6: IHTMLStyleSheet::addRule * method (Internet Explorer) */ style_sheet.addRule(selector, style, CSS_index); // OK 之後才設定. CeL.Log.class_hide[type] = CSS_index; } else { CeL.debug('delete CSS index: ' + CSS_index, 2, 'toggle_log'); style_sheet.deleteRule ? style_sheet .deleteRule(CSS_index) : // IE6 style_sheet.removeRule(CSS_index); // OK 之後才 delete. delete CeL.Log.class_hide[type]; } hiding = !hiding; } catch (e) { CeL .log('The browser may not support Document Object Model CSS? Cannot toggle debug message: ' + e.message + ''); } return !hiding; } var log_front_end_assert = /** * 斷定/測試/驗證 verify/檢查狀態。
* * @param {Boolean|Array|Function}condition * test case.
* {Function} testing function to run. Using default expected * value: true
* {Array} [ condition 1, condition 2 ]
* {Object} 直接將之當作 options * * @param {Object}[options] * 附加參數/設定選擇性/特殊功能與選項。 {
* {String}name: test name 此次測試名稱。,
* {String}NG: meaning of failure,
* {String}OK: meaning of passed,
* {String}hide_OK: false: 當 passed 時不顯示,
* {Boolean}ignorable: false / need 手動 check,
* {String|Object}type: expected type,
* {Boolean}no_cache: false,
* {Any}expect: expected value 預期的結果。should be what value.,
* {Number}error_rate > 0: 容許誤差率 permissible error ratio. * e.g., Number.EPSILON,
* {Boolean}exactly: true, need exactly (value === expected) * or false: equal (value == expected) is also OK.
* {Boolean}force_true: false, 當測試效能時,強迫測試結果一定成功。
* {String}eval: testing expression code to eval = value / * function(){return value_to_test;}
* {Function}callback: 回調函數。 callback(passed)
} * * @returns {Boolean|...} {Boolean}assertion is succeed.
* {...} ignorable message. * * @since 2012/9/19 00:20:49, 2015/10/18 21:31:35 重構 */ function log_front_end_assert(condition, options) { // -------------------------------- // 前置處理作業: condition。 if (!options) { if (CeL.is_Object(condition)) { // 直接將之當作 options options = condition; condition = options.eval; } else { // 前置處理作業: options。 // (undefined | null).attribute is NOT accessable. // ('attribute' in false), ('attribute' in 0) is NOT // evaluable. // options = Object.create(null); // This is faster. options = new Boolean; // assert: options.attribute is accessable. // assert: ('attribute' in options) is evaluable. } } else if (typeof options === 'string') { options = { name : options }; } else if (typeof options === 'function') { options = { callback : options }; } var type = options.type; if (Array.isArray(condition)) { condition = condition.slice(0, type ? 1 : 2); if (!type && typeof condition[1] !== 'function' && typeof condition[1] !== 'object') // record original condition. condition.original = condition[0]; } else { // 有 options.type 將忽略 options.expect 以及 condition[1]!! if (type) { condition = [ condition ]; } else { condition = [ condition, // default expected value: true 'expect' in options ? options.expect : true ]; // record original condition. condition.original = condition[0]; } } // assert: condition = {Array} [ condition 1, condition 2 ] function condition_handler(_c, index) { if (options.eval && typeof _c === 'string') _c = CeL.eval_parse(_c); if (typeof _c === 'function') _c = _c(); // may use .map() condition[index] = _c; } // fatal error var fatal; // if (!options.force_true) condition.forEach(options.no_cache ? condition_handler // : function(_c, index) { try { condition_handler(_c, index); } catch (e) { // 執行 condition 時出錯,throw 時的處置。 fatal = e || true; CeL.warn('assert: 執行 condition 時出錯: ' + e.message); if (typeof console === 'object' && console.trace) { // Will show stacks console.trace(e); } } }); // assert: condition = // {Array} [ 純量 value 1, 純量 value 2: expected value ] // condition = The actual value to test. // -------------------------------- var exactly, equal; if (!fatal // && !options.force_true ) { if (type) { // 處理作業: type。 condition = condition[0]; exactly = equal = typeof type === 'string' // ? typeof condition === type || CeL.is_type(condition, type) // TODO: check // String|Function|Object|Array|Boolean|Number|Date|RegExp|Error|undefined : condition.constructor === type || Object.getPrototypeOf(condition) === type || (type = CeL.native_name(type)) && CeL.is_type(condition, type); } else if ((equal = +options.error_rate) > 0) { // 容許誤差率 permissible error ratio / rate. e.g., // Number.EPSILON exactly = equal = Math.abs(1 - +condition[0] / +condition[1]) <= equal; } else { exactly = equal = Object.is(condition[0], condition[1]); if (!exactly) { // Do not use "===" equal = condition[0] == condition[1]; } } } // -------------------------------- // report. var MAX_LENGTH = 200; function get_type_of(value, with_quote) { var type, is_native_type; if (Array.isArray(value)) { type = 'Array'; is_native_type = true; } else if (CeL.is_Object(value)) { type = 'Object'; is_native_type = true; } else if (CeL.is_RegExp(value)) { type = 'RegExp'; is_native_type = true; } else if (CeL.is_Date(value)) { type = 'Date'; is_native_type = true; } else if (value instanceof Error) { type = 'Error'; is_native_type = true; } else { type = typeof value; if (type === 'object') { type = CeL.is_type(value) || type; } else if ([ 'string', 'number', 'boolean', 'function', // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof 'bigint', 'symbol' ].includes(type)) { is_native_type = true; type = type.charAt(0).toUpperCase() + type.slice(1); } } return with_quote ? is_native_type ? '{' + type + '}' : '(' + type + ')' : type; } function quote(message, add_type) { if (add_type && // 有些 value 沒必要加上 type。 message !== null && message !== undefined // is not NaN && message === message) { add_type = get_type_of(message, true) + ' '; } else { add_type = ''; } if (typeof message === 'string' && add_type && message.length <= MAX_LENGTH && typeof JSON === 'object' && JSON.stringify) { message = JSON.stringify(message); } else { message = String(message).replace(/\r/g, '\\r'); if (message.length > MAX_LENGTH) message = '[' + message.slice(0, MAX_LENGTH) + ']...' + message.length; else message = '[' + message + ']'; } return add_type + message; } var test_name = options.name ? quote(options.name) : 'Assertion'; // -------------------------------- // failed. if (!options.force_true && (!equal || !exactly && // assert: exactly === true 的條件比 equal === true 嚴苛。 (!('exactly' in options) || options.exactly))) { var error_message = options.NG; if (!error_message) { error_message = [ test_name, // if fault, message: 失敗時所要呈現訊息。 CeL.to_SGR([ ' ', 'fg=red', 'failed:', '-fg;-bg', ' ' ]) ]; if (type) { error_message.push('type of ' + quote(condition) + ' is not (' + type + ')'); } else { if (('original' in condition) && condition[0] !== condition.original) { var original = '' + condition.original; if (typeof condition.original === 'function') { var matched = original .match(CeL.PATTERN_function); if (matched) { original = matched[1]; } } error_message.push(quote(original) + '→'); } error_message.push(quote(condition[0], true) // + ' !== ' + quote(condition[1], true)); } if (equal) { error_message.push(',但 "==" 之關係成立。'); } error_message = error_message.join(''); } CeL.fatal(error_message, CeL.assert.throw_Error ? // exception to throw new Error(error_message) : false); var ignorable = options.ignorable; return ignorable ? ignorable === true ? 'ignored' : ignorable : fatal ? undefined : false; } // -------------------------------- // passed. 無錯誤發生。 if (!options.hide_OK && CeL.is_debug()) { var passed_message = options.OK; if (!passed_message) { passed_message = [ test_name, // CeL.to_SGR([ ' ', 'fg=green', 'passed', '-fg', ' ' ]), // quote(condition[0], true) ].join(''); } CeL.debug(passed_message, 1, // caller: see: CeL.debug has_caller && CeL.get_function_name(arguments.callee.caller)); } return true; } var log_front_end_test = /** * 整套測試, unit test 單元測試。 * * @example CeL.test([ [ 'aa', { type : String } ], [ 456, { type : 123 } ], [ {}, { type : Object } ], [ false, { type : Boolean } ] ], 'type test'); // -------------------------------------- // TODO: // may ignore: CeL.setup_test(test_group_name); CeL.test(test_group_name, conditions, options); // conditions #1: [ // [ test value: true / false, 'test_group_name' ], // [ test value: true / false, {name:'test_group_name'} ], // [ test value: true / false, {options} ], // [ test value: true / false ], // test value: true / false, // // [ [ test value 1, test value 2 ], 'test_group_name' ], // [ [ test value 1, test value 2 ], {name:'test_group_name'} ], // [ [ test value 1, test value 2 ], {options} ], // [ [ test value 1, test value 2 ] ], // // [ function tester(), 'test_group_name' ], // [ function tester() ], // [ function tester(callback), {need_callback:true} ], // function tester(), // function tester() { return new Promise }, // async function tester(), // // ] // conditions #2: // function async_tester(assert) // async function async_tester(assert) // // CeL.test(test_group_name, function async_tester(assert, callback), {need_callback:true}); // // assert(test value: true / false, 'test_group_name'); // assert(test value: true / false, options); // assert(test value: true / false); // assert([ test value 1, test value 2 ], 'test_group_name'); // assert([ test value 1, test value 2 ]); CeL.test_finished(); * * @param {String}[test_group_name] * test name 此次測試名稱。 * @param {Array|Function}conditions * condition list passed to assert(): [ [ condition / test * value, options ], [], ... ].
* 允許 {Function}condition(assert, test_handler) * @param {Object}[options] * 附加參數/設定選擇性/特殊功能與選項。 {
* {String}name: test name 此次測試名稱。
* {Object}options: default options for running CeL.assert().
* {Function}callback: 回調函數。 callback(recorder, * test_group_name)
} * * @returns {Integer}有錯誤發生的數量。 * * @since 2012/9/19 00:20:49, 2015/10/18 23:8:9 refactoring 重構 */ function log_front_end_test(test_group_name, conditions, options) { if ((Array.isArray(test_group_name) || typeof test_group_name === 'function') && !options) { // shift arguments: 跳過 test_group_name。 options = conditions; conditions = test_group_name; } if (!Array.isArray(conditions) && typeof conditions !== 'function') { throw new Error(CeL.Class + '.test: Please input {Array} or {Function}!'); return; } var assert = CeL.assert, // default options for running CeL.assert(). default_options; if (options) { if (typeof options === 'function') { // console.log('Set callback: ' + options); options = { callback : options }; } else if (typeof options === 'string') { if (!test_group_name) test_group_name = options; options = undefined; } else if ('options' in options) default_options = options.options; } default_options = Object.assign({ hide_OK : true, no_cache : true }, default_options); var recorder = { // OK passed : [], // skipped ignored : [], // value is not the same. failed : [], // 執行 condition 時出錯,throw。 fatal : [], // all : [] }; function handler(condition_arguments) { if (!condition_arguments) { // skip this one. return; } recorder.all.push(condition_arguments); var result; try { if (typeof condition_arguments === 'function') { result = condition_arguments(assert_proxy); } else { // assert: Array.isArray(condition_arguments) or // arguments var options = condition_arguments[1], // condition = condition_arguments[0]; // 前置處理作業: condition, options。 // copy from log_front_end_assert() // 目的在將輸入轉成 {Object},以添入 options。 if (!options) { if (CeL.is_Object(condition)) { // 直接將之當作 options options = condition; condition = options.eval; } } else if (typeof options === 'string') { options = { name : options }; } else if (typeof options === 'function') { options = { callback : options }; } // 不汙染 default_options, options options = Object.assign(Object.clone(default_options), options); result = assert(condition, options); } } catch (e) { if (typeof console === 'object' && console.trace) { // Will show stacks console.trace(e); } recorder.fatal.push(condition_arguments); return; } switch (result) { case true: recorder.passed.push(condition_arguments); break; case false: recorder.failed.push(condition_arguments); break; default: recorder.ignored.push(condition_arguments); break; } return result === true; } // 模擬 CeL.assert() function assert_proxy() { var sub_test_data = assert_proxy.tests_left[assert_proxy.latest_sub_test_name]; if (sub_test_data) sub_test_data.assert_count++; return handler.call(null, arguments); } // -------------------------------- // report. 當有多個 setup_test(),report() 可能執行多次! var report = function(sub_test_name) { var messages; if (test_group_name) { messages = [ 'Test ' // asynchronous operations + (assert_proxy.asynchronous ? 'asynchronous ' : '') + '[', 'fg=cyan', test_group_name ]; if (sub_test_name) { messages.push('-fg', // ']=>[', ': ', ']→[', ':', ': ' ': ', 'fg=cyan', sub_test_name); } messages.push('-fg', ']: '); messages = [ CeL.to_SGR(messages) ]; } else { messages = []; } function join() { if (recorder.ignored.length > 0) messages.push(CeL.to_SGR([ ', ' + recorder.ignored.length + ' ', 'fg=yellow', 'ignored', '-fg' ])); // Time elapsed, 使用/耗費時間。cf. eta, Estimated Time of Arrival var elapsed = Date.now() - assert_proxy.starts; if (elapsed >= 1000) messages.push(', ' + (elapsed / 1000).to_fixed(2) + ' s'); messages.push(elapsed === 0 ? ', 0 s elapsed.' : ', ' + ((elapsed = recorder.all.length / elapsed) < 1 // Hz ? (1000 * elapsed).to_fixed(2) + ' tests/s.' : elapsed.to_fixed(2) + ' tests/ms.')); // console.trace(messages); return messages.join(''); } var error_count = recorder.failed.length + recorder.fatal.length; function finish() { // 確保 callback 會在本函數之後執行。 // 最後執行setTimeout(options.callback),使options.callback在訊息顯示完之後才執行。 // 因為已 callback,自此後不應改變 recorder,否則不會被 callback 處理。 if (options && typeof options.callback === 'function') { setTimeout(function() { options.callback(recorder, error_count, test_group_name); }, 0); } return error_count; } if (error_count === 0) { // all passed. 測試通過。 messages.push(CeL.to_SGR([ 'All ' + recorder.passed.length + ' ', 'fg=green', 'passed', '-fg' ])); log_front_end_info(join()); return finish(); } // not all passed. messages.push(recorder.failed.length + '/' // + (recorder.failed.length + recorder.passed.length)); if (recorder.failed.length + recorder.passed.length !== recorder.all.length) messages.push('/' + recorder.all.length); messages.push(CeL.to_SGR([ ' ', 'fg=red', 'failed', '-fg' ])); if (recorder.fatal.length > 0) { // fatal exception error 致命錯誤 messages.push(CeL.to_SGR([ ', ' + recorder.fatal.length + ' ', 'fg=red;bg=white', 'fatal', '-fg;-bg' ])); } // 不採用 log_controller,在 console 會出現奇怪的著色。 // e.g., @ Travis CI if (recorder.passed.length > 0) { // CeL.warn(join()); log_controller[2](join()); } else { // CeL.error(join()); log_controller[3](join() // hack: fg=whilte → fg=red .replace(/\x1B\[37m/g, '\x1B[31m')); } return finish(); } assert_proxy.test_group_name = test_group_name; assert_proxy.report = report; assert_proxy.options = default_options; assert_proxy.starts = Date.now(); // -------------------------------- // ready to go if (Array.isArray(conditions)) { conditions.forEach(handler); } else { var tests_count = 0, // assert: tests_count === Object.keys(tests_left).length tests_left = assert_proxy.tests_left = Object.create(null), // assert: typeof conditions === 'function' setup_test = function setup_test(sub_test_name, test_function) { // need wait (pending) assert_proxy.asynchronous = true; if (sub_test_name) { assert_proxy.latest_sub_test_name = sub_test_name; if (sub_test_name in tests_left) { CeL.warn('已登記過任務組 [' + sub_test_name + ']。'); return true; } tests_count++; tests_left[sub_test_name] = { // sub_test_data assert_count : 0 }; CeL.debug('增加任務組 [' + sub_test_name + ']。尚餘 ' + tests_count + ' 個任務組測試中。', 1, 'CeL.log.test'); } if (typeof test_function === 'function') { try { test_function(finish_test); } catch (e) { if (typeof console === 'object' && console.trace) { // Will show stacks console.trace(e); } recorder.fatal.push(sub_test_name); } if (sub_test_name) finish_test(sub_test_name); } }, // finish_test = function finish_test(sub_test_name) { if (sub_test_name in tests_left) { delete tests_left[sub_test_name]; tests_count--; } else { // 重複呼叫? CeL.warn('已登記過完成測試任務組 [' + sub_test_name + ']。'); return; } CeL.debug('完成測試 [' + sub_test_name + ']。尚餘 ' + tests_count + ' 個任務組測試中。', 1, 'CeL.log.test'); // console.trace([ tests_count, tests_left ]); if (tests_count === 0 // && CeL.is_empty_object(tests_left) ) { if (assert_proxy.tests_loaded) report(sub_test_name); else delete assert_proxy.asynchronous; } }, conditions_error = function(error) { assert_proxy.asynchronous = false; handler([ [ error, "OK" ], test_group_name ]); }; try { var result = conditions(assert_proxy, setup_test, finish_test, { test_name : test_group_name }); // allow async functions if (CeL.is_thenable(result)) { result.then(function() { assert_proxy.tests_loaded = true; // CeL.log('CeL.test: All tests loaded.'); if (tests_count > 0) { CeL.error('CeL.test: ' + tests_count + ' sub test(s) still running: ' + Object.keys(tests_left)); } report(); }, function(error) { console.trace(error); conditions_error(error); }); // Waiting... return; } assert_proxy.tests_loaded = true; if (tests_count > 0) { // console.trace([ tests_count, tests_left ]); CeL.debug('尚餘 ' + tests_count + ' 個任務組測試中。', 1, 'CeL.log.test'); } } catch (e) { // has_console if (typeof console === 'object' && console.error) { // Warning: console.error() won't show stacks @ node // v0.10 // console.trace(e) will show a wrong one. if (e && e.stack && CeL.platform.nodejs && !CeL.platform('node', 8)) { console.error(e.stack); } else { // Will show stacks console.error(e); } } conditions_error(e); } } if (!assert_proxy.asynchronous) { return report(); } else { // Waiting... } } // 在 console 則沿用舊 function。 // 這裡的判別式與 base.js 中的相符: "_.to_SGR = is_WWW ? SGR_to_plain : to_SGR;" // 因為 base.js 中的 styled log 也需要此條件才能發動。 // TODO: 增加 console 的 style (color) if (CeL.is_WWW()) { // 這裡列出的是 base.js 中即已提供,不設定也會由原先之預設函式處理的函式。 Object.assign(CeL, { log : log_controller[1], warn : log_controller[2], error : log_controller[3], info : log_front_end_info, debug : log_front_end_debug }); } Object.assign(CeL, { em : function log_front_end_em(message, clean) { // emphasis CeL.Log.log.call(CeL.Log, message, clean, 'em'); }, // 致命錯誤。 fatal : log_front_end_fatal, // 增加 debug 訊息。 trace : function log_front_end_trace() { // 使用 .apply() 預防 override。 // trace: the least serious CeL.debug.apply(CeL, arguments); }, // 切換(顯示/隱藏)個別訊息。 toggle_log : log_front_end_toggle_log, assert : log_front_end_assert, // CeL.test() test : log_front_end_test }); // 處理 loading 本 module 前即已 log 之 message。 if (Array.isArray(log_buffer) && log_buffer.length > 0) { CeL.debug({ em : 'Before loading ' + module_name + ', there are some debugging message.' }); log_buffer.forEach(function(message) { CeL.debug(message); }); CeL.debug('' + module_name + ' loaded.<\/em>'); } } }