寫程式的時候,通常我們可以利用 printf() 之類的函式,把一些程式內部的資料印出來,以追蹤程式的運行狀態。以程式設計的術語來說,這個叫做 logging。對於有 console 可以使用的程式來說,printf() 通常便已足敷使用,但若寫的是視窗程式之類沒有 console 的程式,就很麻煩,因為用以替代的 MessageBox() 可以顯示的資料不多[1],又沒辦法將訊息內容複製到剪貼簿裡。當「簡易法」無法奏效的這個時候,就必須啟用「真正的」logging 機制,把 logging 確實當作一個程式運作所必須的 component 來處理。

SHELL> find /usr/ports/devel -type d -name 'log4*' -maxdepth 1
/usr/ports/devel/log4c
/usr/ports/devel/log4cpp
/usr/ports/devel/log4j
/usr/ports/devel/log4cplus
/usr/ports/devel/log4cxx
/usr/ports/devel/log4sh

如上所示,常見的程式語言,我們有 log4* 系列的函式庫可以用,log4j 是其濫觴。對於商業軟體開發工具而言,也通常會有其對應的 logging solution 可用。

言歸正傳,最近我在用 AJAX 寫一個加強 trac 的程式時,發現 logging 之於 javascript 寫作,也很重要,因為 alert() 就跟 MessageBox() 一樣地不方便。安裝 debugger 之類的網頁整合開發環境,是個不錯的辦法。不過,其實我們也可以自己來。

簡單講,logging 其實就是把指定的資料,送到某處顯示,以便於當下或事後檢視。因此,logging 基本上必須有「傳送」與「顯示」這兩個步驟,若要做的好一點的話,可以再加個「記錄」,以便於事後檢視。因此,不考慮進階的「記錄」功能的話,要在 javascript 裡做 logging,其實很簡單,基本把握「傳送」與「顯示」兩原則即可:

function JSLogPrint(msg, tag)
{
	var JSLogEnabled = document.JSLogEnabled;
	if (JSLogEnabled == null) {
		JSLogEnabled = true;
	}

	if (JSLogEnabled) {
		/*
		 * A magic string identifier prefix that used to avoid conflict with
		 * any other HTML element id or CSS class name when we are generating
		 * them dynamically.
		 */
		var JSLogPrefix = document.JSLogPrefix;
		if (JSLogPrefix == null) {
			JSLogPrefix = 'JSLog';
		}

		/*
		 * Create output panel for displaying log messages.
		 */
		var idLogPanel = (JSLogPrefix + 'Panel');
		var domLogPanel = document.getElementById(idLogPanel);
		if (domLogPanel == null) {
			domLogPanel = document.createElement('div');
			domLogPanel.id = idLogPanel;
			domLogPanel.className = JSLogPrefix + 'Panel';
			var domBody = document.getElementsByTagName('body')[0];
			domBody.appendChild(domLogPanel);
		}

		var domEntry = document.createElement('div');
		domEntry.className = JSLogPrefix + 'Entry';

		if (tag != null) {
			var domTag = document.createElement('div');
			domTag.className = JSLogPrefix + 'Tag';
			domTag.appendChild(document.createTextNode(tag));
			domEntry.appendChild(domTag);
		}
		var domMsg = document.createElement('div');
		domMsg.className = JSLogPrefix + 'Msg';
		domMsg.appendChild(document.createTextNode(msg));
		domEntry.appendChild(domMsg);

		domLogPanel.appendChild(domEntry);
	}
}

只要把上面的 javascript function 引入,即可使用。若有 HTML element id 或 CSS class name conflict 時,請建立 document.JSLogPrefix 變數[2],並將之設成一個不會造成 conflict 的字串,其預設值為 'JSLog'。同時,也可以建立 document.JSLogEnabled 變數,以決定是否要啟動 logging,其預設值為 true。在使用時,呼叫 JSLogPrint() 傳入參數即可,每呼叫一次,便會建立一個 log entry,附加在網頁最下方。msg 參數指 log message,tag 參數可以被忽略,是用來方便識別各個 log entry,使用時,可以以 timestamp 或程式所在行數[3]為其值。顯示的 log messages 結構如下:

<div id="JSLogPanel" class="JSLogPanel">
	<div class="JSLogEntry">
		<div class="JSLogTag">tag</div>
		<div class="JSLogMsg">msg</div>
	</div>
</div>

若有設定 document.JSLogPrefix,則其中的 HTML element id 與 CSS class name 的 JSLog 字樣,應改以 document.JSLogPrefix 的值替代。為畫面美觀起見,建議設定這幾個 CSS class:JSLogPanelJSLogEntryJSLogTagJSLogMsg。我使用的 CSS 設定如下:

.JSLogPanel
{
	width: 100%;
	border-top: 1px solid;
}

.JSLogEntry
{
	padding: 0.5em;
	border-bottom: 1px dashed #ccb;
	font-size: x-small;
}

.JSLogTag
{
	text-decoration: underline;
	font-weight: bold;
}

.JSLogMsg
{
	margin-top: 0.5em;
}

如欲測試,請按這裡,填入測試用的 log message,剛輸入的 log message,將會在網頁下方顯示。另外,A List Aprt 也有一篇文章在談 JavaScript Logging,亦十分值得一讀。

[1] 另一個替代方案是,使用 OutputDebugString() 這個 Win32 API,會把傳入的字串,輸出到安裝到系統裡,目前正在作用的 debugger 上。
[2] 即在網頁裡的 javascript 全域變數。
[3] 有人知道 javascript 怎麼取得程式所在行數嗎?