内容


使用 HTML5 File API 实现客户端 log

Comments

HTML5 File API 简介

在 HTML 5 里,从 Web 网页上访问本地文件系统变的十分的简单,那就是使用 File API。 这个 File 规范说明里提供了一个 API 来表现 Web 应用里的文件对象,您可以通过编程来选择它们,访问它们的信息。File API 有以下几部分 JavaScript 类组成:

  • 读取、操作文件的类:File/Blob, FileList, FileReader
  • 创建、写入文件的类:BlobBuilder, FileWriter
  • 目录、文件系统访问的类:DirectoryReader, FileEntry/DirectoryEntry,LocalFileSystem

规范里定义了两种类型的 API:异步和同步。一般情况下应该使用异步 API,这样可以防止调用 API 的时候阻塞 UI 进程。同步 API 只可以在 Web Workers 上下文中使用,关于 Web Worker 的资料,可以在文章最后的参考链接中找到。 使用同步版本的 API 不需要指定回调函数,所有的方法将通过返回值返回方法调用的结果。在本文中将主要使用同步 API,原因有以下两点:

  • 需要保证用户调用 HTML5 Log 方法之前,HTML File API 已经初始化完毕
  • 需要保证 log 信息写入的顺序

HTML5 规范可以参考 W3C 官方网站。 HTML5 File API 规范可以参考 W3C 上的 官方网站。 HTML5 File API: Directories and System 规范可以参考 W3C 上的 官方网站。 HTML5 File API: Writer 规范可以参考 W3C 上的官方网站

File API JavaScript 类接口定义介绍

本文中主要使用了同步版本的 FileEntry、FileWriter 和 DirectoryEntry 类。

清单 1. FileEntrySync 接口定义
 [NoInterfaceObject] 
 interface FileEntrySync:EntrySync { 
    FileWriterSync createWriter() raises (FileException);// 创建 FileWriter,用来写内容到文件
    File  file() raises (FileException);// 返回代表当前 File 状态的实例
 };
清单 2. FileWriterSync 接口定义
 [NoInterfaceObject] 
 interface FileWriterSync { 
    readonly attribute unsigned long long position;// 下一次要写入内容的位置
    readonly attribute unsigned long long length;// 文件的长度
    void write (Blob data) raises (FileException);// 写入内容到文件
    void seek (long long offset) raises (FileException);// 设置下一次要写入内容的位置
    void truncate (unsigned long long size) raises (FileException);// 改变文件长度到指定大小
 };
清单 3. DirectoryEntrySync 接口定义
 [NoInterfaceObject] 
 interface DirectoryEntrySync:EntrySync { 
    DirectoryReaderSync createReader () raises (FileException);// 创建 Reader 遍历目录中的文件
	 // 根据文件路径,得到目录中文件
    FileEntrySync  getFile (DOMString path, 
		 optional Flags options) raises (FileException); 
	 // 创建或者获取子目录
    DirectoryEntrySync getDirectory (DOMString path, 
		 optional Flags options) raises (FileException); 
    void removeRecursively () raises (FileException);// 删除目录以及这个目录下面的所有内容
 };

浏览器对 File API 支持情况

表 1. File API
IEFirefoxChromeSafariOperaiOS SafariAndroid Browser
6.0( × )3.6~7.0( 支持部分 API)6.0~12.0( 支持部分 API)3.1~3.2( × )10.5~11.0( × )3.2( × )2.1( × )
7.0( × )8.0( 支持部分 API)13.0( √ )4.0( × )11.1( 支持部分 API)4.0~4.1( × )2.2,2.3( × )
8.0( × )9.0( 支持部分 API)14.0( √ )5.0( × )11.5( 支持部分 API)4.2~4.3( × )3.0( 支持部分 API)
9.0( × )10.0( 支持部分 API)15.0( √ )5.1( 支持部分 API)11.6( 支持部分 API)5.0( × )4.0( 支持部分 API)
10.0( 支持部分 API)11.0( 支持部分 API)16.0( √ )5.2( 支持部分 API)12.0( 支持部分 API)
12.0~13.0( 支持部分 API)17.0~20.0( √ )

浏览器对 File API 支持的更详细信息可以参考网站 When can I use...

客户端 log 设计

根据服务器端 log 的功能,客户端 log 主要实现设置 log 输出的级别:1 表示记录错误信息、2 表示记录警告信息、3 表示记录一般信息、4 表示记录调式信息。提供 info、error、 warn、debug 等方法便于开发人员在程序中输出 log 信息。最后需要对 log 信息进行格式化,方便开发人员根据 log 内容调试客户端应用程序。本文中的 code 是在 Dojo 框架下实现的, 运行时需要引入 Dojo。以下示例示例代码在 Chrome 16.0 中通过测试。

