diff --git a/.babelrc b/.babelrc index f9f2a02..3bff21e 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { - "presets": [ "es2015" ] -} \ No newline at end of file + "presets": [ "es2015-rollup" ] +} diff --git a/.editorconfig b/.editorconfig index 6bfa09f..2ec8766 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,3 +21,6 @@ indent_size = 4 [*.json] # 缩进2个空格 indent_size = 2 + +[makefile] +indent_style = tab diff --git a/CHANGELOG b/CHANGELOG index 758002b..a015504 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,15 +1,21 @@ +v1.0.4 +U use rollup.js bundler to reduce dist package size(33% off) +A add makefile, use make for automatic procedures +U replace comments in English +A add `get` method to get logs in suitable time range, rename `getAll` to `all` + V1.0.3 -F indexDB使用自增键作为主键,修复在高频存储调用过程中会报错的问题(因为唯一keypath timestamp可能会重复) -U 优化了localStorage协议的存储结构,节省空间 +F add auto-increase as key of IndexedDB, to fix error when logging in high frequency(old keypath timestamp may repeat) +U updated localStorage protocol's storage structer to save spaces V1.0.2 -A 自动按照协议的优先级选择可用的协议,优先级来自构建时的参数 -A 自定义数据库名 +A ability to choose protocol automatically +A custom database name V1.0.1 -D 解耦日志上传功能,api `reportTO` 和 `deploy` 被移除 +D log upload api `reportTo` and `deploy` is removed, V1.0.0 -A 日志记录 -A 日志上传 -A 自定义构建,允许只打包业务需要的协议 +A log record +A log upload +A custom build, allow build with only protocols wanted diff --git a/README.md b/README.md index b6cc24a..e270996 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ access [https://github.com/latel/logline/releases](https://github.com/latel/logl ### 2. Import to your project Logline is an UMD ready module, choose to import it as your project needed. +CMD is evil, which is not supported, wrapper it yourself if you need it indeed. ``` javascript // using + + + diff --git a/makefile b/makefile new file mode 100644 index 0000000..341f318 --- /dev/null +++ b/makefile @@ -0,0 +1,21 @@ +PATH := node_modules/.bin:$(PATH) + +default: clean configure dev prod test + +configure: + npm run configure + +dev: + npm run build:dev + +prod: + npm run build:prod + +test: + npm run test + +clean: + @rm -f dist/* + + +.PHONY: default configure dev prod test clean diff --git a/package.json b/package.json index 5f5abaa..4ad51f7 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "logline", - "version": "1.0.3", + "version": "1.0.4", "description": "logs for the frontend", "main": "dist/logline.min.js", "scripts": { - "test": "phantomjs ./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js test/index.html", - "configure": "gulp configure", - "build:dev": "webpack --config webpack.config.js --colors --display-modules --sort-modules-by size --profile", - "build:prod": "webpack --config webpack.config.prod.js", + "test": "./node_modules/.bin/phantomjs ./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js test/index.html", + "configure": "./node_modules/.bin/gulp configure", + "build:dev": "./node_modules/.bin/rollup -c", + "build:prod": "NODE_ENV=production ./node_modules/.bin/rollup -c", "build": "npm run build:dev && npm run build:prod && npm run test" }, "repository": { @@ -22,7 +22,7 @@ "websql", "localstroage" ], - "author": "latel ", + "author": "latel ", "license": "MIT", "bugs": { "url": "https://github.com/latel/logline/issues" @@ -30,25 +30,24 @@ "homepage": "https://github.com/latel/logline#readme", "devDependencies": { "babel-core": "^6.7.7", - "babel-loader": "^6.2.4", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-transform-es2015-modules-umd": "^6.18.0", - "babel-plugin-transform-runtime": "^6.15.0", - "babel-preset-es2015": "^6.6.0", - "babel-preset-stage-3": "^6.5.0", + "babel-preset-es2015-rollup": "^3.0.0", "chai": "^3.5.0", "colors": "^1.1.2", - "extract-text-webpack-plugin": "^1.0.1", "fs-extra": "^1.0.0", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-compile-handlebars": "^0.6.1", "gulp-rename": "^1.2.2", "html-loader": "^0.4.3", + "jsonfile": "^2.4.0", "minimist": "^1.2.0", "mocha": "^2.5.3", "mocha-phantomjs-core": "^2.1.0", "moment": "^2.13.0", - "webpack": "~1.12.11" + "phantomjs": "^2.1.7", + "rollup": "^0.41.4", + "rollup-plugin-babel": "^2.7.1", + "rollup-plugin-license": "^0.2.0", + "rollup-plugin-uglify": "^1.0.1" } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..546ce33 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,31 @@ +// Rollup plugins +import * as path from 'path'; +import babel from 'rollup-plugin-babel'; +import uglify from 'rollup-plugin-uglify'; +import license from 'rollup-plugin-license'; +import * as jsonfile from 'jsonfile'; + +const pkg = jsonfile.readFileSync('./package.json'); + +export default { + entry: 'src/' + pkg.name + '.js', + dest: 'dist/' + pkg.name + (process.env.NODE_ENV === 'production' ? '.min' : '') + '.js', + format: 'umd', + moduleName: pkg.name.replace(/^\w/, starter => starter.toUpperCase()), + sourceMap: process.env.NODE_ENV === 'production', + plugins: [ + babel({ + exclude: 'node_modules/**' + }), + (process.env.NODE_ENV === 'production' && uglify()), + license({ + banner: { + file: path.join(__dirname, 'src', 'BANNER') + }, + thirdParty: { + output: path.join(__dirname, 'dist', 'dependencies.txt'), + includePrivate: true + } + }) + ] +}; diff --git a/src/BANNER b/src/BANNER new file mode 100644 index 0000000..6f5cab1 --- /dev/null +++ b/src/BANNER @@ -0,0 +1,3 @@ +<%=pkg.name%> v<%=pkg.version%> (<%=pkg.homepage%>) +Copyright <%=new Date().getFullYear()%>, <%=pkg.author%> +<%=pkg.license%> license diff --git a/src/configure b/src/configure index ed7cbd1..5775eea 100644 --- a/src/configure +++ b/src/configure @@ -1,3 +1,4 @@ +import Interface from './protocols/interface'; {{#each protocols}} import {{upper this false}}Logger from './protocols/{{this}}'; {{/each}} @@ -5,18 +6,38 @@ import * as util from './lib/util'; class Logline { + /** + * Logline constructor + * @constructor + * @param {String} namespace - namespace to use + * @return {Object Protocol Instance} + */ constructor(namespace) { Logline._checkProtocol(); return new Logline._protocol(namespace); } - // 选择并初始化协议 + /** + * choose a protocol to initialize + * @method _initProtocol + * @private + * @static + * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL + * @return {Object} Logline + */ static _initProtocol(protocol) { Logline._protocol = protocol; Logline._protocol.init(Logline._database || 'logline'); } - // 检查协议 + /** + * check protocol + * if no protocol is chosen, will try to choose an available one automatically + * if none of the protocols is available, an error will be thrown + * @method _checkProtocol + * @private + * @static + */ static _checkProtocol() { if (!Logline._protocol) { let protocols = Object.keys(Logline.PROTOCOL), protocol; @@ -31,27 +52,79 @@ class Logline { } } - // 获取所有日志 - static getAll(readyFn) { + /** + * get logs in range + * if from and end is not defined, will fetch full log + * @method get + * @static + * @param {String} [from] - time from + * @param {String} [to] - time end + * @param {Function} readyFn - function to call back with logs as parameter + */ + static get(from, to, readyFn) { + var now = Date.now(); Logline._checkProtocol(); - Logline._protocol.all(logs => readyFn(logs)); + + switch (arguments.length) { + case 1: + readyFn = from; + from = undefined; + break; + case 2: + readyFn = to; + to = undefined; + break; + case 3: + default: + break; + } + + Logline._protocol.get(from, to, readyFn); } - // 清理日志 + /** + * read all logs + * @method all + * @static + * @param {Function} readyFn - function to call back with logs as parameter + */ + static all(readyFn) { + Logline.get(readyFn); + } + + /** + * clean up logs = keep limited logs + * @method keep + * @static + * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3' + * @return {Object} Logline + */ static keep(daysToMaintain) { Logline._checkProtocol(); Logline._protocol.keep(daysToMaintain); return this; } - // 清空日志并删除数据库 + /** + * delete log database + * @method clean + * @static + * @return {Object} Logline + */ static clean() { Logline._checkProtocol(); Logline._protocol.clean(); return this; } - // 选择一个日志协议 + /** + * choose a protocol + * @method using + * @static + * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL + * @param {String} [database] - custome database name + * @return {Object} Logline + */ static using(protocol, database) { // protocol unavailable is not allowed if (-1 === [{{join protocols 'Logger' ', '}}].indexOf(protocol)) { @@ -68,11 +141,18 @@ class Logline { return this; } + /** + * specialfy a custome database name, in case of any conflicts + * @methd database + * @static + * @param {String} name - target database name + */ static database(name) { Logline._database = name; } } +// export protocols for modification and mounting Logline.PROTOCOL = { {{#each protocols}} {{#compare ../protocols.length @index}} @@ -83,4 +163,7 @@ Logline.PROTOCOL = { {{/each}} }; -module.exports = Logline; +// export protocol interface for user custom implements +Logline.INTERFACE = Object.freeze(Interface); + +export default Logline; diff --git a/src/lib/pool.js b/src/lib/pool.js index 67e54e4..4f4e65b 100644 --- a/src/lib/pool.js +++ b/src/lib/pool.js @@ -1,10 +1,10 @@ /** - * 队列池,用于异步调用过程的寄存 + * Pool, for storage of async calling * @class Pool */ export default class Pool { /** - * 队列池构造器 + * Pool constructor * @constructor */ constructor() { @@ -12,10 +12,10 @@ export default class Pool { } /** - * 向队列中添加过程 + * add an procedure * @method push - * @param {Function} handler - 过程函数 - * @param {Object} context - 过程函数的上下文 + * @param {Function} handler - procedure handler + * @param {Object} context - procedure context */ push(handler, context) { handler.context = context; @@ -23,7 +23,7 @@ export default class Pool { } /** - * 消费队列 + * consume pool * @method consume */ consume() { diff --git a/src/lib/util.js b/src/lib/util.js index ed63067..528606f 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1,4 +1,4 @@ -// 抛出Error错误,并在错误描述前统一添加'Logline: ' +// throw out Errors, with global prefix 'Logline: ' ahead of err.message export function throwError(errMessage) { throw new Error('Logline: ' + errMessage); } diff --git a/src/logline.js b/src/logline.js index 60e4170..55666f6 100644 --- a/src/logline.js +++ b/src/logline.js @@ -1,22 +1,43 @@ +import Interface from './protocols/interface'; import IndexeddbLogger from './protocols/indexeddb'; -import WebsqlLogger from './protocols/websql'; import LocalstorageLogger from './protocols/localstorage'; +import WebsqlLogger from './protocols/websql'; import * as util from './lib/util'; class Logline { + /** + * Logline constructor + * @constructor + * @param {String} namespace - namespace to use + * @return {Object Protocol Instance} + */ constructor(namespace) { Logline._checkProtocol(); return new Logline._protocol(namespace); } - // 选择并初始化协议 + /** + * choose a protocol to initialize + * @method _initProtocol + * @private + * @static + * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL + * @return {Object} Logline + */ static _initProtocol(protocol) { Logline._protocol = protocol; Logline._protocol.init(Logline._database || 'logline'); } - // 检查协议 + /** + * check protocol + * if no protocol is chosen, will try to choose an available one automatically + * if none of the protocols is available, an error will be thrown + * @method _checkProtocol + * @private + * @static + */ static _checkProtocol() { if (!Logline._protocol) { let protocols = Object.keys(Logline.PROTOCOL), protocol; @@ -31,30 +52,82 @@ class Logline { } } - // 获取所有日志 - static getAll(readyFn) { + /** + * get logs in range + * if from and end is not defined, will fetch full log + * @method get + * @static + * @param {String} [from] - time from + * @param {String} [to] - time end + * @param {Function} readyFn - function to call back with logs as parameter + */ + static get(from, to, readyFn) { + var now = Date.now(); Logline._checkProtocol(); - Logline._protocol.all(logs => readyFn(logs)); + + switch (arguments.length) { + case 1: + readyFn = from; + from = undefined; + break; + case 2: + readyFn = to; + to = undefined; + break; + case 3: + default: + break; + } + + Logline._protocol.get(from, to, readyFn); + } + + /** + * read all logs + * @method all + * @static + * @param {Function} readyFn - function to call back with logs as parameter + */ + static all(readyFn) { + Logline.get(readyFn); } - // 清理日志 + /** + * clean up logs = keep limited logs + * @method keep + * @static + * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3' + * @return {Object} Logline + */ static keep(daysToMaintain) { Logline._checkProtocol(); Logline._protocol.keep(daysToMaintain); return this; } - // 清空日志并删除数据库 + /** + * delete log database + * @method clean + * @static + * @return {Object} Logline + */ static clean() { Logline._checkProtocol(); Logline._protocol.clean(); return this; } - // 选择一个日志协议 + /** + * choose a protocol + * @method using + * @static + * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL + * @param {String} [database] - custome database name + * @return {Object} Logline + */ static using(protocol, database) { // protocol unavailable is not allowed - if (-1 === [IndexeddbLogger, WebsqlLogger, LocalstorageLogger].indexOf(protocol)) { + if (-1 === [IndexeddbLogger, LocalstorageLogger, WebsqlLogger].indexOf(protocol)) { util.throwError('specialfied protocol ' + (protocol ? (protocol + ' ') : '') + 'is not available'); } @@ -68,15 +141,25 @@ class Logline { return this; } + /** + * specialfy a custome database name, in case of any conflicts + * @methd database + * @static + * @param {String} name - target database name + */ static database(name) { Logline._database = name; } } +// export protocols for modification and mounting Logline.PROTOCOL = { INDEXEDDB: IndexeddbLogger, - WEBSQL: WebsqlLogger, - LOCALSTORAGE: LocalstorageLogger + LOCALSTORAGE: LocalstorageLogger, + WEBSQL: WebsqlLogger }; -module.exports = Logline; +// export protocol interface for user custom implements +Logline.INTERFACE = Object.freeze(Interface); + +export default Logline; diff --git a/src/protocols/indexeddb.js b/src/protocols/indexeddb.js index 2293965..e8b3737 100644 --- a/src/protocols/indexeddb.js +++ b/src/protocols/indexeddb.js @@ -3,26 +3,26 @@ import Pool from '../lib/pool'; import * as util from '../lib/util'; /** - * indexedDB日志协议 + * IndexedDB protocol * @class IndexedDBLogger */ export default class IndexedDBLogger extends LoggerInterface { /** - * 构造函数 + * IndexedDB protocol constructor * @constructor - * @param {String} namespace - 日志的命名空间 + * @param {String} namespace - namespace to use */ constructor(...args) { super(...args); } /** - * 添加一条日志记录 + * add a log record * @method _reocrd * @private - * @parma {String} level - 日志等级 - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @parma {String} level - log level + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ _record(level, descriptor, data) { if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITED) { @@ -53,10 +53,10 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 初始化协议 + * initialize protocol * @method init * @static - * @param {String} database - 初始化时要使用的数据库名 + * @param {String} database - database name to use */ static init(database) { if (!IndexedDBLogger.support) { @@ -91,27 +91,36 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 读取所有日志内容 - * @method all + * get logs in range + * if from and end is not defined, will fetch full log + * @method get * @static - * @param {Function} readyFn - 用于读取日志内容的回调函数 + * @param {String} from - time from, unix time stamp or falsy + * @param {String} to - time end, unix time stamp or falsy + * @param {Function} readyFn - function to call back with logs as parameter */ - static all(readyFn) { + static get(from, to, readyFn) { if (IndexedDBLogger.status !== super.STATUS.INITED) { IndexedDBLogger._pool.push(() => { - IndexedDBLogger.all(readyFn); + IndexedDBLogger.get(from, to, readyFn); }); return; } + from = LoggerInterface.transTimeFormat(from); + to = LoggerInterface.transTimeFormat(to); + let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY || 'readonly'), request = store.openCursor(), logs = []; request.onsuccess = event => { var cursor = event.target.result; - console && console.log(cursor); if (cursor) { + if ((from && cursor.value.time < from) || (to && cursor.value.time > to)) { + cursor.continue(); + } + logs.push({ time: cursor.value.time, namespace: cursor.value.namespace, @@ -129,10 +138,10 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 清理日志 + * clean logs = keep limited logs * @method keep * @static - * @param {Number} daysToMaintain - 保留多少天数的日志 + * @param {Number} daysToMaintain - keep logs within days */ static keep(daysToMaintain) { if (IndexedDBLogger.status !== super.STATUS.INITED) { @@ -161,7 +170,7 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 删除日志数据库 + * delete log database * @method clean * @static */ @@ -185,12 +194,12 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 获取事务存储过程 + * get internal transaction store * @method _getTransactionStore * @private * @static - * @param {String} mode - 事务过程的参数 - * @return {Object} 实物存储过程 + * @param {String} mode - transaction mode + * @return {Object} - internal object store */ static _getTransactionStore(mode) { if (IndexedDBLogger.db) { @@ -204,7 +213,7 @@ export default class IndexedDBLogger extends LoggerInterface { } /** - * 是否支持indexedDB + * detect support situation * @prop {Boolean} support */ static get support() { diff --git a/src/protocols/interface.js b/src/protocols/interface.js index 7ae594c..b03bfb8 100644 --- a/src/protocols/interface.js +++ b/src/protocols/interface.js @@ -1,119 +1,145 @@ import * as util from '../lib/util'; /** - * 日志协议原型类 + * Logline Interface * @class Interface */ export default class Interface { /** - * 构造函数 + * Logline constructor * @constructor - * @param {String} namespace - 日志的命名空间 + * @param {String} namespace - namespace to use */ constructor(namespace) { this._namesapce = namespace; } /** - * 添加一条日志记录 + * add a log record * @method _reocrd * @private - * @parma {String} level - 日志等级 - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @parma {String} level - log level + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ _record(level, descriptor, data) { util.throwError('method _record is not implemented.'); } /** - * 添加一条等级为info的日志记录 + * add a level-info record * @method info - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ info(...args) { this._record('info', ...args); } /** - * 添加一条等级为warn的日志记录 + * add a level-warn record * @method warn - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ warn(...args) { this._record('warn', ...args); } /** - * 添加一条等级为error的日志记录 + * add a level-error record * @method error - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ error(...args) { this._record('error', ...args); } /** - * 添加一条等级为critical的日志记录 + * add a level-critical record * @method critical - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ critical(...args) { this._record('critical', ...args); } /** - * 初始化协议 + * initialize protocol * @method init * @static - * @param {String} database - 初始化时要使用的数据库名 + * @param {String} database - database name to use */ static init(database) { return true; } /** - * 读取所有日志内容 - * @method all + * transform human readable time string, such as '3d', '.3' and '1.2' into Unix timestamp + * the default relative time is Date.now(), if no second parameter is provided + * @method transTimeFormat * @static - * @param {Function} readyFn - 用于读取日志内容的回调函数 + * @param {String} time - time string to transform + * @param {Number} [relative] - relative time to compare, default Date.now() + * @return {Number|NaN} timestamp transformed */ - static all(readyFn) { - readyFn([]); + static transTimeFormat(time, relative) { + // if falsy value or timestamp already, pass it through directly, + if (!time || /^\d{13}$/.test(time)) { + return +time; + } + // incase relative time isn't unix timestamp format, + // neither a falsy value which will turned out to be Date.now() + if (relative && !/^\d{13}$/.test(relative)) { + throw new TypeError('relative time should be standard unix timestamp'); + } + + return (relative || Date.now()) - time.replace(/d$/, '') * 24 * 3600 * 1000; + } + + /** + * get logs in range + * if from and end is not defined, will fetch full log + * @method get + * @static + * @param {String} from - time from, unix timestamp + * @param {String} to - time end, unix timestamp + * @param {Function} readyFn - function to call back with logs as parameter + */ + static get(from, to, readyFn) { + util.throwError('method get is not implemented.'); } /** - * 清理日志 + * clean logs = keep limited logs * @method keep * @static - * @param {Number} daysToMaintain - 保留多少天数的日志 + * @param {Number} daysToMaintain - keep logs within days */ static keep(daysToMaintain) { - return true; + util.throwError('method keep is not implemented.'); } /** - * 删除日志数据库 + * delete log database * @method clean * @static */ static clean() { - return true; + util.throwError('method clean is not implemented.'); } /** - * 协议状态MAP + * protocol status map * @prop {Object} STATUS */ static get STATUS() { return { - INITING: 1, // 初始化中 - INITED: 2, // 初始化成功 - FAILED: 4 // 初始化失败 + INITING: 1, + INITED: 2, + FAILED: 4 }; } } diff --git a/src/protocols/localstorage.js b/src/protocols/localstorage.js index 30a93b5..7b71990 100644 --- a/src/protocols/localstorage.js +++ b/src/protocols/localstorage.js @@ -2,26 +2,26 @@ import LoggerInterface from './interface'; import * as util from '../lib/util'; /** - * localStorage日志协议 + * Localstorage protocol * @class LocalStorageLogger */ export default class LocalStorageLogger extends LoggerInterface { /** - * 构造函数 + * Localstorage protocol constructor * @constructor - * @param {String} namespace - 日志的命名空间 + * @param {String} namespace - namespace to use */ constructor(...args) { super(...args); } /** - * 添加一条日志记录 + * add a log record * @method _reocrd * @private - * @parma {String} level - 日志等级 - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @parma {String} level - log level + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ _record(level, descriptor, data) { var logs = window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []; @@ -38,10 +38,10 @@ export default class LocalStorageLogger extends LoggerInterface { } /** - * 初始化协议 + * initialize protocol * @method init * @static - * @param {String} database - 初始化时要使用的数据库名 + * @param {String} database - database name to use */ static init(database) { if (!LocalStorageLogger.support) { @@ -55,14 +55,25 @@ export default class LocalStorageLogger extends LoggerInterface { } /** - * 读取所有日志内容 - * @method all + * get logs in range + * if from and end is not defined, will fetch full log + * @method get * @static - * @param {Function} readyFn - 用于读取日志内容的回调函数 + * @param {String} from - time from, unix time stamp or falsy + * @param {String} to - time end, unix time stamp or falsy + * @param {Function} readyFn - function to call back with logs as parameter */ - static all(readyFn) { + static get(from, to, readyFn) { var logs = JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)), i; + + from = LoggerInterface.transTimeFormat(from); + to = LoggerInterface.transTimeFormat(to); + for (i = 0; i < logs.length; i++) { + if ((from && logs[i][0] < from) || (to && logs[i][0] > to)) { + continue; + } + logs[i] = { time: logs[i][0], namespace: logs[i][1], @@ -75,10 +86,10 @@ export default class LocalStorageLogger extends LoggerInterface { } /** - * 清理日志 + * clean logs = keep limited logs * @method keep * @static - * @param {Number} daysToMaintain - 保留多少天数的日志 + * @param {Number} daysToMaintain - keep logs within days */ static keep(daysToMaintain) { var logs = !daysToMaintain ? [] : (window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []).filter(log => { @@ -88,7 +99,7 @@ export default class LocalStorageLogger extends LoggerInterface { } /** - * 删除日志数据库 + * delete log database * @method clean * @static */ @@ -98,7 +109,7 @@ export default class LocalStorageLogger extends LoggerInterface { } /** - * 是否支持localStorage + * detect support situation * @prop {Boolean} support */ static get support() { diff --git a/src/protocols/websql.js b/src/protocols/websql.js index f86dfb0..9a62f0e 100644 --- a/src/protocols/websql.js +++ b/src/protocols/websql.js @@ -3,26 +3,26 @@ import Pool from '../lib/pool'; import * as util from '../lib/util'; /** - * websql日志协议 + * Websql protocol * @class WebsqlLogger */ export default class WebsqlLogger extends LoggerInterface { /** - * 构造函数 + * Websql logline constructor * @constructor - * @param {String} namespace - 日志的命名空间 + * @param {String} namespace - namespace to use */ constructor(...args) { super(...args); } /** - * 添加一条日志记录 + * add a log record * @method _reocrd * @private - * @parma {String} level - 日志等级 - * @param {String} descriptor - 描述符,用于快速理解和全局搜索 - * @param {Mixed} data - 要记录的附加数据 + * @parma {String} level - log level + * @param {String} descriptor - to speed up search and improve understanding + * @param {Mixed} [data] - additional data */ _record(level, descriptor, data) { if (WebsqlLogger.status !== LoggerInterface.STATUS.INITED) { @@ -48,10 +48,10 @@ export default class WebsqlLogger extends LoggerInterface { } /** - * 初始化协议 + * initialize protocol * @method init * @static - * @param {String} database - 初始化时要使用的数据库名 + * @param {String} database - database name to use */ static init(database) { if (!WebsqlLogger.support) { @@ -84,30 +84,43 @@ export default class WebsqlLogger extends LoggerInterface { } /** - * 读取所有日志内容 - * @method all + * get logs in range + * if from and end is not defined, will fetch full log + * @method get * @static - * @param {Function} readyFn - 用于读取日志内容的回调函数 + * @param {String} from - time from, unix time stamp or falsy + * @param {String} to - time end, unix time stamp or falsy + * @param {Function} readyFn - function to call back with logs as parameter */ - static all(readyFn) { + static get(from, to, readyFn) { if (WebsqlLogger.status !== super.STATUS.INITED) { WebsqlLogger._pool.push(() => { - WebsqlLogger.all(readyFn); + WebsqlLogger.get(from, to, readyFn); }); return; } + from = LoggerInterface.transTimeFormat(from); + to = LoggerInterface.transTimeFormat(to); + try { WebsqlLogger._db.transaction(function(tx) { tx.executeSql( 'SELECT * FROM logs ORDER BY time DESC', [], (tx, res) => { - var logs = [], line, index = res.rows.length; + var logs = [], line, index = res.rows.length, item; while (--index >= 0) { + item = res.rows.item(index); + if ((from && item.time < from) || (to && item.time > to)) { + continue; + } + // in some devices, properties are configureable: false, writable: false // we need deep copy - line = JSON.parse(JSON.stringify(res.rows.item(index))); - line.data = JSON.parse(line.data); + line = JSON.parse(JSON.stringify(item)); + // incase data is an object, not a string + try { line.data = JSON.parse(line.data); } + catch (e) {/* leave line.data as it be */} logs.push(line); } readyFn(logs); @@ -119,10 +132,10 @@ export default class WebsqlLogger extends LoggerInterface { } /** - * 清理日志 + * clean logs = keep limited logs * @method keep * @static - * @param {Number} daysToMaintain - 保留多少天数的日志 + * @param {Number} daysToMaintain - keep logs within days */ static keep(daysToMaintain) { if (WebsqlLogger.status !== super.STATUS.INITED) { @@ -154,7 +167,7 @@ export default class WebsqlLogger extends LoggerInterface { } /** - * 删除日志数据库 + * delete log database * @method clean * @static */ @@ -180,7 +193,7 @@ export default class WebsqlLogger extends LoggerInterface { } /** - * 是否支持websql + * detect support situation * @prop {Boolean} support */ static get support() { diff --git a/test/tests.js b/test/tests.js index 2bcd777..ab54cd6 100644 --- a/test/tests.js +++ b/test/tests.js @@ -37,10 +37,21 @@ describe('Logline', function() { assert.isFunction(window.Logline.using, 'static interface using'); assert.isFunction(window.Logline.keep, 'static interface keep'); assert.isFunction(window.Logline.clean, 'static interface clean'); - assert.isFunction(window.Logline.getAll, 'static interface getAll'); + assert.isFunction(window.Logline.get, 'static interface get'); done(); }); + it('should correctly transform human readable time string', function() { + var now = Date.now(); + assert.equal(window.Logline.INTERFACE.transTimeFormat('.3d', now), now - 1000 * 3600 * 24 * .3, 'humand readable string .3d is not correctly transformed'); + assert.equal(window.Logline.INTERFACE.transTimeFormat(now), now, 'unix timestamp is not correctly passed through'); + assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(undefined), 'falsy value `undefined` is not correctly passed through'); + assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(null), 'falsy value `null` is not correctly passed through'); + assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(NaN), 'falsy value `NaN` is not correctly passed through'); + assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(false), 'falsy value `false` is not correctly passed through'); + assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(''), 'falsy value `\'\'` is not correctly pass through'); + }); + it('should be able to specialfy any available protocols', function(done) { for (var i = 0; i < Object.keys(window.Logline.PROTOCOL).length; i++) { window.Logline.using(window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]]); @@ -51,7 +62,7 @@ describe('Logline', function() { done(); }); - it('should be able to create any available protocols instant', function(done) { + it('should be able to create any available protocols instance', function(done) { isReady(function() { for (var i = 0; i < Object.keys(window.Logline.PROTOCOL).length; i++) { window.Logline.using(window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]]); @@ -92,7 +103,7 @@ if (false && window.Logline.PROTOCOL.INDEXEDDB && window.Logline.PROTOCOL.INDEXE // logger.info('error', randomVars[2]); // logger.info('critical', randomVars[3]); - window.Logline.getAll(function(logs) { + window.Logline.all(function(logs) { assert.isArray(logs, 'logs collect from database'); assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); // assert.equal(logs[1].data, randomVars[1], 'record get from database is not the one we stored'); @@ -141,7 +152,7 @@ if (window.Logline.PROTOCOL.WEBSQL && window.Logline.PROTOCOL.WEBSQL.support) { var WebsqlLogger = window.Logline._protocol; assert.isFunction(WebsqlLogger.init, 'static interface init'); - assert.isFunction(WebsqlLogger.all, 'static interface all'); + assert.isFunction(WebsqlLogger.get, 'static interface get'); assert.isFunction(WebsqlLogger.keep, 'static interface keep'); assert.isFunction(WebsqlLogger.clean, 'static interface keep'); done(); @@ -159,8 +170,9 @@ if (window.Logline.PROTOCOL.WEBSQL && window.Logline.PROTOCOL.WEBSQL.support) { logger.info('error', randomVars[2]); logger.info('critical', randomVars[3]); - window.Logline._protocol.all(function(logs) { + window.Logline.all(function(logs) { assert.isArray(logs, 'logs collect from database'); + assert.isOk(logs.length === 4, 'logs collected is not equal to we acctually stored, which is 4, but get ' + logs.length); assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); assert.equal(logs[1].data, randomVars[1], 'record get from database is not the one we stored'); assert.equal(logs[2].data, randomVars[2], 'record get from database is not the one we stored'); @@ -221,7 +233,7 @@ if (window.Logline.PROTOCOL.LOCALSTORAGE && window.Logline.PROTOCOL.LOCALSTORAGE var LocalStorageLogger = window.Logline._protocol; assert.isFunction(LocalStorageLogger.init, 'method `init` should be a function'); - assert.isFunction(LocalStorageLogger.all, 'method `all` should be a function'); + assert.isFunction(LocalStorageLogger.get, 'method `get` should be a function'); assert.isFunction(LocalStorageLogger.keep, 'method `keep` should be a function'); assert.isFunction(LocalStorageLogger.clean, 'method `clean` should be a function'); done(); @@ -239,7 +251,7 @@ if (window.Logline.PROTOCOL.LOCALSTORAGE && window.Logline.PROTOCOL.LOCALSTORAGE logger.info('error', randomVars[2]); logger.info('critical', randomVars[3]); - window.Logline.getAll(function(logs) { + window.Logline.all(function(logs) { assert.isArray(logs, 'logs collect from database should be an array'); assert.equal(logs.length, 4, 'record length should be 4, currently ' + logs.length); assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index ff4f4b0..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,36 +0,0 @@ -var pkg = require('./package.json'); -var webpack = require('webpack'); - -module.exports = { - devtool: false, - entry: { - logline: './src/logline.js' - }, - output: { - path: './dist', - filename: '[name].js', - library: 'Logline', - libraryTarget: 'umd', - umdNameDefine: true - }, - module: { - loaders: [ - { - test: /\.js$/, - loader: 'babel' - }, - { - test: /\.json$/, - loader: 'json' - } - ] - }, - plugins: [ - new webpack.BannerPlugin([ - pkg.name + ' v' + pkg.version + ' (' + pkg.homepage + ')', - 'Copyright ' + new Date().getFullYear() + ', ' + pkg.author, - pkg.license + ' license' - ].join('\n')) - ] - -}; diff --git a/webpack.config.prod.js b/webpack.config.prod.js deleted file mode 100644 index e818c8d..0000000 --- a/webpack.config.prod.js +++ /dev/null @@ -1,48 +0,0 @@ -var pkg = require('./package.json'); -var webpack = require('webpack'); - -module.exports = { - devtool: 'cheap-module-source-map', - entry: { - logline: './src/logline.js' - }, - output: { - path: './dist', - filename: '[name].min.js', - library: 'Logline', - libraryTarget: 'umd', - umdNameDefine: true, - sourceMapFilename: '[name].min.map' - }, - module: { - loaders: [ - { - test: /\.js$/, - loader: 'babel' - }, - { - test: /\.json$/, - loader: 'json' - } - ] - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify('production') - } - }), - new webpack.BannerPlugin([ - pkg.name + ' v' + pkg.version + ' (' + pkg.homepage + ')', - 'Copyright ' + new Date().getFullYear() + ', ' + pkg.author, - pkg.license + ' license' - ].join('\n')), - new webpack.optimize.UglifyJsPlugin({ - minimize: true, - compress: { - warnings: false - } - }) - ] - -};