首先,在构造函数中初始化 log 需要的属性,logLevel 代表当前 log 需要记录信息的级别,indent 代表方法调用的深度。初始化 SPACE、MSGMAP 以及 METHODMAP 常量并且添加相应的 get、set 方法用来改变 logLevel,以及判断当前 log 级别是不是 debug 的 isDebugEnabled 方法。

清单 4. html5.Log 类实现:初始化属性
 dojo.provide("html5.Log"); 
 dojo.require("dojo.date.locale"); 
 dojo.declare("html5.Log", null, { 
	 SPACE:4, 
	 MSGMAP:{"1":"ERROR","2":"WARNING","3":"INFO","4":"DEBUG"}, 
	 // 映射 log 级别到 console 中的方法名称
	 METHODMAP:{"1":"error","2":"warn","3":"info","4":"debug"}, 
	 constructor: function() { 
		 //1 表示记录错误信息,2 表示记录警告信息,3 表示记录一般信息,4 表示记录调式信息
		 this.logLevel = 1; 
		 this.indent = 0; 
	 }, 
	 getLogLevel: function() { 
		 return this.logLevel; 
	 }, 
	 setLogLevel: function(logLevel) { 
		 this.logLevel = logLevel; 
	 }, 
	 isDebugEnabled: function() { 
		 return this.logLevel >= 4; 
	 } 
 });

然后,仿照服务器端 log 的设计,加入相应的 error、info、warn、debug、entry、exit 等方法。

清单 4. html5.Log 类实现:添加记录 log 内容的方法
 dojo.declare("html5.Log", null, { 

	 // 记录错误信息
	 error: function(location, message, extra) { 
		 this._record(1, location, message, extra); 
	 }, 
	 // 省略 info,warn,debug 方法,方法参数和方法主体和 error 方法类似。
	
	 // 进入一个函数
	 entry: function(location, extra) { 
		 this._record(-1, location, "Entry", extra); 
		 this.indent += this.SPACE; 
	 } 
	 // 省略 exit 方法,具体实现参考附件中的源代码。
	
	 _record: function(level, location, message, extra) { 
		 var logEntry = { 
			 level: level, 
			 time: new Date(), 
			 loc: location, 
			 msg: message, 
			 extra: extra, 
			 indent: this.indent 
		 }; 	
		 if (level <= this.logLevel) { 
			 var logInfo = this._formatLogEntry(logEntry); 
			 // 根据 log 级别映射到 console 中的不同方法,把信息输出到浏览器控制台
			 if(this.METHODMAP[level+""]){ 
				 console[this.METHODMAP[level+""]](logInfo); 
			 } 
			 else{ 
				 console.log(logInfo); 
			 } 
		 } 
	 } 
 });

最后,根据自己的需要加入格式化 log 信息内容的方法。

清单 4. html5.Log 类实现-添加记录 log 内容的方法
 dojo.declare("html5.Log", null, { 

	 _formatLogEntry: function(logEntry) { 	
		 var result = dojo.date.locale.format(logEntry.time, 
			 {datePattern: "yyyy-MM-dd", timePattern: "HH:mm:ss "}); 
		 for ( var i = 0; i < logEntry.indent; i++) { 
			 result += " "; 
		 } 
		 if(logEntry.level>=0){ 
			 result += this.MSGMAP[logEntry.level+""]+ " - "; 
		 }// 在每条记录前加入 log 的时间已经 log 的级别
		 result += logEntry.loc; 
		 if (logEntry.msg) { 
			 result += " - " + this._formatObject(logEntry.msg); 
		 } 
		 if (logEntry.extra) { 
			 result += " - " + this._formatObject(logEntry.extra); 
		 } 
		 result += "\r\n"; 
		 return result; 
	 }, 
	 // 格式化 Object 类型信息
	 _formatObject: function(data) { 
		 if (dojo.isObject(data)) { 
			 return dojo.toJson(data); 
		 } else { 
			 return data; 
		 } 
	 } 
 });

使用 HTML5 File API 实现输出客户端 log 信息到文件

在传统的客户端 log 设计中,只能将客户端 log 输出到浏览器的控制台,但在客户环境中,客户端程序一旦出错,客户将收集不到这些 log 信息,对于开发人员来说, 一旦客户端程序出错,需要模拟客户的真实环境,重现错误信息,这将是一件非常有挑战的工作,因为客户的实际环境千差万别。本文将根据上面传统客户端 log 的设计,使用 HTML5 File API 实现 将客户端 log 输出到文件中,一旦客户端程序出错,可以很容易的收集到这些 log 信息。

1. 实现 Web Workers

本文中需要使用同步版本的 File API,所以需要实现一个 Web Workers 用来将信息输出到文件中。在 html5.Log 类中,通过 postMessage/onmessage API 实现和 Web Workers 的数据交互。

清单 5. 实现 Web Workers
 // 解决 File API 浏览器兼容性问题
 self.requestFileSystemSync = self.webkitRequestFileSystemSync 
	 || self.requestFileSystemSync; 
 self.BlobBuilder = self.BlobBuilder || self.WebKitBlobBuilder 
	 || self.MozBlobBuilder; 

 var html5logFolder = null; 
 self.onmessage = function(e) { 
	 var data = e.data; 
	 switch (data.cmd){ 
		 // 初始化 log 文件夹
		 case "init":{ 
             var fs = self.requestFileSystemSync(PERSISTENT, data.grantedBytes); 
             html5logFolder = fs.root.getDirectory('html5logs', {create: true}); 
			 break; 
		 } 
		 // 写内容到 log 文件
		 case "log":{ 
             var fileEntry = html5logFolder.getFile(data.logFileName, {create: true}); 
			 var fileWriter = fileEntry.createWriter(); 
			 var blob = new BlobBuilder(); 
			 blob.append(data.logEntryStr); 
			 fileWriter.seek(fileWriter.length); 
			 fileWriter.write(blob.getBlob('text/plain')); 
			 break; 
		 } 
	 } 
 };

在 Web Workers JavaScript 文件中,通过 onmessage 方法,接受 html5.Log 类中发过来的命令。在 init 命令中,将申请相应的磁盘空间,并创建一个 html5logs 的文件夹,其中 create:true 表示如果文件夹不存在则创建,存在则返回文件夹示例。在 log 命令中,将把 log 内容输出到具体的 log 文件中。同理 getFile 方法中的 create:true 参数意义和 getDirectory 方法一样。

2. 重构 html5.Log 类,实现与 Web Workers 交互

首先,需要在 html5.Log 类中创建一个 Web Workers。在 METHODMAP 属性后面加上私有的属性 _logData 和 _logWorker。 其中 _logData 用来表示 Web Workers 中的 init 命令是否已经被调用了,而 _logWorker 用来表示真正的 Web Workers 实例。

清单 6. 添加类属性
 METHODMAP:{"1":"error","2":"warn","3":"info","4":"debug"}, 
 _logData:{"status":false}, 
 _logWorker:new Worker('HTML5File.js'),

然后,在 html5.Log 类的构造函数中使用 postMessage 方法,向 Web Workers 发送 init 命令。

清单 7. 向 Web Workers 发送 init 命令
 constructor: function(grantedBytes) { 
	 //1 表示记录错误信息,2 表示记录警告信息,3 表示记录一般信息,4 表示记录调式信息
	 this.logLevel = 1; 
	 this.indent = 0; 

	 if(!this._logData.status){// 所有实例只初始化一次
		 this._logData.status = true; 
      this._logWorker.postMessage({"cmd":"init","grantedBytes":grantedBytes});
	 } 
	 this._logWorker.onmessage = function(e) { 
		 // 用来接收 Web Workers 返回的数据
	 } 
 }

最后,在 html5.Log 类的 _record 方法中,调用 _writeLogEntryToFile 方法并在这个方法里使用 postMessage 方法,向 Web Workers 发送 log 命令,传递 log 文件名称以及要记录的 log 信息作为参数。

清单 8. 向 Web Workers 发送 log 命令
 _record: function(level, location, message, extra) { 
	 var logEntry = { 
		 level: level, 
		 time: new Date(), 
		 loc: location, 
		 msg: message, 
		 extra: extra, 
		 indent: this.indent 
	 }; 	
	 if (level <= this.logLevel) { 
		 var logInfo = this._formatLogEntry(logEntry); 
		 this._writeLogEntryToFile(logEntry,logInfo); 
	 } 
 }, 
 _writeLogEntryToFile:function(logEntryObj,logEntryStr){ 
	 // 每一天创建一个 log 文件
	 var logFileName = dojo.date.locale.format(logEntryObj.time, 
		 {datePattern: "yyyy-MM-dd'.log'","selector":"date"}); 
	 this._logWorker.postMessage({"cmd":"log","logFileName":logFileName,"logEntryStr":logEntryStr});
 }

收集客户端 log

在浏览器中通过 HTML5 File API 操作文件时,所有的 log 信息都是存在客户端的机器上。 通过 HTML5 File API 创建的目录以及文件都是由浏览器进行管理的,用户很难在磁盘中的某个位置找到这些 log 文件。 所以有必要实现 log 文件下载功能,更方便地提供用户通过浏览器直接收集 log 文件。

在提供 log 下载的 code 中,本文将使用 HTML5 File API 异步版本,基于 Dojo 实现简单的 UI,通过 DirectoryEntry 类提供的方法遍历 html5logs 文件夹,遍历出里面的所有文件,按照日期排序,出输到页面中, 并提供下载功能,用户可以单击下载链接,将 log 文件下载的磁盘中的某个位置。

清单 9. 获取 html5logs 文件夹下所有文件
 _readEntries:function(){ 
	 var dirReader = this.html5logFolder.createReader(); 
	 var entries = [],self = this; 
	 var readFiles = function() { 
		 dirReader.readEntries(function(results) {// 读取目录内容,直到返回内容为 0 
			 if (!results.length) { 
				 entries.sort(function(a, b) { 
                  return a.name < b.name ? -1 :b.name < a.name ? 1 : 0;
				 }); 
				 self._listResults(entries); 
			 } else { 
                 entries = entries.concat(Array.prototype.slice.call(results||[],0));
				 readFiles(); 
			 } 
		 }); 
	 }; 
	 readFiles(); 
 }

这里主要使用了 DirectoryEntry 类的 readEntries 方法,递归获取 html5logs 文件夹下的所有 log 文件,并根据文件名排序,随后调用 _listResults 方法,输出结果到 UI。具体的实现参考附件中的源码。

测试 HTML5 客户端 log 实现

首先,需要创建一个 html5.Log 类的实例。这里需要注意的是,由于程序要请求 PERSISTENT(永久)类型的磁盘空间来存储文件, 而浏览器发现程序请求使用 PERSISTENT(永久)类型的空间时,需要征求用户的同意。在用户同意以后,才能授权使用 PERSISTENT(永久)类型的磁盘空间。 所以创建 html5.Log 的代码需要放在 requestQuota 方法的回调函数中。

图 1. 请求使用磁盘空间
请求使用磁盘空间
请求使用磁盘空间
清单 10. 创建 html5.Log 实例
 var logger = null; 
 window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024*10/*10MB*/, 
	 function(grantedBytes) { 
		 logger = new html5.Log(grantedBytes); 
		 logger.setLogLevel(4); 
 });

然后,在程序中就可以使用 html5.Log 来记录信息了。

清单 11. 使用 html5.Log
 function sum(a,b){ 
	 logger.entry("Enter function sum","a="+a+",b="+b); 
	 var result = a+b; 
	 logger.debug("function sum,line 66","The result is:",result); 
	 logger.exit("Exit function sum"); 
	 return result; 
 }

下载 log 文件

图 2. 测试页面
测试页面
测试页面

点击写日志按钮可以运行测试用例,单击显示日志列表按钮将用下面的表格列出所有的 log 文件,单击某一个日志文件的下载链接,可以下载 log 到本地。页面源码请参考附件中的文件。

图 3. 下载 log 文件
下载 log 文件
下载 log 文件
图 4. log 文件内容
log 文件内容
log 文件内容

结束语

在传统的 web 应用程序中,服务器端会有很多 log 文件,客户在使用应用程序出错的时候,开发人员可以根据 log 定位错误。但是客户端却不能记录 log,因为在 HTML5 以前,不能在客户端写信息到文件中。 随着 HTML5 的推出,File API 已经可以实现在客户端把内容写进文件中,这样即使客户端程序出现错误,开发人员仍然可以根据 log 信息定位出程序的问题。 随着 Web2.0 的发展,客户端应用程序越来越丰富,随之而来的客户端程序复杂度越来越大,记录 log 信息也越来越必要。 本文基于 HTML5 File API 实现了简单的客户端 log 读写以及下载功能,基于本文的思路,用户可以自己扩展功能,实现基于配置文件信息的客户端 log 功能。


下载资源


相关主题

  • 关于 HTML5 的规范,请参考 HTML5
  • 关于 HTML5 File API 的规范,请参考 File API
  • 关于 HTML5 File API: Directories and System 的规范,请参考 Directories and System
  • 关于 HTML5 File API: Writer 的规范,请参考 Writer
  • 关于 HTML5 Web Worker 的规范,请参考 Web Worker
  • 关于 HTML5 Web Worker Wiki,请参考 Web Worker Wiki
  • 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。
  • developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
  • developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
  • developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。
  • 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=843026
ArticleTitle=使用 HTML5 File API 实现客户端 log
publish-date=11012012