├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG ├── Jenkinsfile ├── LICENSE ├── README.en_US.md ├── README.md ├── TODO ├── bower.json ├── build ├── rollup.build.js └── rollup.dev.js ├── dist ├── dependencies.txt ├── logline.js ├── logline.min.js └── logline.min.js.map ├── example ├── index.html ├── indexeddb.html ├── localstorage.html ├── main.js ├── requirejs.html ├── style.css └── websql.html ├── gulpfile.js ├── index.html ├── makefile ├── package-lock.json ├── package.json ├── src ├── BANNER ├── configure ├── lib │ ├── config.js │ ├── pool.js │ └── util.js ├── logline.js └── protocols │ ├── indexeddb.js │ ├── interface.js │ ├── localstorage.js │ └── websql.js └── test ├── index.html ├── mocha.css └── tests.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-object-assign" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig 超级棒: http://EditorConfig.org 2 | # 最顶层的配置,不会再继续往上查找配置 3 | root = true 4 | 5 | # 匹配所有文件 6 | [*] 7 | # 使用Unix风格的换行控制 8 | end_of_line = lf 9 | # 在每个文件结尾添加Unix风格的空行 10 | insert_final_newline = true 11 | # 去除行尾多余的空格 12 | trim_trailing_whitespace = true 13 | # 设定文件的编码为UTF-8 14 | charset = utf-8 15 | # 缩进风格为空格 16 | indent_style = space 17 | # 缩进4个空格 18 | indent_size = 4 19 | 20 | # 匹配JSON文件 21 | [*.json] 22 | # 缩进2个空格 23 | indent_size = 2 24 | 25 | [makefile] 26 | indent_style = tab 27 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "amd": true, 9 | "node": true, 10 | "jquery": true, 11 | "mocha": true 12 | }, 13 | "globals": { 14 | "seajs": true, 15 | "G_SPEED_TTL": true, 16 | "iScroll": true, 17 | "Promise": true, 18 | "Logline": true 19 | }, 20 | "rules": { 21 | "strict": [1, "function"], // 要求严格模式 22 | 23 | //警告 24 | "quotes": [1, "single"], // 建议使用单引号 25 | "quote-props": 0, // 对象字面量属性名的风格 26 | "no-inner-declarations": [1, "both"], // 不建议在{}代码块内部声明变量或函数 27 | "no-case-declarations": 1, // 禁止在case段落中定义块级作用域变量 28 | "no-extra-semi": 2, // 多余的分号 29 | "no-extra-parens": 0, // 多余的括号,允许因为 no-cond-assign='except-parens' 30 | "no-empty": [1, {"allowEmptyCatch": true}], // 空代码块 31 | "no-use-before-define": [0, {"functions": false, "classes": true}], // 使用前未定义 32 | //"complexity": [1, 10], // 圈复杂度大于10 警告 33 | "no-console": 0, // 不允许有console 34 | "no-debugger": 2, // debugger 调试代码未删除 35 | "no-dupe-args": 2, // 参数重复 36 | 37 | //常见错误 38 | "semi": 2, // 要求分号 39 | "no-unexpected-multiline": 2, // 行尾缺少分号可能导致一些意外情况 40 | "comma-dangle": [2, "never"], // 定义数组或对象最后多余的逗号 41 | "no-cond-assign": [2, "except-parens"], // 条件语句中禁止赋值操作,除非使用括号包裹 42 | "no-alert": 1, // 不允许有alert 43 | "no-caller": 0, // 不允许使用arguments.caller和arguments.callee,影响脚本解析器的性能优化 44 | "no-constant-condition": 2, // 常量作为条件 45 | "no-dupe-keys": 2, // 对象属性重复 46 | "no-duplicate-case": 2, // case重复 47 | "no-empty-character-class": 2, // 正则无法匹配任何值 48 | "no-extra-boolean-cast": 2, // 多余的感叹号转布尔型 49 | //"no-extra-semi": 2, // 多余的分号 50 | "no-invalid-regexp": 2, // 无效的正则 51 | "no-func-assign": 2, // 函数被赋值 52 | "valid-typeof": 2, // 无效的类型判断 53 | "no-unreachable": 1, // 不可能执行到的代码 54 | "no-sparse-arrays": 2, // 数组中多出逗号 55 | "no-shadow-restricted-names": 2, // 关键词与命名冲突 56 | "no-undef": 2, // 不允许使用未定义的变量 57 | "no-unused-vars": 1, // 变量定义后未使用 58 | "no-native-reassign": 2, // 禁止覆盖原生对象 59 | "no-obj-calls": 2, // 禁止将Math和JSON作为函数调用 60 | "no-regex-spaces": 2, // 禁止正则表达式中出现多个空格 61 | "no-irregular-whitespace": 2, // 禁止异常的空白符出现在字符串和正则之外 62 | "no-negated-in-lhs": 2, // 禁止有歧义的in运算, if (!x in obj) {..} 63 | "no-unsafe-finally": 2, // 禁止在finally段落中出现流程控制语句 64 | "use-isnan": 2, // 要求使用isNaN()来判断NaN 65 | 66 | //代码风格优化 67 | "valid-jsdoc": 0, // 强制使用符合JSDoc规范的注释 68 | "semi-spacing": [1, {"before": false, "after": true}], // 要求分号后有空格 69 | "no-multi-spaces": 1, // 不允许多个空格 70 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}], // object直接量建议写法 : 后一个空格签名留空格 71 | "comma-spacing": [1, {"before": false, "after": true}], // 逗号后面跟空格 72 | "eqeqeq": 1, // 用全等比较和全等不比较最佳实践 73 | "guard-for-in": 1, // 在for-in循环中使用hasOwnProperty检查是最佳实践 74 | "no-empty-function": 1, // 不允许空函数体的函数 75 | "no-eq-null": 2, // 使用严格比较来判断是否===null,!==null 76 | "no-eval": 2, // eval是邪恶的 77 | "no-implied-eval": 2, // 禁用eval类似的用法 78 | "no-extra-bind": 1, // 不允许不必要的bind调用 79 | "no-fallthrough": 1, // 禁止case表达式的穿透(缺少break) 80 | "no-iterator": 2, // 禁止使用__iterator__属性 81 | "no-proto": 2, // 不适用__proto__属性 82 | "no-loop-func": 2, // 禁止在循环体中定义函数 83 | "no-new-func": 1, // 禁止new Function(...) 写法 84 | "no-lone-blocks": 1, // 禁止不必要的嵌套块级作用域 85 | "no-labels": 1, // 无用的标记(如switch和for) 86 | "block-scoped-var": 1, // 变量定义后未使用 87 | "dot-location": [2, "property"], // 换行调用对象方法 点操作符应写在行首 88 | "dot-notation": 1, // 优先使用.来调用对象的属性而不是中括号 89 | "no-extend-native": 0, // 禁止扩展原生对象 90 | "no-floating-decimal": 0, // 浮点型需要写全 禁止.1 或 2.写法 91 | "no-self-compare": 2, // 不允与自己比较作为条件 92 | "no-sequences": 2, // 禁止可能导致结果不明确的逗号操作符 93 | "no-throw-literal": 0, // 禁止抛出一个直接量 应是Error对象 94 | "no-return-assign": [0, "always"], // 不允return时有赋值操作 95 | "no-redeclare": [2, {"builtinGlobals": true}], // 不允许重复声明 96 | "no-unused-expressions": [1, {"allowShortCircuit": true, "allowTernary": true}], // 未使用的表达式 97 | "no-useless-call": 1, // 无意义的函数call或apply 98 | "no-useless-concat": 1, // 无意义的string concat 99 | "no-useless-escape": 1, // 禁用无意义的转义 100 | "no-void": 2, // 禁用void 101 | "no-with": 1, // 禁用with 102 | "radix": 2, // 使用parseInt时带上第二个进制参数 103 | "no-warning-comments": [2, { "terms": ["fixme", "fix me"], "location": "anywhere" }], // 标记未写注释 104 | "spaced-comment": 1, // 强制要求注释符//和/*后加空格 105 | "default-case": 1, // 要求在switch语句中包含default case 106 | "space-unary-ops": 1, // 一元运算符后要求空格,nonword类型的除外 107 | "space-infix-ops": 1, // 运算符左右要求空格 a + b 108 | "newline-before-return": 0, // return语句前要求有空行 109 | "newline-after-var": 0, // var定义后要求留空行 110 | "padded-blocks": 0, // 块级作用域内部首位包含空行 111 | "space-before-blocks": 1, // 块级作用域前要求有空格function() { 112 | "space-before-function-paren": [1, "never"], // function名称和括号之间不要有空格 113 | "require-jsdoc": [0, { "require": {"FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true }}], // 要求注释 114 | "operator-linebreak": [0, "after"], // 换行运算符的位置 115 | "operator-assignment": [0, "never"], // 是否允许使用 += %= 这样的形式 116 | "no-trailing-spaces": [1, { "skipBlankLines": true }], // 不允许行尾的空格,除非该行完全由空格组成 117 | "no-new-object": 2, // 不允许使用Object构造器 118 | "no-array-constructor": 2, // 不允许使用Array构造器 119 | "no-whitespace-before-property": 1, // 不允许属性访问前的空格 a. b 120 | "no-unneeded-ternary": 1, // 过滤不必要的三元运算,var isYes = answer === 1 ? true : false; 121 | "no-underscore-dangle": [0, {"allowAfterThis": true}], // 不允许在变量名上悬挂下划线 122 | "no-spaced-func": 1, // 调用时, 函数名和括号之间不要有空格 123 | "no-plusplus": 0, // 不允许使用自增自减 ++ -- 运算符 124 | "no-nested-ternary": 1, // 不允许嵌套的三元运算 125 | "no-negated-condition": 0, // 不允许反向的条件判断 126 | "no-mixed-spaces-and-tabs": 2, // 不要同时存在tab和空格缩进 127 | "no-inline-comments": 0, // 不允许在同行注释 128 | "newline-per-chained-call": [1, {"ignoreChainWithDepth": 4}], // 链式调用要求新起一行 129 | "new-parens": 2, // new一个构造器时要求括号 130 | "new-cap": 2, // 构造函数要求第一个字母大写 131 | "max-statements-per-line": [2, {"max": 3}], // 不要在同一行写3个及以上的语句 132 | "block-spacing": 0, // 单行块级作用域内前后是否加空格 133 | "linebreak-style": 1, // 统一的换行符 LF 134 | "camelcase": 1, // 使用驼峰命名约定 135 | "keyword-spacing": 1, // 关键字前后要求空格如 } else { 136 | "indent": [1, 4], // 要求缩进 137 | "eol-last": 1, // 要求文件以空行结尾 138 | "consistent-this": [1, "self"], // 用什么变量名带替代this指针 139 | "brace-style": [0, "stroustrup"], // 代码结构风格 [1tbs, stroustrup, allman] 140 | "no-undef-init": 1, // 不需要初始化变量为undefined 141 | "curly": 1 // if、else、while、for代码块用{}包围 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '35 14 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node 2 | node_modules 3 | npm-debug.log 4 | 5 | # bower 6 | bower_components 7 | 8 | # editors 9 | .vscode 10 | .idea 11 | *.swp 12 | *.swo 13 | 14 | # system 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | sudo: false 5 | branches: 6 | only: 7 | - master 8 | install: 9 | - npm install 10 | script: 11 | - echo 1 12 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v1.0.5 2 | A output logs in developer tool's console 3 | F IndexedDB: unable to remove special targeted logs 4 | 5 | v1.0.4 6 | U use rollup.js bundler to reduce dist package size(33% off) 7 | A add makefile, use make for automatic procedures 8 | U replace comments in English 9 | A add `get` method to get logs in suitable time range, rename `getAll` to `all` 10 | 11 | V1.0.3 12 | F add auto-increase as key of IndexedDB, to fix error when logging in high frequency(old keypath timestamp may repeat) 13 | U updated localStorage protocol's storage structer to save spaces 14 | 15 | V1.0.2 16 | A ability to choose protocol automatically 17 | A custom database name 18 | 19 | V1.0.1 20 | D log upload api `reportTo` and `deploy` is removed, 21 | 22 | V1.0.0 23 | A log record 24 | A log upload 25 | A custom build, allow build with only protocols wanted 26 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Checkout') { 5 | steps { 6 | git(url: 'https://github.com/latel/logline', branch: 'master', changelog: true) 7 | } 8 | } 9 | stage('Build') { 10 | steps { 11 | sh 'npm run build' 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 cinwell.li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.en_US.md: -------------------------------------------------------------------------------- 1 | Logline 2 | ======= 3 | 4 | [中文](https://github.com/latel/logline/blob/master/README.md) | English 5 | 6 | [![Build Status][travis-image]][travis-url] 7 | 8 | Logline is a light-weighted, useful log agent for front-end on the client-side. 9 | 10 | Why position problems is difficult for the front-end? 11 | --------------------------------------------------- 12 | Most front-enders should have similar experience, when codes are deployed for production, running on countless clients. In most caes, we can only guess the problems, especially some occasional visible problems, because we have no idea what's our user's acctual operations, thus it's hard for us to reproduce the scenes. At this moment, we think, it will be great help if we have a detailed and classified log agent, just like the backend do. 13 | 14 | Application scenario 15 | ------------------- 16 | + Reproduce user operations 17 | 18 | In the production environment, user's operations are un-predicable, even they 19 | cannot remember the details themselves, with log, we have the ability to 20 | reproduce the operation paths and running state. 21 | 22 | + Monitoring core processes 23 | 24 | In the core processes in our products, we can upload the logs positively, 25 | as we can focus our user's problems and count the amount quickly. 26 | 27 | + Positively get user's logs and analysis user's activities 28 | 29 | Don't believe on the users to coordinate you, we can design a strategy, such as 30 | deploy an json file online, configure a list containing the target users we 31 | wanted, when our product is opened on their client, they will download this 32 | json file after a certain delay(to prevent affections on the core process's 33 | performance), if the user id is in the list, we will upload the logs positively. 34 | 35 | + Count errors and help to analysis 36 | 37 | We can make use of Logline to count js errors. With the error stack, we can speed 38 | up the analysis. 39 | 40 | Features 41 | ------- 42 | 43 | + No extra dependencies 44 | + client-side(reach when acctually needed, save bandwith and traffic) 45 | + multiple filter dimension(namespace, degree and keyword) 46 | + multiple stores(IndexDB, Websql, localStorage) 47 | + cleanable(in case take too much user space) 48 | 49 | Quick to get started 50 | ------------------- 51 | 52 | ### 1. Installation 53 | 54 | #### with Bower 55 | 56 | ``` shell 57 | bower install logline 58 | ``` 59 | 60 | #### Download archive 61 | access [https://github.com/latel/logline/releases](https://github.com/latel/logline/releases), selecte the version you wanted. 62 | 63 | ### 2. Import to your project 64 | Logline is an UMD ready module, choose to import it as your project needed. 65 | CMD is evil, which is not supported, wrapper it yourself if you need it indeed. 66 | 67 | ``` javascript 68 | // using 70 | 71 | // using AMD loader 72 | var Logline = require('./mod/logline.min'); 73 | ``` 74 | ### 3. Choose a log protocol 75 | Logline implements three protocols, all of them are mounted on the `Logline` object for special uses, together with better semantics. 76 | 77 | + `websql:` Logline.PROTOCOL.WEBSQL 78 | + `indexeddb:` Logline.PROTOCOL.INDEXEDDB 79 | + `localstorage:` Logline.PROTOCOL.LOCALSTORAGE 80 | 81 | you can use `using` method to specialfy a protocol. 82 | 83 | ``` javascript 84 | Logline.using(Logline.PROTOCOL.WEBSQL); 85 | ``` 86 | 87 | ***If you call Logline related APIs, without specialfy a protocol in advance***, Logline will choose a available protocol automatically, respect the priority according to the configuration parameters during the compile process. 88 | 89 | such as, your compile command is `npm run configure -- --with-indexeddb --with-websql --with-localstorage`, 90 | if protocol indexeddb is available, then indexeddb protocol with be chosen automatically, 91 | otherwise, if indexeddb protocol is not available and websql protocol is available, then websql protocol will be chosen, and so on. 92 | 93 | If none of the compiled protocols are available, an error will be thrown. 94 | 95 | #### 4. Record logs 96 | ``` javascript 97 | var spaLog = new Logline('spa'), 98 | sdkLog = new Logline('sdk'); 99 | 100 | // with description, without extra data 101 | spaLog.info('init.succeed'); 102 | 103 | // with description and extra data 104 | spaLog.error('init.failed', { 105 | retcode: 'EINIT', 106 | retmsg: 'invalid signature' 107 | }); 108 | 109 | // with description, without extra data 110 | sdkLog.warning('outdated'); 111 | 112 | // with description and extra data 113 | sdkLog.critical('system.vanish', { 114 | // debug infos here 115 | }); 116 | ``` 117 | 118 | ### 5. Read logs 119 | ``` javascript 120 | // collect all logs 121 | Logline.all(function(logs) { 122 | // process logs here 123 | }); 124 | 125 | // collet logs within .3 days 126 | Logline.get('.3d', function(logs) { 127 | // process logs here 128 | }); 129 | 130 | // collect logs from 3 days before, and earlier than 1 days ago 131 | Logline.get('3d', '1d', function(logs) { 132 | // process logs here 133 | }); 134 | ``` 135 | 136 | ### 6. Clean logs 137 | ``` javascript 138 | Logline.keep(.5); // keep logs within half a day, if `.5` is not provided, will clean up all logs 139 | Logline.clean(); // clean all logs and delete database 140 | ``` 141 | 142 | Custom database name 143 | ------------------- 144 | Because indexeddb, websql and localstorage are all domain shared storage, the default database name `logline` may have already been taken, you can specialfy a custom database name in two ways as follows: 145 | 146 | ``` javascript 147 | // special a second parameter when calling `using` API 148 | Logline.using(Logline.PROTOCOL.WEBSQL, 'newlogline'); 149 | 150 | // call `database` API 151 | Logline.database('newlogline'); 152 | ``` 153 | 154 | Custom Compile 155 | -------------- 156 | Logline implements `localstorage`, `websql` and `indexeddb` protocols, all of them are compiled by default, if you don't need all of them, you can use `npm run configure` and `npm run build` to compile your custom build with partial protocols packed. This helps to reduces the package size. 157 | 158 | ``` shell 159 | // pack all protocols with no parameters 160 | npm run configure 161 | // pack only wanted protocols, remove corresponding --with-xx 162 | npm run configure -- --with-localstorage --with-websql 163 | 164 | // re-compile 165 | npm run build 166 | // find the custom build in dist fold 167 | ls -l dist/ 168 | ``` 169 | 170 | FAQ 171 | --- 172 | 173 | ### How to upload logs 174 | since v1.0.1, log upload ability is removed, as the upload procedures varies upon different projects, 175 | and we do hope Logline to focus on log recording and maintenance. 176 | Anyway, you can still use `Logline.all` and `Logline.get` to get the logs, 177 | and implement your own upload procedure. 178 | 179 | 180 | 181 | [travis-image]: https://api.travis-ci.org/latel/logline.svg 182 | [travis-url]: https://travis-ci.org/latel/logline 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logline 2 | ======= 3 | 4 | ~本项目的实现和结构已经过时,不建议使用~ 5 | 6 | 中文 | [English](https://github.com/latel/logline/blob/master/README.en_US.md) 7 | 8 | [![Build Status][travis-image]][travis-url] 9 | 10 | logline是一个轻量,实用和客户端级的前端日志记录工具。 11 | 12 | 13 | 为何前端定位问题很困难 14 | ----------------- 15 | 前端同学对此肯定深有体会,代码发出去之后,犹如脱缰的野马,运行在万千的客户终端上,等到产品和后台反馈问题到我们这边,很多时候定位问题只能靠猜,尤其是一些偶发诱因,因为根本不知道用户是如何操作的,真实环境遇到的问题通常是很多随机因素叠加的形成的,因此很难回放用户的操作来还原现场找到原因。这个时候,我们想,如果有一个像后台一样详实的可分类和检索的运行日志,无疑将会提供巨大的帮助。 16 | 17 | 应用场景 18 | ------ 19 | 20 | + 回放用户细节操作 21 | 22 | 真实应用场景下,用户的行为可能是不可预料的,甚至用户自己也无法记得自己的操作,有了日志,我们有了回放用户操作和代码运行状态的能力。 23 | 24 | + 核心流程监控 25 | 26 | 在产品的一些核心流程中,我们可以在用户出错的情况下主动上传用户日志,以便我们可以快速统计和定位用户遇到的问题。 27 | 28 | + 主动抓取用户的日志分析用户行为 29 | 30 | 有时候在用户不配合开发人员的时候,我们可以设计一种策略,比如我们在线上发布一个json文件,里面配置一个希望主动抓取日志的用户列表,当我们的产品在用户手机上被打开后,延时下载(避免影响主流程性能)这个json,当匹配当前用户时,直接主动上报该用户的日志。 31 | 32 | + 统计和辅助分析JS错误 33 | 34 | 我们可以记录js的报错,包含调用队列一起记录,直接上传此错误日志或者在累计达到一个阈值的时候统一上传。 35 | 36 | 特性 37 | --- 38 | 39 | + 零外部依赖 40 | + 客户端存放(需要时再获取,节省移动带宽、流量和连接数) 41 | + 多维度过滤(命名空间、日志等级和关键词) 42 | + 多个存储方案(Websql、localStorage和IndexedDB) 43 | + 可清理(防止日志过多,占用上传带宽和占满用户允许的内存) 44 | 45 | 快速上手 46 | ------ 47 | 48 | ### 1. 安装 49 | 50 | #### 通过npm 51 | 52 | ``` shell 53 | npm install logline 54 | ``` 55 | 56 | #### 直接下载 57 | 访问 [https://github.com/latel/logline/releases](https://github.com/latel/logline/releases),选择需要的版本下载,引入自己的项目。 58 | 59 | 60 | ### 2. 引入脚本 61 | 62 | Logline 支持直接使用 script 标签引用,也支持 AMD 模块加载器。 63 | 64 | ``` javascript 65 | // Script标签引入方式 66 | 67 | // AMD模块方式(如requirejs) 68 | var Logline = require('./mod/logline.min'); 69 | // CMD引入方式(使用npm安装) 70 | var Logline = require('logline'); 71 | // ES6引入方式(使用npm安装) 72 | import Logline from 'logline'; 73 | ``` 74 | 75 | ### 3. 选择日志协议 76 | 77 | 目前一共支持三个协议, 三个协议都被直接挂载在Logline对象上以便一些特殊的应用场景,也更好的符合语义化: 78 | 79 | + websql: Logline.PROTOCOL.WEBSQL 80 | + indexeddb: Logline.PROTOCOL.INDEXEDDB 81 | + localstorage: Logline.PROTOCOL.LOCALSTORAGE 82 | 83 | 你可以在引入Logline之后,使用 `using` 主动选定一个期望使用的日志协议。 84 | 85 | ``` javascript 86 | Logline.using(Logline.PROTOCOL.WEBSQL); 87 | 88 | ``` 89 | 90 | ***如果你没有提前选择一个日志协议,那么当你调用Logline的相关 API 时***,Logline 会根据你在构建时给定的参数作为优先级来选择可用的优先级最高的协议。 91 | 比如你的自定义构建命令是`npm run configure -- --with-indexeddb --with-websql --with-localstorage`, 92 | 如果 indexeddb 协议可用,那么indexeddb将作为自动选择的协议。 93 | 如果 indexeddb 协议不可用但是 websql 协议可用,那么将选择 websql 协议,如此类推。 94 | 95 | 如果最后发现所有的协议都不可用,将会抛出错误。 96 | 97 | ### 4. 记录日志 98 | 99 | ``` javascript 100 | // 不同的模块使用不同的日志会话 101 | var spaLog = new Logline('spa'), 102 | sdkLog = new Logline('sdk'); 103 | 104 | // 不包含数据的,描述为 init.succeed 的记录 105 | spaLog.info('init.succeed'); 106 | 107 | // 包含错误描述数据,描述为 init.failed 的记录 108 | spaLog.error('init.failed', { 109 | retcode: 'EINIT', 110 | retmsg: 'invalid signature' 111 | }); 112 | 113 | // 不包含数据的,描述为 outdated 的记录 114 | sdkLog.warn('outdated'); 115 | 116 | // 包含错误描述数据,描述为 system.vanish 的记录 117 | sdkLog.critical('system.vanish', { 118 | // debug infos here 119 | }); 120 | ``` 121 | 122 | ### 5. 读取日志 123 | 124 | ``` javascript 125 | // collect all logs 126 | Logline.all(function(logs) { 127 | // process logs here 128 | }); 129 | 130 | // collet logs within .3 days 131 | Logline.get('.3d', function(logs) { 132 | // process logs here 133 | }); 134 | 135 | // collect logs from 3 days before, and earlier than 1 days ago 136 | Logline.get('3d', '1d', function(logs) { 137 | // process logs here 138 | }); 139 | ``` 140 | 141 | ### 6. 清理日志 142 | 143 | ``` javascript 144 | Logline.keep(.5); // 保留半天以内的日志,如果不传参则清空日志 145 | Logline.clean(); // 清空日志并删除数据库 146 | ``` 147 | 148 | 自定义数据库名 149 | --------- 150 | 由于 indexeddb, websql 和 localStorage 都是同域共享的,这时候 Logline 默认的数据库名 logline 可能会已经被占用,需要指定一个新的数据库名。 151 | 可以通过下面2个方法指定数据库名。 152 | 153 | ``` javascript 154 | // 调用`using`时,同时指定第二个参数作为数据库名 155 | Logline.using(Logline.PROTOCOL.WEBSQL, 'newlogline'); 156 | 157 | // 调用`database`来指定数据库名 158 | Logline.database('newlogline'); 159 | ``` 160 | 161 | 162 | 自定义构建 163 | -------- 164 | 目前Logline一共实现了`localstorage`、`websql`和`indexeddb`三个日志协议,默认是全部打包,可能你只想使用其中某个协议而已,你可以通过`npm run configure`和`npm run build`来自定义构建你需要的版本。这样有利于减小包的大小。 165 | 166 | ``` shell 167 | // 不跟参数默认构建所有协议 168 | npm run configure 169 | // 配置你需要的协议,去掉不需要的协议申明--with-xxx 170 | npm run configure -- --with-localstorage --with-websql 171 | 172 | // 重新打包 173 | npm run build 174 | // 去dist目录寻找新构建的打包文件 175 | ``` 176 | 177 | 178 | FAQ 179 | ---- 180 | 181 | ### 如何上传日志? 182 | 从v1.0.1以开始,日志上传功能被移除,我们希望logline更专注于日志的记录和维护工作, 183 | 你可以通过`Logline.all`和`Logline.get`来获取日志来自行实现上传过程。 184 | 185 | 186 | 187 | [travis-image]: https://api.travis-ci.org/latel/logline.svg 188 | [travis-url]: https://travis-ci.org/latel/logline 189 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | WIP 2 | [ ] IndexedDB: cursor.continue() meet `throw InvalidStateError when the cursor is being iterated` 3 | [ ] spread arguments support, such as log.info('xx', true, { data: 1 }, 'b'); 4 | 5 | CONSIDERING 6 | [ ] fullfill error handling 7 | [ ] 修复phantomjs无法正确测试indexeddb的问题,phantomjs在关闭数据后才能读取到新存储的内容 8 | [ ] add a tag, to indicate pending operations 9 | [ ] selecte random background color per namespace for the outpus on developer tool's console panel 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logline", 3 | "description": "logs for the frontend", 4 | "main": "dist/logline.min.js", 5 | "authors": [ 6 | "latel " 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "log", 11 | "frontend", 12 | "js", 13 | "html", 14 | "websql", 15 | "localstroage" 16 | ], 17 | "homepage": "https://github.com/latel/logline", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /build/rollup.build.js: -------------------------------------------------------------------------------- 1 | // Rollup plugins 2 | import * as path from 'path'; 3 | import babel from 'rollup-plugin-babel'; 4 | import uglify from 'rollup-plugin-uglify'; 5 | import license from 'rollup-plugin-license'; 6 | import * as jsonfile from 'jsonfile'; 7 | 8 | const pkg = jsonfile.readFileSync('./package.json'); 9 | 10 | export default { 11 | entry: 'src/' + pkg.name + '.js', 12 | dest: 'dist/' + pkg.name + (process.env.NODE_ENV === 'production' ? '.min' : '') + '.js', 13 | format: 'umd', 14 | moduleName: pkg.name.replace(/^\w/, starter => starter.toUpperCase()), 15 | sourceMap: process.env.NODE_ENV === 'production', 16 | plugins: [ 17 | babel({ 18 | exclude: 'node_modules/**', 19 | runtimeHelpers: true 20 | }), 21 | (process.env.NODE_ENV === 'production' && uglify()), 22 | license({ 23 | banner: { 24 | file: path.join(__dirname, 'src', 'BANNER') 25 | }, 26 | thirdParty: { 27 | output: path.join(__dirname, '../dist', 'dependencies.txt'), 28 | includePrivate: true 29 | } 30 | }) 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /build/rollup.dev.js: -------------------------------------------------------------------------------- 1 | // Rollup plugins 2 | import * as path from 'path'; 3 | import babel from 'rollup-plugin-babel'; 4 | import uglify from 'rollup-plugin-uglify'; 5 | import license from 'rollup-plugin-license'; 6 | import serve from 'rollup-plugin-serve'; 7 | import livereload from 'rollup-plugin-livereload'; 8 | import * as jsonfile from 'jsonfile'; 9 | 10 | const pkg = jsonfile.readFileSync('./package.json'); 11 | 12 | export default { 13 | entry: 'src/' + pkg.name + '.js', 14 | dest: 'dist/' + pkg.name + (process.env.NODE_ENV === 'production' ? '.min' : '') + '.js', 15 | format: 'umd', 16 | moduleName: pkg.name.replace(/^\w/, starter => starter.toUpperCase()), 17 | sourceMap: process.env.NODE_ENV === 'production', 18 | plugins: [ 19 | babel({ 20 | exclude: 'node_modules/**' 21 | }), 22 | (process.env.NODE_ENV === 'production' && uglify()), 23 | license({ 24 | banner: { 25 | file: path.join(__dirname, 'src', 'BANNER') 26 | }, 27 | thirdParty: { 28 | output: path.join(__dirname, '../dist', 'dependencies.txt'), 29 | includePrivate: true 30 | } 31 | }), 32 | serve({ 33 | contentBase: './', 34 | open: true 35 | }), 36 | livereload() 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /dist/dependencies.txt: -------------------------------------------------------------------------------- 1 | No third parties dependencies -------------------------------------------------------------------------------- /dist/logline.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.Logline = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 8 | return typeof obj; 9 | } : function (obj) { 10 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 11 | }; 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | var classCallCheck = function (instance, Constructor) { 24 | if (!(instance instanceof Constructor)) { 25 | throw new TypeError("Cannot call a class as a function"); 26 | } 27 | }; 28 | 29 | var createClass = function () { 30 | function defineProperties(target, props) { 31 | for (var i = 0; i < props.length; i++) { 32 | var descriptor = props[i]; 33 | descriptor.enumerable = descriptor.enumerable || false; 34 | descriptor.configurable = true; 35 | if ("value" in descriptor) descriptor.writable = true; 36 | Object.defineProperty(target, descriptor.key, descriptor); 37 | } 38 | } 39 | 40 | return function (Constructor, protoProps, staticProps) { 41 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 42 | if (staticProps) defineProperties(Constructor, staticProps); 43 | return Constructor; 44 | }; 45 | }(); 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | var _extends = Object.assign || function (target) { 54 | for (var i = 1; i < arguments.length; i++) { 55 | var source = arguments[i]; 56 | 57 | for (var key in source) { 58 | if (Object.prototype.hasOwnProperty.call(source, key)) { 59 | target[key] = source[key]; 60 | } 61 | } 62 | } 63 | 64 | return target; 65 | }; 66 | 67 | var get$1 = function get(object, property, receiver) { 68 | if (object === null) object = Function.prototype; 69 | var desc = Object.getOwnPropertyDescriptor(object, property); 70 | 71 | if (desc === undefined) { 72 | var parent = Object.getPrototypeOf(object); 73 | 74 | if (parent === null) { 75 | return undefined; 76 | } else { 77 | return get(parent, property, receiver); 78 | } 79 | } else if ("value" in desc) { 80 | return desc.value; 81 | } else { 82 | var getter = desc.get; 83 | 84 | if (getter === undefined) { 85 | return undefined; 86 | } 87 | 88 | return getter.call(receiver); 89 | } 90 | }; 91 | 92 | var inherits = function (subClass, superClass) { 93 | if (typeof superClass !== "function" && superClass !== null) { 94 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 95 | } 96 | 97 | subClass.prototype = Object.create(superClass && superClass.prototype, { 98 | constructor: { 99 | value: subClass, 100 | enumerable: false, 101 | writable: true, 102 | configurable: true 103 | } 104 | }); 105 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 106 | }; 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | var possibleConstructorReturn = function (self, call) { 119 | if (!self) { 120 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 121 | } 122 | 123 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 124 | }; 125 | 126 | var DEFAULT_CONFIG = { 127 | verbose: true 128 | }; 129 | 130 | var store = _extends({}, DEFAULT_CONFIG); 131 | 132 | function get$$1(key) { 133 | return key ? store[key] : store; 134 | } 135 | 136 | function set$$1(key, value) { 137 | var changes = {}; 138 | if (typeof key === 'string') { 139 | changes[key] = value; 140 | } else if (Object.prototype.toString.call(key) === '[object Object]') { 141 | changes = key; 142 | } 143 | _extends(store, changes); 144 | } 145 | 146 | var config = set$$1; 147 | config.set = set$$1; 148 | config.get = get$$1; 149 | 150 | var HAS_CONSOLE = window.console; 151 | var LEVEL_CONSOLE_MAP = { 152 | INFO: 'log', 153 | WARN: 'warn', 154 | ERROR: 'error', 155 | CRITICAL: 'error' 156 | }; 157 | 158 | // throw out Errors, with global prefix 'Logline: ' ahead of err.message 159 | function throwError(errMessage) { 160 | HAS_CONSOLE && console.error('Logline: ' + errMessage); 161 | } 162 | 163 | // print debug info in develper's console 164 | // TODO: if WechatFE/vConsole is detected, will not use %c feature, as it is not well supported 165 | function debug(namespace, level, descriptor, data) { 166 | if (HAS_CONSOLE && config.get().verbose) { 167 | window.console[LEVEL_CONSOLE_MAP[level.toUpperCase()] || LEVEL_CONSOLE_MAP.INFO]('[' + namespace + '] ' + level.toUpperCase() + ' ' + descriptor, data || ''); 168 | } 169 | } 170 | 171 | // filter any function in a object 172 | function filterFunction(obj) { 173 | var newObj = {}, 174 | i; 175 | 176 | if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object') { 177 | return obj; 178 | } 179 | 180 | for (i in obj) { 181 | if (obj.hasOwnProperty(i)) { 182 | if (typeof obj[i] !== 'function') { 183 | newObj[i] = filterFunction(obj[i]); 184 | } 185 | } 186 | } 187 | return newObj; 188 | } 189 | 190 | /** 191 | * Logline Interface 192 | * @class Interface 193 | */ 194 | 195 | var Interface = function () { 196 | /** 197 | * Logline constructor 198 | * @constructor 199 | * @param {String} namespace - namespace to use 200 | */ 201 | function Interface(namespace) { 202 | classCallCheck(this, Interface); 203 | 204 | this._namespace = namespace; 205 | } 206 | 207 | /** 208 | * add a log record 209 | * @method _reocrd 210 | * @private 211 | * @parma {String} level - log level 212 | * @param {String} descriptor - to speed up search and improve understanding 213 | * @param {Mixed} [data] - additional data 214 | */ 215 | 216 | 217 | createClass(Interface, [{ 218 | key: '_record', 219 | value: function _record(level, descriptor, data) { 220 | throwError('method _record is not implemented.'); 221 | } 222 | 223 | /** 224 | * add a level-info record 225 | * @method info 226 | * @param {String} descriptor - to speed up search and improve understanding 227 | * @param {Mixed} [data] - additional data 228 | */ 229 | 230 | }, { 231 | key: 'info', 232 | value: function info() { 233 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 234 | args[_key] = arguments[_key]; 235 | } 236 | 237 | this._record.apply(this, ['info'].concat(args)); 238 | } 239 | 240 | /** 241 | * add a level-warn record 242 | * @method warn 243 | * @param {String} descriptor - to speed up search and improve understanding 244 | * @param {Mixed} [data] - additional data 245 | */ 246 | 247 | }, { 248 | key: 'warn', 249 | value: function warn() { 250 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 251 | args[_key2] = arguments[_key2]; 252 | } 253 | 254 | this._record.apply(this, ['warn'].concat(args)); 255 | } 256 | 257 | /** 258 | * add a level-error record 259 | * @method error 260 | * @param {String} descriptor - to speed up search and improve understanding 261 | * @param {Mixed} [data] - additional data 262 | */ 263 | 264 | }, { 265 | key: 'error', 266 | value: function error() { 267 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 268 | args[_key3] = arguments[_key3]; 269 | } 270 | 271 | this._record.apply(this, ['error'].concat(args)); 272 | } 273 | 274 | /** 275 | * add a level-critical record 276 | * @method critical 277 | * @param {String} descriptor - to speed up search and improve understanding 278 | * @param {Mixed} [data] - additional data 279 | */ 280 | 281 | }, { 282 | key: 'critical', 283 | value: function critical() { 284 | for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 285 | args[_key4] = arguments[_key4]; 286 | } 287 | 288 | this._record.apply(this, ['critical'].concat(args)); 289 | } 290 | 291 | /** 292 | * initialize protocol 293 | * @method init 294 | * @static 295 | * @param {String} database - database name to use 296 | */ 297 | 298 | }], [{ 299 | key: 'init', 300 | value: function init(database) { 301 | return true; 302 | } 303 | 304 | /** 305 | * transform human readable time string, such as '3d', '.3' and '1.2' into Unix timestamp 306 | * the default relative time is Date.now(), if no second parameter is provided 307 | * @method transTimeFormat 308 | * @static 309 | * @param {String} time - time string to transform 310 | * @param {Number} [relative] - relative time to compare, default Date.now() 311 | * @return {Number|NaN} timestamp transformed 312 | */ 313 | 314 | }, { 315 | key: 'transTimeFormat', 316 | value: function transTimeFormat(time, relative) { 317 | // if falsy value or timestamp already, pass it through directly, 318 | if (!time || /^\d{13}$/.test(time)) { 319 | return +time; 320 | } 321 | // incase relative time isn't unix timestamp format, 322 | // neither a falsy value which will turned out to be Date.now() 323 | if (relative && !/^\d{13}$/.test(relative)) { 324 | throw new TypeError('relative time should be standard unix timestamp'); 325 | } 326 | 327 | return (relative || Date.now()) - time.replace(/d$/, '') * 24 * 3600 * 1000; 328 | } 329 | 330 | /** 331 | * get logs in range 332 | * if from and end is not defined, will fetch full log 333 | * @method get 334 | * @static 335 | * @param {String} from - time from, unix timestamp 336 | * @param {String} to - time end, unix timestamp 337 | * @param {Function} readyFn - function to call back with logs as parameter 338 | */ 339 | 340 | }, { 341 | key: 'get', 342 | value: function get$$1(from, to, readyFn) { 343 | throwError('method get is not implemented.'); 344 | } 345 | 346 | /** 347 | * clean logs = keep limited logs 348 | * @method keep 349 | * @static 350 | * @param {Number} daysToMaintain - keep logs within days 351 | */ 352 | 353 | }, { 354 | key: 'keep', 355 | value: function keep(daysToMaintain) { 356 | throwError('method keep is not implemented.'); 357 | } 358 | 359 | /** 360 | * delete log database 361 | * @method clean 362 | * @static 363 | */ 364 | 365 | }, { 366 | key: 'clean', 367 | value: function clean() { 368 | throwError('method clean is not implemented.'); 369 | } 370 | 371 | /** 372 | * protocol status map 373 | * @prop {Object} STATUS 374 | */ 375 | 376 | }, { 377 | key: 'STATUS', 378 | get: function get$$1() { 379 | return { 380 | INITING: 1, 381 | INITED: 2, 382 | FAILED: 4 383 | }; 384 | } 385 | }]); 386 | return Interface; 387 | }(); 388 | 389 | /** 390 | * Pool, for storage of async calling 391 | * @class Pool 392 | */ 393 | var Pool = function () { 394 | /** 395 | * Pool constructor 396 | * @constructor 397 | */ 398 | function Pool() { 399 | classCallCheck(this, Pool); 400 | 401 | this._pool = []; 402 | } 403 | 404 | /** 405 | * add an procedure 406 | * @method push 407 | * @param {Function} handler - procedure handler 408 | * @param {Object} context - procedure context 409 | */ 410 | 411 | 412 | createClass(Pool, [{ 413 | key: "push", 414 | value: function push(handler, context) { 415 | handler.context = context; 416 | this._pool.push(handler); 417 | } 418 | 419 | /** 420 | * consume pool 421 | * @method consume 422 | */ 423 | 424 | }, { 425 | key: "consume", 426 | value: function consume() { 427 | var handler; 428 | while (handler = this._pool.shift()) { 429 | handler.call(handler.context); 430 | } 431 | } 432 | }]); 433 | return Pool; 434 | }(); 435 | 436 | var READ_WRITE = 'readwrite'; 437 | 438 | /** 439 | * IndexedDB protocol 440 | * @class IndexedDBLogger 441 | */ 442 | 443 | var IndexedDBLogger = function (_LoggerInterface) { 444 | inherits(IndexedDBLogger, _LoggerInterface); 445 | 446 | /** 447 | * IndexedDB protocol constructor 448 | * @constructor 449 | * @param {String} namespace - namespace to use 450 | */ 451 | function IndexedDBLogger() { 452 | var _ref; 453 | 454 | classCallCheck(this, IndexedDBLogger); 455 | 456 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 457 | args[_key] = arguments[_key]; 458 | } 459 | 460 | return possibleConstructorReturn(this, (_ref = IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger)).call.apply(_ref, [this].concat(args))); 461 | } 462 | 463 | /** 464 | * add a log record 465 | * @method _reocrd 466 | * @private 467 | * @parma {String} level - log level 468 | * @param {String} descriptor - to speed up search and improve understanding 469 | * @param {Mixed} [data] - additional data 470 | */ 471 | 472 | 473 | createClass(IndexedDBLogger, [{ 474 | key: '_record', 475 | value: function _record(level, descriptor, data) { 476 | var _this2 = this; 477 | 478 | try { 479 | if (IndexedDBLogger.status !== Interface.STATUS.INITED) { 480 | IndexedDBLogger._pool.push(function () { 481 | return _this2._record(level, descriptor, data); 482 | }); 483 | if (IndexedDBLogger.status !== Interface.STATUS.INITING) { 484 | IndexedDBLogger.init(); 485 | } 486 | return; 487 | } 488 | 489 | debug(this._namespace, level, descriptor, data); 490 | var transaction = IndexedDBLogger.db.transaction(['logs'], READ_WRITE || 'readwrite'); 491 | transaction.onerror = function (event) { 492 | return throwError(event.target.error); 493 | }; 494 | 495 | var store = transaction.objectStore('logs'); 496 | // should not contains any function in data 497 | // otherwise 'DOMException: Failed to execute 'add' on 'IDBObjectStore': An object could not be cloned.' will be thrown 498 | var request = store.add({ 499 | time: Date.now(), 500 | level: level, 501 | namespace: this._namespace, 502 | descriptor: descriptor, 503 | data: filterFunction(data) 504 | }); 505 | 506 | request.onerror = function (event) { 507 | IndexedDBLogger.status = Interface.STATUS.FAILED; 508 | throwError(event.target.error); 509 | }; 510 | } catch (e) { 511 | throwError('failed to write, ' + e.message); 512 | } 513 | } 514 | 515 | /** 516 | * initialize protocol 517 | * @method init 518 | * @static 519 | * @param {String} database - database name to use 520 | */ 521 | 522 | }], [{ 523 | key: 'init', 524 | value: function init(database) { 525 | var _this3 = this; 526 | 527 | try { 528 | if (!IndexedDBLogger.support) { 529 | throwError('your platform does not support indexeddb protocol.'); 530 | } 531 | 532 | if (IndexedDBLogger.status) { 533 | return false; 534 | } 535 | 536 | IndexedDBLogger._pool = IndexedDBLogger._pool || new Pool(); 537 | IndexedDBLogger._database = database || 'logline'; 538 | IndexedDBLogger.status = get$1(IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger), 'STATUS', this).INITING; 539 | 540 | IndexedDBLogger.request = window.indexedDB.open(IndexedDBLogger._database); 541 | IndexedDBLogger.request.onerror = function (event) { 542 | return throwError('protocol indexeddb is prevented.'); 543 | }; 544 | IndexedDBLogger.request.onsuccess = function (event) { 545 | IndexedDBLogger.db = event.target.result; 546 | IndexedDBLogger.status = get$1(IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger), 'STATUS', _this3).INITED; 547 | IndexedDBLogger._pool.consume(); 548 | // globally handle db request errors 549 | IndexedDBLogger.db.onerror = function (event) { 550 | return throwError(event.target.error); 551 | }; 552 | }; 553 | IndexedDBLogger.request.onupgradeneeded = function (event) { 554 | // init dabasebase 555 | var db = event.target.result, 556 | store = db.createObjectStore('logs', { autoIncrement: true }); 557 | store.createIndex('namespace', 'namespace', { unique: false }); 558 | store.createIndex('level', 'level', { unique: false }); 559 | store.createIndex('descriptor', 'descriptor', { unique: false }); 560 | store.createIndex('data', 'data', { unique: false }); 561 | }; 562 | } catch (e) { 563 | throwError('failed init, ' + e.message); 564 | } 565 | } 566 | 567 | /** 568 | * get logs in range 569 | * if from and end is not defined, will fetch full log 570 | * @method get 571 | * @static 572 | * @param {String} [from] - time from, unix time stamp or falsy 573 | * @param {String} [to] - time end, unix time stamp or falsy 574 | * @param {Function} readyFn - function to call back with logs as parameter 575 | */ 576 | 577 | }, { 578 | key: 'get', 579 | value: function get$$1(from, to, readyFn) { 580 | try { 581 | if (IndexedDBLogger.status !== get$1(IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger), 'STATUS', this).INITED) { 582 | return IndexedDBLogger._pool.push(function () { 583 | return IndexedDBLogger.get(from, to, readyFn); 584 | }); 585 | } 586 | 587 | from = Interface.transTimeFormat(from); 588 | to = Interface.transTimeFormat(to); 589 | 590 | var store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY); 591 | if (!store) { 592 | return readyFn([]); 593 | } 594 | 595 | // IDBObjectStore.getAll is a non-standard API 596 | if (store.getAll) { 597 | var result = void 0, 598 | logs = []; 599 | store.getAll().onsuccess = function (event) { 600 | result = event.target.result; 601 | for (var i = 0; i < result.length; i++) { 602 | if (from && result[i].time < from || to && result[i].time > to) { 603 | continue; 604 | } 605 | logs.push(result[i]); 606 | } 607 | readyFn(logs); 608 | }; 609 | } else { 610 | var request = store.openCursor(), 611 | _logs = []; 612 | request.onsuccess = function (event) { 613 | var cursor = event.target.result; 614 | if (cursor) { 615 | if (from && cursor.value.time < from || to && cursor.value.time > to) { 616 | return cursor.continue(); 617 | } 618 | 619 | _logs.push({ 620 | time: cursor.value.time, 621 | level: cursor.value.level, 622 | namespace: cursor.value.namespace, 623 | descriptor: cursor.value.descriptor, 624 | data: cursor.value.data 625 | }); 626 | cursor.continue(); 627 | } else { 628 | readyFn(_logs); 629 | } 630 | }; 631 | } 632 | } catch (e) { 633 | throwError('failed to get logs, ' + e.message); 634 | } 635 | } 636 | 637 | /** 638 | * clean logs = keep limited logs 639 | * @method keep 640 | * @static 641 | * @param {Number} daysToMaintain - keep logs within days 642 | */ 643 | 644 | }, { 645 | key: 'keep', 646 | value: function keep(daysToMaintain) { 647 | try { 648 | if (IndexedDBLogger.status !== get$1(IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger), 'STATUS', this).INITED) { 649 | return IndexedDBLogger._pool.push(function () { 650 | return IndexedDBLogger.keep(daysToMaintain); 651 | }); 652 | } 653 | 654 | var store = IndexedDBLogger._getTransactionStore(READ_WRITE); 655 | if (!store) { 656 | return false; 657 | } 658 | if (!daysToMaintain) { 659 | var request = store.clear().onerror = function (event) { 660 | return throwError(event.target.error); 661 | }; 662 | } else { 663 | var range = Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000; 664 | var _request = store.openCursor(); 665 | _request.onsuccess = function (event) { 666 | var cursor = event.target.result; 667 | if (cursor && cursor.value.time < range) { 668 | store.delete(cursor.primaryKey); 669 | cursor.continue(); 670 | } 671 | }; 672 | _request.onerror = function (event) { 673 | return throwError('unable to locate logs earlier than ' + daysToMaintain + 'd.'); 674 | }; 675 | } 676 | } catch (e) { 677 | throwError('failed to keep logs, ' + e.message); 678 | } 679 | } 680 | 681 | /** 682 | * delete log database 683 | * @method clean 684 | * @static 685 | */ 686 | 687 | }, { 688 | key: 'clean', 689 | value: function clean() { 690 | try { 691 | if (IndexedDBLogger.status !== get$1(IndexedDBLogger.__proto__ || Object.getPrototypeOf(IndexedDBLogger), 'STATUS', this).INITED) { 692 | return IndexedDBLogger._pool.push(function () { 693 | return IndexedDBLogger.clean(); 694 | }); 695 | } 696 | 697 | // database can be removed only after all connections are closed 698 | IndexedDBLogger.db.close(); 699 | var request = window.indexedDB.deleteDatabase(IndexedDBLogger._database); 700 | request.onerror = function (event) { 701 | return throwError(event.target.error); 702 | }; 703 | /* eslint no-unused-vars: "off" */ 704 | request.onsuccess = function (event) { 705 | delete IndexedDBLogger.status; 706 | delete IndexedDBLogger.db; 707 | }; 708 | } catch (e) { 709 | throwError('failed to cleanup logs, ' + e.message); 710 | } 711 | } 712 | 713 | /** 714 | * get internal transaction store 715 | * @method _getTransactionStore 716 | * @private 717 | * @static 718 | * @param {String} mode - transaction mode 719 | * @return {Object} - internal object store 720 | */ 721 | 722 | }, { 723 | key: '_getTransactionStore', 724 | value: function _getTransactionStore(mode) { 725 | try { 726 | if (IndexedDBLogger.db) { 727 | var transaction = IndexedDBLogger.db.transaction(['logs'], mode || READ_WRITE); 728 | transaction.onerror = function (event) { 729 | return throwError(event.target.error); 730 | }; 731 | return transaction.objectStore('logs'); 732 | } else { 733 | throwError('log database is not created or connections are closed, considering init it.'); 734 | } 735 | } catch (e) { 736 | throwError('failed to generate new transaction, ' + e.message); 737 | return false; 738 | } 739 | } 740 | 741 | /** 742 | * detect support situation 743 | * @prop {Boolean} support 744 | */ 745 | 746 | }, { 747 | key: 'support', 748 | get: function get$$1() { 749 | var support = !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange); 750 | return support; 751 | } 752 | }]); 753 | return IndexedDBLogger; 754 | }(Interface); 755 | 756 | /** 757 | * Localstorage protocol 758 | * @class LocalStorageLogger 759 | */ 760 | 761 | var LocalStorageLogger = function (_LoggerInterface) { 762 | inherits(LocalStorageLogger, _LoggerInterface); 763 | 764 | /** 765 | * Localstorage protocol constructor 766 | * @constructor 767 | * @param {String} namespace - namespace to use 768 | */ 769 | function LocalStorageLogger() { 770 | var _ref; 771 | 772 | classCallCheck(this, LocalStorageLogger); 773 | 774 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 775 | args[_key] = arguments[_key]; 776 | } 777 | 778 | return possibleConstructorReturn(this, (_ref = LocalStorageLogger.__proto__ || Object.getPrototypeOf(LocalStorageLogger)).call.apply(_ref, [this].concat(args))); 779 | } 780 | 781 | /** 782 | * add a log record 783 | * @method _reocrd 784 | * @private 785 | * @parma {String} level - log level 786 | * @param {String} descriptor - to speed up search and improve understanding 787 | * @param {Mixed} [data] - additional data 788 | */ 789 | 790 | 791 | createClass(LocalStorageLogger, [{ 792 | key: '_record', 793 | value: function _record(level, descriptor, data) { 794 | var logs; 795 | try { 796 | logs = window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []; 797 | logs.push([Date.now(), this._namespace, level, descriptor, data]); 798 | debug(this._namespace, level, descriptor, data); 799 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs)); 800 | } catch (e) { 801 | window.localStorage.removeItem(LocalStorageLogger._database); 802 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([])); 803 | throwError('failed to write, may be localStorage is full, ' + e.message); 804 | } 805 | } 806 | 807 | /** 808 | * initialize protocol 809 | * @method init 810 | * @static 811 | * @param {String} database - database name to use 812 | */ 813 | 814 | }], [{ 815 | key: 'init', 816 | value: function init(database) { 817 | try { 818 | if (!LocalStorageLogger.support) { 819 | throwError('your platform does not support localstorage protocol.'); 820 | } 821 | LocalStorageLogger._database = database || 'logline'; 822 | if (!window.localStorage.getItem(LocalStorageLogger._database)) { 823 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([])); 824 | } 825 | LocalStorageLogger.status = get$1(LocalStorageLogger.__proto__ || Object.getPrototypeOf(LocalStorageLogger), 'STATUS', this).INITED; 826 | } catch (e) { 827 | throwError('failed to init, ' + e.message); 828 | } 829 | } 830 | 831 | /** 832 | * get logs in range 833 | * if from and end is not defined, will fetch full log 834 | * @method get 835 | * @static 836 | * @param {String} from - time from, unix time stamp or falsy 837 | * @param {String} to - time end, unix time stamp or falsy 838 | * @param {Function} readyFn - function to call back with logs as parameter 839 | */ 840 | 841 | }, { 842 | key: 'get', 843 | value: function get$$1(from, to, readyFn) { 844 | var logs, i; 845 | try { 846 | logs = JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)); 847 | 848 | from = Interface.transTimeFormat(from); 849 | to = Interface.transTimeFormat(to); 850 | 851 | for (i = 0; i < logs.length; i++) { 852 | if (from && logs[i][0] < from || to && logs[i][0] > to) { 853 | continue; 854 | } 855 | 856 | logs[i] = { 857 | time: logs[i][0], 858 | namespace: logs[i][1], 859 | level: logs[i][2], 860 | descriptor: logs[i][3], 861 | data: logs[i][4] 862 | }; 863 | } 864 | readyFn(logs); 865 | } catch (e) { 866 | throwError('failed to get, ' + e.message); 867 | readyFn([]); 868 | } 869 | } 870 | 871 | /** 872 | * clean logs = keep limited logs 873 | * @method keep 874 | * @static 875 | * @param {Number} daysToMaintain - keep logs within days 876 | */ 877 | 878 | }, { 879 | key: 'keep', 880 | value: function keep(daysToMaintain) { 881 | var logs; 882 | try { 883 | logs = !daysToMaintain ? [] : (window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []).filter(function (log) { 884 | return log.time >= Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000; 885 | }); 886 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs)); 887 | } catch (e) { 888 | throwError('failed to keep, ' + e.message); 889 | } 890 | } 891 | 892 | /** 893 | * delete log database 894 | * @method clean 895 | * @static 896 | */ 897 | 898 | }, { 899 | key: 'clean', 900 | value: function clean() { 901 | try { 902 | delete LocalStorageLogger.status; 903 | window.localStorage.removeItem(LocalStorageLogger._database); 904 | } catch (e) { 905 | throwError('failed to clean, ' + e.message); 906 | } 907 | } 908 | 909 | /** 910 | * detect support situation 911 | * @prop {Boolean} support 912 | */ 913 | 914 | }, { 915 | key: 'support', 916 | get: function get$$1() { 917 | return 'localStorage' in window; 918 | } 919 | }]); 920 | return LocalStorageLogger; 921 | }(Interface); 922 | 923 | /** 924 | * Websql protocol 925 | * @class WebsqlLogger 926 | */ 927 | 928 | var WebsqlLogger = function (_LoggerInterface) { 929 | inherits(WebsqlLogger, _LoggerInterface); 930 | 931 | /** 932 | * Websql logline constructor 933 | * @constructor 934 | * @param {String} namespace - namespace to use 935 | */ 936 | function WebsqlLogger() { 937 | var _ref; 938 | 939 | classCallCheck(this, WebsqlLogger); 940 | 941 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 942 | args[_key] = arguments[_key]; 943 | } 944 | 945 | return possibleConstructorReturn(this, (_ref = WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger)).call.apply(_ref, [this].concat(args))); 946 | } 947 | 948 | /** 949 | * add a log record 950 | * @method _reocrd 951 | * @private 952 | * @parma {String} level - log level 953 | * @param {String} descriptor - to speed up search and improve understanding 954 | * @param {Mixed} [data] - additional data 955 | */ 956 | 957 | 958 | createClass(WebsqlLogger, [{ 959 | key: '_record', 960 | value: function _record(level, descriptor, data) { 961 | var _this2 = this; 962 | 963 | if (WebsqlLogger.status !== Interface.STATUS.INITED) { 964 | WebsqlLogger._pool.push(function () { 965 | return _this2._record(level, descriptor, data); 966 | }); 967 | if (WebsqlLogger.status !== Interface.STATUS.INITING) { 968 | WebsqlLogger.init(); 969 | } 970 | return; 971 | } 972 | 973 | try { 974 | debug(this._namespace, level, descriptor, data); 975 | WebsqlLogger._db.transaction(function (tx) { 976 | tx.executeSql('INSERT INTO logs (time, namespace, level, descriptor, data) VALUES(?, ?, ?, ? ,?)', [Date.now(), _this2._namespace, level, descriptor, data === undefined || data === '' ? '' : JSON.stringify(data) || ''], function () {/* empty func */}, function (tx, e) { 977 | throwError('write error, ' + e.message); 978 | }); 979 | }); 980 | } catch (e) { 981 | throwError('error inserting record, ' + e.message); 982 | } 983 | } 984 | 985 | /** 986 | * initialize protocol 987 | * @method init 988 | * @static 989 | * @param {String} database - database name to use 990 | */ 991 | 992 | }], [{ 993 | key: 'init', 994 | value: function init(database) { 995 | var _this3 = this; 996 | 997 | if (!WebsqlLogger.support) { 998 | throwError(new Error('your platform does not support websql protocol.')); 999 | } 1000 | 1001 | if (WebsqlLogger.status) { 1002 | return false; 1003 | } 1004 | 1005 | WebsqlLogger._pool = WebsqlLogger._pool || new Pool(); 1006 | WebsqlLogger._database = database || 'logline'; 1007 | WebsqlLogger.status = get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', this).INITING; 1008 | 1009 | try { 1010 | WebsqlLogger._db = window.openDatabase(WebsqlLogger._database, '1.0', 'cats loves logs', 4.85 * 1024 * 1024); 1011 | WebsqlLogger._db.transaction(function (tx) { 1012 | tx.executeSql('CREATE TABLE IF NOT EXISTS logs (time, namespace, level, descriptor, data)', [], function () { 1013 | WebsqlLogger.status = get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', _this3).INITED; 1014 | WebsqlLogger._pool.consume(); 1015 | }, function (tx, e) { 1016 | WebsqlLogger.status = get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', _this3).FAILED; 1017 | throwError('unable to create table, ' + e.message); 1018 | }); 1019 | }); 1020 | } catch (e) { 1021 | throwError('unable to init log database, ' + e.message); 1022 | } 1023 | } 1024 | 1025 | /** 1026 | * get logs in range 1027 | * if from and end is not defined, will fetch full log 1028 | * @method get 1029 | * @static 1030 | * @param {String} from - time from, unix time stamp or falsy 1031 | * @param {String} to - time end, unix time stamp or falsy 1032 | * @param {Function} readyFn - function to call back with logs as parameter 1033 | */ 1034 | 1035 | }, { 1036 | key: 'get', 1037 | value: function get$$1(from, to, readyFn) { 1038 | if (WebsqlLogger.status !== get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', this).INITED) { 1039 | return WebsqlLogger._pool.push(function () { 1040 | return WebsqlLogger.get(from, to, readyFn); 1041 | }); 1042 | } 1043 | 1044 | from = Interface.transTimeFormat(from); 1045 | to = Interface.transTimeFormat(to); 1046 | 1047 | try { 1048 | WebsqlLogger._db.transaction(function (tx) { 1049 | tx.executeSql('SELECT * FROM logs ORDER BY time DESC', [], function (tx, res) { 1050 | var logs = [], 1051 | line, 1052 | index = res.rows.length, 1053 | item; 1054 | while (--index >= 0) { 1055 | item = res.rows.item(index); 1056 | if (from && item.time < from || to && item.time > to) { 1057 | continue; 1058 | } 1059 | 1060 | // in some devices, properties are configureable: false, writable: false 1061 | // we need deep copy 1062 | line = JSON.parse(JSON.stringify(item)); 1063 | // incase data is an object, not a string 1064 | try { 1065 | line.data = JSON.parse(line.data); 1066 | } catch (e) {/* leave line.data as it be */} 1067 | logs.push(line); 1068 | } 1069 | readyFn(logs); 1070 | }, function (tx, e) { 1071 | throwError(e.message); 1072 | }); 1073 | }); 1074 | } catch (e) { 1075 | throwError('unable to collect logs from database.'); 1076 | } 1077 | } 1078 | 1079 | /** 1080 | * clean logs = keep limited logs 1081 | * @method keep 1082 | * @static 1083 | * @param {Number} daysToMaintain - keep logs within days 1084 | */ 1085 | 1086 | }, { 1087 | key: 'keep', 1088 | value: function keep(daysToMaintain) { 1089 | if (WebsqlLogger.status !== get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', this).INITED) { 1090 | return WebsqlLogger._pool.push(function () { 1091 | return WebsqlLogger.keep(daysToMaintain); 1092 | }); 1093 | } 1094 | 1095 | try { 1096 | WebsqlLogger._db.transaction(function (tx) { 1097 | if (daysToMaintain) { 1098 | tx.executeSql('DELETE FROM logs WHERE time < ?', [Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000], function () {/* empty func */}, function (tx, e) { 1099 | throwError(e.message); 1100 | }); 1101 | } else { 1102 | tx.executeSql('DELETE FROM logs', [], function () {/* empty func */}, function (tx, e) { 1103 | throwError(e.message); 1104 | }); 1105 | } 1106 | }); 1107 | } catch (e) { 1108 | throwError('unable to clean logs from database.'); 1109 | } 1110 | } 1111 | 1112 | /** 1113 | * delete log database 1114 | * @method clean 1115 | * @static 1116 | */ 1117 | 1118 | }, { 1119 | key: 'clean', 1120 | value: function clean() { 1121 | if (WebsqlLogger.status !== get$1(WebsqlLogger.__proto__ || Object.getPrototypeOf(WebsqlLogger), 'STATUS', this).INITED) { 1122 | WebsqlLogger._pool.push(function () { 1123 | return WebsqlLogger.clean(); 1124 | }); 1125 | return; 1126 | } 1127 | 1128 | try { 1129 | WebsqlLogger._db.transaction(function (tx) { 1130 | tx.executeSql('DROP TABLE logs', [], function () { 1131 | delete WebsqlLogger.status; 1132 | }, function (tx, e) { 1133 | throwError(e.message); 1134 | }); 1135 | }); 1136 | } catch (e) { 1137 | throwError('unable to clean log database.'); 1138 | } 1139 | } 1140 | 1141 | /** 1142 | * detect support situation 1143 | * @prop {Boolean} support 1144 | */ 1145 | 1146 | }, { 1147 | key: 'support', 1148 | get: function get$$1() { 1149 | return 'openDatabase' in window; 1150 | } 1151 | }]); 1152 | return WebsqlLogger; 1153 | }(Interface); 1154 | 1155 | var Logline = function () { 1156 | /** 1157 | * Logline constructor 1158 | * @constructor 1159 | * @param {String} namespace - namespace to use 1160 | * @return {Object Protocol Instance} 1161 | */ 1162 | function Logline(namespace) { 1163 | classCallCheck(this, Logline); 1164 | 1165 | if (!(this instanceof Logline)) { 1166 | return new Logline(namespace); 1167 | } 1168 | try { 1169 | Logline._checkProtocol(); 1170 | return new Logline._protocol(namespace); 1171 | } catch (e) { 1172 | return new Interface(namespace); 1173 | } 1174 | } 1175 | 1176 | /** 1177 | * change config 1178 | * @method config 1179 | * @param {String|Object} key - config key, or config object 1180 | * @param {Any} [value] - new config value 1181 | * @return {Void} 1182 | */ 1183 | 1184 | 1185 | createClass(Logline, null, [{ 1186 | key: '_initProtocol', 1187 | 1188 | 1189 | /** 1190 | * choose a protocol to initialize 1191 | * @method _initProtocol 1192 | * @private 1193 | * @static 1194 | * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL 1195 | * @return {Object} Logline 1196 | */ 1197 | value: function _initProtocol(protocol) { 1198 | Logline._protocol = protocol; 1199 | Logline._protocol.init(Logline._database || 'logline'); 1200 | } 1201 | 1202 | /** 1203 | * check protocol 1204 | * if no protocol is chosen, will try to choose an available one automatically 1205 | * if none of the protocols is available, an error will be thrown 1206 | * @method _checkProtocol 1207 | * @private 1208 | * @static 1209 | */ 1210 | 1211 | }, { 1212 | key: '_checkProtocol', 1213 | value: function _checkProtocol() { 1214 | if (!Logline._protocol) { 1215 | var protocols = Object.keys(Logline.PROTOCOL), 1216 | protocol = void 0; 1217 | while (protocol = Logline.PROTOCOL[protocols.shift()]) { 1218 | if (protocol.support) { 1219 | Logline._initProtocol(protocol); 1220 | return; 1221 | } 1222 | } 1223 | 1224 | throwError('protocols ' + protocols.join(', ').toLowerCase() + ' are not supported on this platform'); 1225 | } 1226 | } 1227 | 1228 | /** 1229 | * get logs in range 1230 | * if from and end is not defined, will fetch full log 1231 | * @method get 1232 | * @static 1233 | * @param {String} [from] - time from 1234 | * @param {String} [to] - time end 1235 | * @param {Function} readyFn - function to call back with logs as parameter 1236 | */ 1237 | 1238 | }, { 1239 | key: 'get', 1240 | value: function get$$1(from, to, readyFn) { 1241 | Logline._checkProtocol(); 1242 | 1243 | switch (arguments.length) { 1244 | case 1: 1245 | readyFn = from; 1246 | from = undefined; 1247 | break; 1248 | case 2: 1249 | readyFn = to; 1250 | to = undefined; 1251 | break; 1252 | case 3: 1253 | default: 1254 | break; 1255 | } 1256 | 1257 | Logline._protocol.get(from, to, readyFn); 1258 | } 1259 | 1260 | /** 1261 | * read all logs 1262 | * @method all 1263 | * @static 1264 | * @param {Function} readyFn - function to call back with logs as parameter 1265 | */ 1266 | 1267 | }, { 1268 | key: 'all', 1269 | value: function all(readyFn) { 1270 | Logline.get(readyFn); 1271 | } 1272 | 1273 | /** 1274 | * clean up logs = keep limited logs 1275 | * @method keep 1276 | * @static 1277 | * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3' 1278 | * @return {Object} Logline 1279 | */ 1280 | 1281 | }, { 1282 | key: 'keep', 1283 | value: function keep(daysToMaintain) { 1284 | Logline._checkProtocol(); 1285 | Logline._protocol.keep(daysToMaintain); 1286 | return this; 1287 | } 1288 | 1289 | /** 1290 | * delete log database 1291 | * @method clean 1292 | * @static 1293 | * @return {Object} Logline 1294 | */ 1295 | 1296 | }, { 1297 | key: 'clean', 1298 | value: function clean() { 1299 | Logline._checkProtocol(); 1300 | Logline._protocol.clean(); 1301 | return this; 1302 | } 1303 | 1304 | /** 1305 | * choose a protocol 1306 | * @method using 1307 | * @static 1308 | * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL 1309 | * @param {String} [database] - custome database name 1310 | * @return {Object} Logline 1311 | */ 1312 | 1313 | }, { 1314 | key: 'using', 1315 | value: function using(protocol, database) { 1316 | // protocol unavailable is not allowed 1317 | if (-1 === [IndexedDBLogger, LocalStorageLogger, WebsqlLogger].indexOf(protocol)) { 1318 | throwError('specialfied protocol ' + (protocol ? protocol + ' ' : '') + 'is not available'); 1319 | } 1320 | 1321 | // once protocol is selected, it shall not be changed during runtime 1322 | if (Logline._protocol) { 1323 | return this; 1324 | } 1325 | 1326 | Logline.database(database || Logline._database); 1327 | Logline._initProtocol(protocol); 1328 | return this; 1329 | } 1330 | 1331 | /** 1332 | * specialfy a custome database name, in case of any conflicts 1333 | * @methd database 1334 | * @static 1335 | * @param {String} name - target database name 1336 | */ 1337 | 1338 | }, { 1339 | key: 'database', 1340 | value: function database(name) { 1341 | Logline._database = name; 1342 | } 1343 | }, { 1344 | key: 'config', 1345 | get: function get$$1() { 1346 | return config; 1347 | } 1348 | }]); 1349 | return Logline; 1350 | }(); 1351 | 1352 | // export protocols for modification and mounting 1353 | 1354 | 1355 | Logline.PROTOCOL = { 1356 | INDEXEDDB: IndexedDBLogger, 1357 | LOCALSTORAGE: LocalStorageLogger, 1358 | WEBSQL: WebsqlLogger 1359 | }; 1360 | 1361 | // export protocol interface for user custom implements 1362 | Logline.INTERFACE = Object.freeze(Interface); 1363 | 1364 | // export Logline env, just like Unix Environment variables 1365 | Logline.env = { 1366 | verbose: true 1367 | }; 1368 | 1369 | return Logline; 1370 | 1371 | }))); 1372 | -------------------------------------------------------------------------------- /dist/logline.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Logline=t()}(this,function(){"use strict";function e(e){return e?d[e]:d}function t(e,t){var o={};"string"==typeof e?o[e]=t:"[object Object]"===Object.prototype.toString.call(e)&&(o=e),s(d,o)}function o(e){_&&console.error("Logline: "+e)}function r(e,t,o,r){_&&g.get().verbose&&window.console[y[t.toUpperCase()]||y.INFO]("["+e+"] "+t.toUpperCase()+" "+o,r||"")}function n(e){var t,o={};if("object"!==(void 0===e?"undefined":a(e)))return e;for(t in e)e.hasOwnProperty(t)&&"function"!=typeof e[t]&&(o[t]=n(e[t]));return o}var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},c=function(){function e(e,t){for(var o=0;or||c.push(i[o]);n(c)}}else{var s=a.openCursor(),l=[];s.onsuccess=function(t){var o=t.target.result;if(o){if(e&&o.value.timer)return o.continue();l.push({time:o.value.time,level:o.value.level,namespace:o.value.namespace,descriptor:o.value.descriptor,data:o.value.data}),o.continue()}else n(l)}}}catch(e){o("failed to get logs, "+e.message)}}},{key:"keep",value:function(e){try{if(t.status!==u(t.__proto__||Object.getPrototypeOf(t),"STATUS",this).INITED)return t._pool.push(function(){return t.keep(e)});var r=t._getTransactionStore("readwrite");if(!r)return!1;if(e){var n=Date.now()-24*(e||2)*3600*1e3,a=r.openCursor();a.onsuccess=function(e){var t=e.target.result;t&&t.value.timer||(a[i]={time:a[i][0],namespace:a[i][1],level:a[i][2],descriptor:a[i][3],data:a[i][4]});n(a)}catch(e){o("failed to get, "+e.message),n([])}}},{key:"keep",value:function(e){var r;try{r=e?(window.localStorage.getItem(t._database)?JSON.parse(window.localStorage.getItem(t._database)):[]).filter(function(t){return t.time>=Date.now()-24*(e||2)*3600*1e3}):[],window.localStorage.setItem(t._database,JSON.stringify(r))}catch(e){o("failed to keep, "+e.message)}}},{key:"clean",value:function(){try{delete t.status,window.localStorage.removeItem(t._database)}catch(e){o("failed to clean, "+e.message)}}},{key:"support",get:function(){return"localStorage"in window}}]),t}(v),S=function(e){function t(){var e;i(this,t);for(var o=arguments.length,r=Array(o),n=0;n=0;)if(i=o.rows.item(s),!(e&&i.timer)){a=JSON.parse(JSON.stringify(i));try{a.data=JSON.parse(a.data)}catch(e){}c.push(a)}n(c)},function(e,t){o(t.message)})})}catch(e){o("unable to collect logs from database.")}}},{key:"keep",value:function(e){if(t.status!==u(t.__proto__||Object.getPrototypeOf(t),"STATUS",this).INITED)return t._pool.push(function(){return t.keep(e)});try{t._db.transaction(function(t){e?t.executeSql("DELETE FROM logs WHERE time < ?",[Date.now()-24*(e||2)*3600*1e3],function(){},function(e,t){o(t.message)}):t.executeSql("DELETE FROM logs",[],function(){},function(e,t){o(t.message)})})}catch(e){o("unable to clean logs from database.")}}},{key:"clean",value:function(){if(t.status!==u(t.__proto__||Object.getPrototypeOf(t),"STATUS",this).INITED)return void t._pool.push(function(){return t.clean()});try{t._db.transaction(function(e){e.executeSql("DROP TABLE logs",[],function(){delete t.status},function(e,t){o(t.message)})})}catch(e){o("unable to clean log database.")}}},{key:"support",get:function(){return"openDatabase"in window}}]),t}(v),w=function(){function e(t){if(i(this,e),!(this instanceof e))return new e(t);try{return e._checkProtocol(),new e._protocol(t)}catch(e){return new v(t)}}return c(e,null,[{key:"_initProtocol",value:function(t){e._protocol=t,e._protocol.init(e._database||"logline")}},{key:"_checkProtocol",value:function(){if(!e._protocol){for(var t=Object.keys(e.PROTOCOL),r=void 0;r=e.PROTOCOL[t.shift()];)if(r.support)return void e._initProtocol(r);o("protocols "+t.join(", ").toLowerCase()+" are not supported on this platform")}}},{key:"get",value:function(t,o,r){switch(e._checkProtocol(),arguments.length){case 1:r=t,t=void 0;break;case 2:r=o,o=void 0}e._protocol.get(t,o,r)}},{key:"all",value:function(t){e.get(t)}},{key:"keep",value:function(t){return e._checkProtocol(),e._protocol.keep(t),this}},{key:"clean",value:function(){return e._checkProtocol(),e._protocol.clean(),this}},{key:"using",value:function(t,r){return-1===[h,b,S].indexOf(t)&&o("specialfied protocol "+(t?t+" ":"")+"is not available"),e._protocol?this:(e.database(r||e._database),e._initProtocol(t),this)}},{key:"database",value:function(t){e._database=t}},{key:"config",get:function(){return g}}]),e}();return w.PROTOCOL={INDEXEDDB:h,LOCALSTORAGE:b,WEBSQL:S},w.INTERFACE=Object.freeze(v),w.env={verbose:!0},w}); 2 | //# sourceMappingURL=logline.min.js.map 3 | -------------------------------------------------------------------------------- /dist/logline.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"logline.min.js","sources":["../src/lib/config.js","../src/lib/util.js","../src/protocols/interface.js","../src/lib/pool.js","../src/protocols/indexeddb.js","../src/protocols/localstorage.js","../src/protocols/websql.js","../src/logline.js"],"sourcesContent":["const DEFAULT_CONFIG = {\n verbose: true\n};\n\nlet store = Object.assign({}, DEFAULT_CONFIG);\n\nexport function get(key) {\n return key ? store[key] : store;\n}\n\nexport function set(key, value) {\n let changes = {};\n if (typeof key === 'string') {\n changes[key] = value;\n } else if (Object.prototype.toString.call(key) === '[object Object]') {\n changes = key;\n }\n Object.assign(store, changes);\n}\n\nlet config = set;\nconfig.set = set;\nconfig.get = get;\n\nexport default config;\n","import config from './config';\n\nconst HAS_CONSOLE = window.console;\nconst LEVEL_CONSOLE_MAP = {\n INFO: 'log',\n WARN: 'warn',\n ERROR: 'error',\n CRITICAL: 'error'\n};\n\n// throw out Errors, with global prefix 'Logline: ' ahead of err.message\nexport function throwError(errMessage) {\n HAS_CONSOLE && console.error('Logline: ' + errMessage);\n}\n\n// print debug info in develper's console\n// TODO: if WechatFE/vConsole is detected, will not use %c feature, as it is not well supported\nexport function debug(namespace, level, descriptor, data) {\n if (HAS_CONSOLE && config.get().verbose) {\n window.console[LEVEL_CONSOLE_MAP[level.toUpperCase()] || LEVEL_CONSOLE_MAP.INFO](`[${namespace}] ${level.toUpperCase()} ${descriptor}`, data || '');\n }\n}\n\n// filter any function in a object\nexport function filterFunction(obj) {\n var newObj = {}, i;\n\n if (typeof obj !== 'object') {\n return obj;\n }\n\n for (i in obj) {\n if (obj.hasOwnProperty(i)) {\n if (typeof obj[i] !== 'function') {\n newObj[i] = filterFunction(obj[i]);\n }\n }\n }\n return newObj;\n}\n","import * as util from '../lib/util';\n\n/**\n * Logline Interface\n * @class Interface\n */\nexport default class Interface {\n /**\n * Logline constructor\n * @constructor\n * @param {String} namespace - namespace to use\n */\n constructor(namespace) {\n this._namespace = namespace;\n }\n\n /**\n * add a log record\n * @method _reocrd\n * @private\n * @parma {String} level - log level\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n _record(level, descriptor, data) {\n util.throwError('method _record is not implemented.');\n }\n\n /**\n * add a level-info record\n * @method info\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n info(...args) {\n this._record('info', ...args);\n }\n\n /**\n * add a level-warn record\n * @method warn\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n warn(...args) {\n this._record('warn', ...args);\n }\n\n /**\n * add a level-error record\n * @method error\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n error(...args) {\n this._record('error', ...args);\n }\n\n /**\n * add a level-critical record\n * @method critical\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n critical(...args) {\n this._record('critical', ...args);\n }\n\n /**\n * initialize protocol\n * @method init\n * @static\n * @param {String} database - database name to use\n */\n static init(database) {\n return true;\n }\n\n /**\n * transform human readable time string, such as '3d', '.3' and '1.2' into Unix timestamp\n * the default relative time is Date.now(), if no second parameter is provided\n * @method transTimeFormat\n * @static\n * @param {String} time - time string to transform\n * @param {Number} [relative] - relative time to compare, default Date.now()\n * @return {Number|NaN} timestamp transformed\n */\n static transTimeFormat(time, relative) {\n // if falsy value or timestamp already, pass it through directly,\n if (!time || /^\\d{13}$/.test(time)) {\n return +time;\n }\n // incase relative time isn't unix timestamp format,\n // neither a falsy value which will turned out to be Date.now()\n if (relative && !/^\\d{13}$/.test(relative)) {\n throw new TypeError('relative time should be standard unix timestamp');\n }\n\n return (relative || Date.now()) - time.replace(/d$/, '') * 24 * 3600 * 1000;\n }\n\n /**\n * get logs in range\n * if from and end is not defined, will fetch full log\n * @method get\n * @static\n * @param {String} from - time from, unix timestamp\n * @param {String} to - time end, unix timestamp\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static get(from, to, readyFn) {\n util.throwError('method get is not implemented.');\n }\n\n /**\n * clean logs = keep limited logs\n * @method keep\n * @static\n * @param {Number} daysToMaintain - keep logs within days\n */\n static keep(daysToMaintain) {\n util.throwError('method keep is not implemented.');\n }\n\n /**\n * delete log database\n * @method clean\n * @static\n */\n static clean() {\n util.throwError('method clean is not implemented.');\n }\n\n /**\n * protocol status map\n * @prop {Object} STATUS\n */\n static get STATUS() {\n return {\n INITING: 1,\n INITED: 2,\n FAILED: 4\n };\n }\n}\n","/**\n * Pool, for storage of async calling\n * @class Pool\n */\nexport default class Pool {\n /**\n * Pool constructor\n * @constructor\n */\n constructor() {\n this._pool = [];\n }\n\n /**\n * add an procedure\n * @method push\n * @param {Function} handler - procedure handler\n * @param {Object} context - procedure context\n */\n push(handler, context) {\n handler.context = context;\n this._pool.push(handler);\n }\n\n /**\n * consume pool\n * @method consume\n */\n consume() {\n var handler;\n while ((handler = this._pool.shift())) {\n handler.call(handler.context);\n }\n }\n}\n","import LoggerInterface from './interface';\nimport Pool from '../lib/pool';\nimport * as util from '../lib/util';\n\nconst READ_WRITE = 'readwrite';\n\n/**\n * IndexedDB protocol\n * @class IndexedDBLogger\n */\nexport default class IndexedDBLogger extends LoggerInterface {\n /**\n * IndexedDB protocol constructor\n * @constructor\n * @param {String} namespace - namespace to use\n */\n constructor(...args) {\n super(...args);\n }\n\n /**\n * add a log record\n * @method _reocrd\n * @private\n * @parma {String} level - log level\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n _record(level, descriptor, data) {\n try {\n if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITED) {\n IndexedDBLogger._pool.push(() => this._record(level, descriptor, data));\n if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITING) {\n IndexedDBLogger.init();\n }\n return;\n }\n\n util.debug(this._namespace, level, descriptor, data);\n let transaction = IndexedDBLogger.db.transaction(['logs'], READ_WRITE || 'readwrite');\n transaction.onerror = event => util.throwError(event.target.error);\n\n let store = transaction.objectStore('logs');\n // should not contains any function in data\n // otherwise 'DOMException: Failed to execute 'add' on 'IDBObjectStore': An object could not be cloned.' will be thrown\n let request = store.add({\n time: Date.now(),\n level: level,\n namespace: this._namespace,\n descriptor: descriptor,\n data: util.filterFunction(data)\n });\n\n request.onerror = event => {\n IndexedDBLogger.status = LoggerInterface.STATUS.FAILED;\n util.throwError(event.target.error);\n };\n } catch (e) {\n util.throwError('failed to write, ' + e.message);\n }\n }\n\n /**\n * initialize protocol\n * @method init\n * @static\n * @param {String} database - database name to use\n */\n static init(database) {\n try {\n if (!IndexedDBLogger.support) {\n util.throwError('your platform does not support indexeddb protocol.');\n }\n\n if (IndexedDBLogger.status) {\n return false;\n }\n\n IndexedDBLogger._pool = IndexedDBLogger._pool || new Pool();\n IndexedDBLogger._database = database || 'logline';\n IndexedDBLogger.status = super.STATUS.INITING;\n\n IndexedDBLogger.request = window.indexedDB.open(IndexedDBLogger._database);\n IndexedDBLogger.request.onerror = event => util.throwError('protocol indexeddb is prevented.');\n IndexedDBLogger.request.onsuccess = event => {\n IndexedDBLogger.db = event.target.result;\n IndexedDBLogger.status = super.STATUS.INITED;\n IndexedDBLogger._pool.consume();\n // globally handle db request errors\n IndexedDBLogger.db.onerror = event => util.throwError(event.target.error);\n };\n IndexedDBLogger.request.onupgradeneeded = event => {\n // init dabasebase\n let db = event.target.result, store = db.createObjectStore('logs', { autoIncrement: true });\n store.createIndex('namespace', 'namespace', { unique: false });\n store.createIndex('level', 'level', { unique: false });\n store.createIndex('descriptor', 'descriptor', { unique: false });\n store.createIndex('data', 'data', { unique: false });\n };\n } catch (e) {\n util.throwError('failed init, ' + e.message);\n }\n }\n\n /**\n * get logs in range\n * if from and end is not defined, will fetch full log\n * @method get\n * @static\n * @param {String} [from] - time from, unix time stamp or falsy\n * @param {String} [to] - time end, unix time stamp or falsy\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static get(from, to, readyFn) {\n try {\n if (IndexedDBLogger.status !== super.STATUS.INITED) {\n return IndexedDBLogger._pool.push(() => IndexedDBLogger.get(from, to, readyFn));\n }\n\n from = LoggerInterface.transTimeFormat(from);\n to = LoggerInterface.transTimeFormat(to);\n\n let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY);\n if (!store) {\n return readyFn([]);\n }\n\n // IDBObjectStore.getAll is a non-standard API\n if (store.getAll) {\n let result, logs = [];\n store.getAll().onsuccess = event => {\n result = event.target.result;\n for (let i = 0; i < result.length; i++) {\n if ((from && result[i].time < from) || (to && result[i].time > to)) {\n continue;\n }\n logs.push(result[i]);\n }\n readyFn(logs);\n };\n } else {\n let request = store.openCursor(), logs = [];\n request.onsuccess = event => {\n var cursor = event.target.result;\n if (cursor) {\n if ((from && cursor.value.time < from) || (to && cursor.value.time > to)) {\n return cursor.continue();\n }\n\n logs.push({\n time: cursor.value.time,\n level: cursor.value.level,\n namespace: cursor.value.namespace,\n descriptor: cursor.value.descriptor,\n data: cursor.value.data\n });\n cursor.continue();\n }\n else {\n readyFn(logs);\n }\n };\n }\n } catch (e) {\n util.throwError('failed to get logs, ' + e.message);\n }\n }\n\n /**\n * clean logs = keep limited logs\n * @method keep\n * @static\n * @param {Number} daysToMaintain - keep logs within days\n */\n static keep(daysToMaintain) {\n try {\n if (IndexedDBLogger.status !== super.STATUS.INITED) {\n return IndexedDBLogger._pool.push(() => IndexedDBLogger.keep(daysToMaintain));\n }\n\n let store = IndexedDBLogger._getTransactionStore(READ_WRITE);\n if (!store) {\n return false;\n }\n if (!daysToMaintain) {\n let request = store.clear().onerror = event => util.throwError(event.target.error);\n }\n else {\n let range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);\n let request = store.openCursor();\n request.onsuccess = event => {\n let cursor = event.target.result;\n if (cursor && cursor.value.time < range) {\n store.delete(cursor.primaryKey);\n cursor.continue();\n }\n };\n request.onerror = event => util.throwError('unable to locate logs earlier than ' + daysToMaintain + 'd.');\n }\n } catch (e) {\n util.throwError('failed to keep logs, ' + e.message);\n }\n }\n\n /**\n * delete log database\n * @method clean\n * @static\n */\n static clean() {\n try {\n if (IndexedDBLogger.status !== super.STATUS.INITED) {\n return IndexedDBLogger._pool.push(() => IndexedDBLogger.clean());\n }\n\n // database can be removed only after all connections are closed\n IndexedDBLogger.db.close();\n let request = window.indexedDB.deleteDatabase(IndexedDBLogger._database);\n request.onerror = event => util.throwError(event.target.error);\n /* eslint no-unused-vars: \"off\" */\n request.onsuccess = event => {\n delete IndexedDBLogger.status;\n delete IndexedDBLogger.db;\n };\n } catch (e) {\n util.throwError('failed to cleanup logs, ' + e.message);\n }\n }\n\n /**\n * get internal transaction store\n * @method _getTransactionStore\n * @private\n * @static\n * @param {String} mode - transaction mode\n * @return {Object} - internal object store\n */\n static _getTransactionStore(mode) {\n try {\n if (IndexedDBLogger.db) {\n let transaction = IndexedDBLogger.db.transaction(['logs'], mode || READ_WRITE);\n transaction.onerror = event => util.throwError(event.target.error);\n return transaction.objectStore('logs');\n }\n else {\n util.throwError('log database is not created or connections are closed, considering init it.');\n }\n } catch (e) {\n util.throwError('failed to generate new transaction, ' + e.message);\n return false;\n }\n }\n\n /**\n * detect support situation\n * @prop {Boolean} support\n */\n static get support() {\n const support = !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange);\n return support;\n }\n}\n","import LoggerInterface from './interface';\nimport * as util from '../lib/util';\n\n/**\n * Localstorage protocol\n * @class LocalStorageLogger\n */\nexport default class LocalStorageLogger extends LoggerInterface {\n /**\n * Localstorage protocol constructor\n * @constructor\n * @param {String} namespace - namespace to use\n */\n constructor(...args) {\n super(...args);\n }\n\n /**\n * add a log record\n * @method _reocrd\n * @private\n * @parma {String} level - log level\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n _record(level, descriptor, data) {\n var logs;\n try {\n logs = window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : [];\n logs.push([\n Date.now(),\n this._namespace,\n level,\n descriptor,\n data\n ]);\n util.debug(this._namespace, level, descriptor, data);\n window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs));\n } catch (e) {\n window.localStorage.removeItem(LocalStorageLogger._database);\n window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([]));\n util.throwError('failed to write, may be localStorage is full, ' + e.message);\n }\n }\n\n /**\n * initialize protocol\n * @method init\n * @static\n * @param {String} database - database name to use\n */\n static init(database) {\n try {\n if (!LocalStorageLogger.support) {\n util.throwError('your platform does not support localstorage protocol.');\n }\n LocalStorageLogger._database = database || 'logline';\n if (!window.localStorage.getItem(LocalStorageLogger._database)) {\n window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([]));\n }\n LocalStorageLogger.status = super.STATUS.INITED;\n } catch (e) {\n util.throwError('failed to init, ' + e.message);\n }\n }\n\n /**\n * get logs in range\n * if from and end is not defined, will fetch full log\n * @method get\n * @static\n * @param {String} from - time from, unix time stamp or falsy\n * @param {String} to - time end, unix time stamp or falsy\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static get(from, to, readyFn) {\n var logs, i;\n try {\n logs = JSON.parse(window.localStorage.getItem(LocalStorageLogger._database));\n\n from = LoggerInterface.transTimeFormat(from);\n to = LoggerInterface.transTimeFormat(to);\n\n for (i = 0; i < logs.length; i++) {\n if ((from && logs[i][0] < from) || (to && logs[i][0] > to)) {\n continue;\n }\n\n logs[i] = {\n time: logs[i][0],\n namespace: logs[i][1],\n level: logs[i][2],\n descriptor: logs[i][3],\n data: logs[i][4]\n };\n }\n readyFn(logs);\n } catch (e) {\n util.throwError('failed to get, ' + e.message);\n readyFn([]);\n }\n }\n\n /**\n * clean logs = keep limited logs\n * @method keep\n * @static\n * @param {Number} daysToMaintain - keep logs within days\n */\n static keep(daysToMaintain) {\n var logs;\n try {\n logs = !daysToMaintain ? [] : (window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []).filter(log => {\n return log.time >= (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);\n });\n window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs));\n } catch (e) {\n util.throwError('failed to keep, ' + e.message);\n }\n }\n\n /**\n * delete log database\n * @method clean\n * @static\n */\n static clean() {\n try {\n delete LocalStorageLogger.status;\n window.localStorage.removeItem(LocalStorageLogger._database);\n } catch (e) {\n util.throwError('failed to clean, ' + e.message);\n }\n }\n\n /**\n * detect support situation\n * @prop {Boolean} support\n */\n static get support() {\n return 'localStorage' in window;\n }\n}\n","import LoggerInterface from './interface';\nimport Pool from '../lib/pool';\nimport * as util from '../lib/util';\n\n/**\n * Websql protocol\n * @class WebsqlLogger\n */\nexport default class WebsqlLogger extends LoggerInterface {\n /**\n * Websql logline constructor\n * @constructor\n * @param {String} namespace - namespace to use\n */\n constructor(...args) {\n super(...args);\n }\n\n /**\n * add a log record\n * @method _reocrd\n * @private\n * @parma {String} level - log level\n * @param {String} descriptor - to speed up search and improve understanding\n * @param {Mixed} [data] - additional data\n */\n _record(level, descriptor, data) {\n if (WebsqlLogger.status !== LoggerInterface.STATUS.INITED) {\n WebsqlLogger._pool.push(() => this._record(level, descriptor, data));\n if (WebsqlLogger.status !== LoggerInterface.STATUS.INITING) {\n WebsqlLogger.init();\n }\n return;\n }\n\n try {\n util.debug(this._namespace, level, descriptor, data);\n WebsqlLogger._db.transaction(tx => {\n tx.executeSql(\n 'INSERT INTO logs (time, namespace, level, descriptor, data) VALUES(?, ?, ?, ? ,?)',\n [Date.now(), this._namespace, level, descriptor, (data === undefined || data === '') ? '' : (JSON.stringify(data) || '')],\n () => {/* empty func */},\n (tx, e) => { util.throwError('write error, ' + e.message); }\n );\n });\n } catch (e) { util.throwError('error inserting record, ' + e.message); }\n }\n\n /**\n * initialize protocol\n * @method init\n * @static\n * @param {String} database - database name to use\n */\n static init(database) {\n if (!WebsqlLogger.support) {\n util.throwError(new Error('your platform does not support websql protocol.'));\n }\n\n if (WebsqlLogger.status) {\n return false;\n }\n\n WebsqlLogger._pool = WebsqlLogger._pool || new Pool();\n WebsqlLogger._database = database || 'logline';\n WebsqlLogger.status = super.STATUS.INITING;\n\n try {\n WebsqlLogger._db = window.openDatabase(WebsqlLogger._database, '1.0', 'cats loves logs', 4.85 * 1024 * 1024);\n WebsqlLogger._db.transaction(tx => {\n tx.executeSql(\n 'CREATE TABLE IF NOT EXISTS logs (time, namespace, level, descriptor, data)', [],\n () => {\n WebsqlLogger.status = super.STATUS.INITED;\n WebsqlLogger._pool.consume();\n },\n (tx, e) => {\n WebsqlLogger.status = super.STATUS.FAILED;\n util.throwError('unable to create table, ' + e.message);\n }\n );\n });\n } catch (e) { util.throwError('unable to init log database, ' + e.message); }\n }\n\n /**\n * get logs in range\n * if from and end is not defined, will fetch full log\n * @method get\n * @static\n * @param {String} from - time from, unix time stamp or falsy\n * @param {String} to - time end, unix time stamp or falsy\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static get(from, to, readyFn) {\n if (WebsqlLogger.status !== super.STATUS.INITED) {\n return WebsqlLogger._pool.push(() => WebsqlLogger.get(from, to, readyFn));\n }\n\n from = LoggerInterface.transTimeFormat(from);\n to = LoggerInterface.transTimeFormat(to);\n\n try {\n WebsqlLogger._db.transaction(function(tx) {\n tx.executeSql(\n 'SELECT * FROM logs ORDER BY time DESC', [],\n (tx, res) => {\n var logs = [], line, index = res.rows.length, item;\n while (--index >= 0) {\n item = res.rows.item(index);\n if ((from && item.time < from) || (to && item.time > to)) {\n continue;\n }\n\n // in some devices, properties are configureable: false, writable: false\n // we need deep copy\n line = JSON.parse(JSON.stringify(item));\n // incase data is an object, not a string\n try { line.data = JSON.parse(line.data); }\n catch (e) {/* leave line.data as it be */}\n logs.push(line);\n }\n readyFn(logs);\n },\n (tx, e) => { util.throwError(e.message); }\n );\n });\n } catch (e) { util.throwError('unable to collect logs from database.'); }\n }\n\n /**\n * clean logs = keep limited logs\n * @method keep\n * @static\n * @param {Number} daysToMaintain - keep logs within days\n */\n static keep(daysToMaintain) {\n if (WebsqlLogger.status !== super.STATUS.INITED) {\n return WebsqlLogger._pool.push(() => WebsqlLogger.keep(daysToMaintain));\n }\n\n try {\n WebsqlLogger._db.transaction(function(tx) {\n if (daysToMaintain) {\n tx.executeSql(\n 'DELETE FROM logs WHERE time < ?',\n [Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000],\n function() { /* empty func */ },\n function(tx, e) { util.throwError(e.message); }\n );\n }\n else {\n tx.executeSql(\n 'DELETE FROM logs', [],\n () => {/* empty func */},\n (tx, e) => { util.throwError(e.message); }\n );\n }\n });\n } catch (e) { util.throwError('unable to clean logs from database.'); }\n }\n\n /**\n * delete log database\n * @method clean\n * @static\n */\n static clean() {\n if (WebsqlLogger.status !== super.STATUS.INITED) {\n WebsqlLogger._pool.push(() => WebsqlLogger.clean());\n return;\n }\n\n try {\n WebsqlLogger._db.transaction(tx => {\n tx.executeSql(\n 'DROP TABLE logs', [],\n () => {\n delete WebsqlLogger.status;\n },\n (tx, e) => { util.throwError(e.message); }\n );\n });\n } catch (e) { util.throwError('unable to clean log database.'); }\n }\n\n /**\n * detect support situation\n * @prop {Boolean} support\n */\n static get support() {\n return 'openDatabase' in window;\n }\n}\n","import Interface from './protocols/interface';\nimport IndexeddbLogger from './protocols/indexeddb';\nimport LocalstorageLogger from './protocols/localstorage';\nimport WebsqlLogger from './protocols/websql';\nimport * as util from './lib/util';\nimport config from './lib/config';\n\n\nclass Logline {\n /**\n * Logline constructor\n * @constructor\n * @param {String} namespace - namespace to use\n * @return {Object Protocol Instance}\n */\n constructor(namespace) {\n if (!(this instanceof Logline)) {\n return new Logline(namespace);\n }\n try {\n Logline._checkProtocol();\n return new Logline._protocol(namespace);\n } catch (e) {\n return new Interface(namespace);\n }\n }\n\n /**\n * change config\n * @method config\n * @param {String|Object} key - config key, or config object\n * @param {Any} [value] - new config value\n * @return {Void}\n */\n static get config() {\n return config;\n }\n\n /**\n * choose a protocol to initialize\n * @method _initProtocol\n * @private\n * @static\n * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL\n * @return {Object} Logline\n */\n static _initProtocol(protocol) {\n Logline._protocol = protocol;\n Logline._protocol.init(Logline._database || 'logline');\n }\n\n /**\n * check protocol\n * if no protocol is chosen, will try to choose an available one automatically\n * if none of the protocols is available, an error will be thrown\n * @method _checkProtocol\n * @private\n * @static\n */\n static _checkProtocol() {\n if (!Logline._protocol) {\n let protocols = Object.keys(Logline.PROTOCOL), protocol;\n while ((protocol = Logline.PROTOCOL[protocols.shift()])) {\n if (protocol.support) {\n Logline._initProtocol(protocol);\n return;\n }\n }\n\n util.throwError('protocols ' + protocols.join(', ').toLowerCase() + ' are not supported on this platform');\n }\n }\n\n /**\n * get logs in range\n * if from and end is not defined, will fetch full log\n * @method get\n * @static\n * @param {String} [from] - time from\n * @param {String} [to] - time end\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static get(from, to, readyFn) {\n Logline._checkProtocol();\n\n switch (arguments.length) {\n case 1:\n readyFn = from;\n from = undefined;\n break;\n case 2:\n readyFn = to;\n to = undefined;\n break;\n case 3:\n default:\n break;\n }\n\n Logline._protocol.get(from, to, readyFn);\n }\n\n /**\n * read all logs\n * @method all\n * @static\n * @param {Function} readyFn - function to call back with logs as parameter\n */\n static all(readyFn) {\n Logline.get(readyFn);\n }\n\n /**\n * clean up logs = keep limited logs\n * @method keep\n * @static\n * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3'\n * @return {Object} Logline\n */\n static keep(daysToMaintain) {\n Logline._checkProtocol();\n Logline._protocol.keep(daysToMaintain);\n return this;\n }\n\n /**\n * delete log database\n * @method clean\n * @static\n * @return {Object} Logline\n */\n static clean() {\n Logline._checkProtocol();\n Logline._protocol.clean();\n return this;\n }\n\n /**\n * choose a protocol\n * @method using\n * @static\n * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL\n * @param {String} [database] - custome database name\n * @return {Object} Logline\n */\n static using(protocol, database) {\n // protocol unavailable is not allowed\n if (-1 === [IndexeddbLogger, LocalstorageLogger, WebsqlLogger].indexOf(protocol)) {\n util.throwError('specialfied protocol ' + (protocol ? (protocol + ' ') : '') + 'is not available');\n }\n\n // once protocol is selected, it shall not be changed during runtime\n if (Logline._protocol) {\n return this;\n }\n\n Logline.database(database || Logline._database);\n Logline._initProtocol(protocol);\n return this;\n }\n\n /**\n * specialfy a custome database name, in case of any conflicts\n * @methd database\n * @static\n * @param {String} name - target database name\n */\n static database(name) {\n Logline._database = name;\n }\n}\n\n// export protocols for modification and mounting\nLogline.PROTOCOL = {\n INDEXEDDB: IndexeddbLogger,\n LOCALSTORAGE: LocalstorageLogger,\n WEBSQL: WebsqlLogger\n};\n\n// export protocol interface for user custom implements\nLogline.INTERFACE = Object.freeze(Interface);\n\n// export Logline env, just like Unix Environment variables\nLogline.env = {\n verbose: true\n};\n\nexport default Logline;\n"],"names":["get","key","store","set","value","changes","Object","prototype","toString","call","throwError","errMessage","console","error","debug","namespace","level","descriptor","data","HAS_CONSOLE","config","verbose","LEVEL_CONSOLE_MAP","toUpperCase","INFO","filterFunction","obj","i","newObj","hasOwnProperty","DEFAULT_CONFIG","babelHelpers.extends","window","Interface","_namespace","args","_record","database","time","relative","test","TypeError","Date","now","replace","from","to","readyFn","daysToMaintain","Pool","_pool","handler","context","push","this","shift","IndexedDBLogger","status","LoggerInterface","STATUS","INITED","_this2","INITING","init","transaction","db","onerror","util","event","target","objectStore","add","FAILED","e","message","support","_database","babelHelpers.get","request","indexedDB","open","onsuccess","result","consume","onupgradeneeded","createObjectStore","autoIncrement","createIndex","unique","transTimeFormat","_getTransactionStore","IDBTransaction","READ_ONLY","getAll","logs","length","openCursor","cursor","continue","keep","range","delete","primaryKey","clear","clean","close","deleteDatabase","mode","IDBKeyRange","LocalStorageLogger","localStorage","getItem","JSON","parse","setItem","stringify","removeItem","filter","log","WebsqlLogger","_db","executeSql","undefined","tx","Error","openDatabase","res","line","item","index","rows","Logline","_checkProtocol","_protocol","protocol","protocols","keys","PROTOCOL","_initProtocol","join","toLowerCase","arguments","IndexeddbLogger","LocalstorageLogger","indexOf","name","INTERFACE","freeze","env"],"mappings":"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"} -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logline 6 | 7 | 8 |

选择你想测试的协议

9 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/indexeddb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Logline - indexeddb 11 | 12 | 13 |

请打开开发者工具的Application标签

14 |
15 |
16 |
namespace:
17 |
18 | level: 19 | 25 |
26 |
descriptor:
27 |
data:
28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/localstorage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Logline - localstorage 11 | 12 | 13 |

请打开开发者工具的Application标签

14 |
15 |
16 |
namespace:
17 |
18 | level: 19 | 25 |
26 |
descriptor:
27 |
data:
28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | function test(protocol) { 2 | 3 | var values = Object.values || function(obj) { 4 | var i, values = []; 5 | for (i in obj) { 6 | if (obj.hasOwnProperty(i)) { 7 | values.push(obj[i]); 8 | } 9 | } 10 | return values; 11 | }; 12 | 13 | if (!Logline) { 14 | document.querySelector('h1').innerHTML = 'Logline is not been properly built.'; 15 | document.querySelector('h1').style.color = 'red'; 16 | throw new Error('Logline is not been properly built.'); 17 | } 18 | 19 | if (!Logline.PROTOCOL[protocol.toUpperCase()]) { 20 | document.querySelector('h1').innerHTML = 'Error: Logline is not build with ' + protocol + ' protocol.'; 21 | document.querySelector('h1').style.color = 'red'; 22 | throw new Error('Logline is not build with ' + protocol + ' protocol.'); 23 | } 24 | 25 | Logline.using(Logline.PROTOCOL[protocol.toUpperCase()]); 26 | 27 | document.querySelector('#add').addEventListener('click', function () { 28 | var form = document.forms.namedItem("form"); 29 | if (!form.namespace.value) { 30 | alert('模块名不可为空'); 31 | return false; 32 | } 33 | if (!form.descriptor.value) { 34 | alert('描述符不可为空'); 35 | return false; 36 | } 37 | var log = new Logline(form.namespace.value); 38 | log[form.level.value](form.descriptor.value, form.data.value); 39 | }); 40 | 41 | document.querySelector('#keep').addEventListener('click', function () { 42 | Logline.keep(0); 43 | }); 44 | 45 | document.querySelector('#clean').addEventListener('click', function () { 46 | Logline.clean(); 47 | document.querySelector('article').innerHTML = ''; 48 | }); 49 | 50 | document.querySelector('form').addEventListener('submit', function(ev) { 51 | ev.preventDefault(); 52 | }); 53 | 54 | setInterval(function () { 55 | if (Logline.PROTOCOL[protocol.toUpperCase()].status === 2) { 56 | Logline.all(function (logs) { 57 | var html = '', 58 | i; 59 | for (i = 0; i < logs.length; i++) { 60 | html += values(logs[i]).join('\t') + '
'; 61 | } 62 | document.querySelector('article').innerHTML = html; 63 | }); 64 | } 65 | }, 200); 66 | } 67 | -------------------------------------------------------------------------------- /example/requirejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Logline with requirejs 11 | 12 | 13 |

请打开开发者工具的Application标签

14 |
15 |
16 |
namespace:
17 |
18 | level: 19 | 25 |
26 |
descriptor:
27 |
data:
28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 | 41 | 42 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | .box { 2 | background: #ffffc0; 3 | padding: .5rem; 4 | margin: 0 0 1rem; 5 | } 6 | 7 | .form > div { 8 | padding: .2rem 0; 9 | } 10 | 11 | .box span { 12 | width: 7rem; 13 | display: inline-block; 14 | } 15 | 16 | .box input { 17 | width: 12rem; 18 | } 19 | 20 | .box input[type="button"], .box input[type="submit"] { 21 | width: initial; 22 | margin-right: .1rem; 23 | } 24 | -------------------------------------------------------------------------------- /example/websql.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Logline - websql 11 | 12 | 13 |

请打开开发者工具的Application标签

14 |
15 |
16 |
namespace:
17 |
18 | level: 19 | 25 |
26 |
descriptor:
27 |
data:
28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var fse = require('fs-extra'); 3 | var path = require('path'); 4 | var pkg = require('./package.json'); 5 | var rename = require('gulp-rename'); 6 | var gulp = require('gulp'); 7 | var handlebars = require('gulp-compile-handlebars'); 8 | var argv = require('minimist')(process.argv.slice(2)); 9 | var colors = require('colors'); 10 | var moment = require('moment'); 11 | 12 | function verbose(log) { 13 | console.log('[' + moment().format('HH:mm:ss').grey + '] ' + log); 14 | } 15 | 16 | 17 | gulp.task('configure', function() { 18 | var protocols = []; 19 | // 修正参数 20 | delete argv._; 21 | // 根据configure参数得到需要构建的协议 22 | if (!Object.keys(argv).length) { 23 | fse.walkSync(path.join(__dirname, '/src/protocols')).map(function(protocol) { 24 | if (/\.js$/.test(protocol)) { 25 | protocol = protocol.match(/(\w+)\.js$/)[1]; 26 | if (protocol !== 'interface') { 27 | protocols.push(protocol); 28 | } 29 | } 30 | }); 31 | } 32 | else { 33 | protocols = Object.keys(argv).map(function(name) { 34 | return name.replace(/^with\-/, ''); 35 | }); 36 | } 37 | 38 | verbose('Using protocols(priority is respected): ' + protocols.join().magenta); 39 | 40 | gulp.src(path.join(__dirname, '/src/configure')) 41 | .pipe(handlebars( 42 | { 43 | protocols: protocols 44 | }, 45 | { 46 | helpers: { 47 | compare: function(v1, v2, options) { 48 | if (v1 - 1 > v2) { 49 | return options.fn(this); 50 | } 51 | else { 52 | return options.inverse(this); 53 | } 54 | }, 55 | upper: function(str, all) { 56 | return all ? str.toUpperCase() : str.replace(/^(\w)(.*)/, function(str, cap, rest) { 57 | return cap.toUpperCase() + rest; 58 | }); 59 | }, 60 | join: function(joiner, postFix, seperator) { 61 | return joiner.map(function(str) { 62 | return (str + postFix).replace(/^\w/, function(cap) { 63 | return cap.toUpperCase(); 64 | }); 65 | }).join(seperator); 66 | } 67 | } 68 | } 69 | )) 70 | .pipe(rename(pkg.name + '.js')) 71 | .pipe(gulp.dest('./src')); 72 | }); 73 | 74 | gulp.task('default', [ 'configure' ]); 75 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PATH := node_modules/.bin:$(PATH) 2 | 3 | default: clean configure dev prod test 4 | 5 | configure: 6 | npm run configure 7 | 8 | dev: 9 | npm run build:dev 10 | 11 | prod: 12 | npm run build:prod 13 | 14 | test: 15 | npm run test 16 | 17 | clean: 18 | @rm -f dist/* 19 | 20 | 21 | .PHONY: default configure dev prod test clean 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logline", 3 | "version": "1.1.5", 4 | "description": "logging the frontend", 5 | "main": "dist/logline.min.js", 6 | "scripts": { 7 | "test": "mocha-headless-chrome -f test/index.html -a no-sandbox", 8 | "configure": "gulp configure", 9 | "dev": "rollup -c build/rollup.dev.js -w", 10 | "build:dev": "rollup -c build/rollup.build.js", 11 | "build:prod": "cross-env NODE_ENV=production rollup -c build/rollup.build.js", 12 | "build": "npm run build:dev && npm run build:prod && npm run test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/latel/logline.git" 17 | }, 18 | "keywords": [ 19 | "log", 20 | "frontend", 21 | "js", 22 | "html", 23 | "websql", 24 | "localstroage" 25 | ], 26 | "author": "latel ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/latel/logline/issues" 30 | }, 31 | "homepage": "https://github.com/latel/logline#readme", 32 | "devDependencies": { 33 | "babel-core": "^6.7.7", 34 | "babel-plugin-external-helpers": "^6.22.0", 35 | "babel-plugin-transform-object-assign": "^6.22.0", 36 | "babel-preset-env": "^1.7.0", 37 | "chai": "^3.5.0", 38 | "colors": "^1.1.2", 39 | "cross-env": "^5.1.3", 40 | "fs-extra": "^1.0.0", 41 | "gulp": "^3.9.1", 42 | "gulp-babel": "^6.1.2", 43 | "gulp-compile-handlebars": "^0.6.1", 44 | "gulp-rename": "^1.2.2", 45 | "html-loader": "^0.4.3", 46 | "install": "^0.12.1", 47 | "jsonfile": "^2.4.0", 48 | "minimist": "^1.2.0", 49 | "mocha": "^2.5.3", 50 | "mocha-headless-chrome": "1.7.1", 51 | "moment": "^2.13.0", 52 | "npm": "^6.4.1", 53 | "rollup": "^0.41.4", 54 | "rollup-plugin-babel": "^2.7.1", 55 | "rollup-plugin-license": "^0.2.0", 56 | "rollup-plugin-livereload": "^0.6.0", 57 | "rollup-plugin-serve": "^0.4.2", 58 | "rollup-plugin-uglify": "^1.0.1", 59 | "rollup-watch": "^4.3.1" 60 | }, 61 | "dependencies": { 62 | "lodash": "^4.17.10", 63 | "lodash-es": "^4.17.10" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/BANNER: -------------------------------------------------------------------------------- 1 | <%=pkg.name%> v<%=pkg.version%> (<%=pkg.homepage%>) 2 | Copyright <%=new Date().getFullYear()%>, <%=pkg.author%> 3 | <%=pkg.license%> license 4 | -------------------------------------------------------------------------------- /src/configure: -------------------------------------------------------------------------------- 1 | import Interface from './protocols/interface'; 2 | {{#each protocols}} 3 | import {{upper this false}}Logger from './protocols/{{this}}'; 4 | {{/each}} 5 | import * as util from './lib/util'; 6 | import config from './lib/config'; 7 | 8 | 9 | class Logline { 10 | /** 11 | * Logline constructor 12 | * @constructor 13 | * @param {String} namespace - namespace to use 14 | * @return {Object Protocol Instance} 15 | */ 16 | constructor(namespace) { 17 | if (!(this instanceof Logline)) { 18 | return new Logline(namespace); 19 | } 20 | try { 21 | Logline._checkProtocol(); 22 | return new Logline._protocol(namespace); 23 | } catch (e) { 24 | return new Interface(namespace); 25 | } 26 | } 27 | 28 | /** 29 | * change config 30 | * @method config 31 | * @param {String|Object} key - config key, or config object 32 | * @param {Any} [value] - new config value 33 | * @return {Void} 34 | */ 35 | static get config() { 36 | return config; 37 | } 38 | 39 | /** 40 | * choose a protocol to initialize 41 | * @method _initProtocol 42 | * @private 43 | * @static 44 | * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL 45 | * @return {Object} Logline 46 | */ 47 | static _initProtocol(protocol) { 48 | Logline._protocol = protocol; 49 | Logline._protocol.init(Logline._database || 'logline'); 50 | } 51 | 52 | /** 53 | * check protocol 54 | * if no protocol is chosen, will try to choose an available one automatically 55 | * if none of the protocols is available, an error will be thrown 56 | * @method _checkProtocol 57 | * @private 58 | * @static 59 | */ 60 | static _checkProtocol() { 61 | if (!Logline._protocol) { 62 | let protocols = Object.keys(Logline.PROTOCOL), protocol; 63 | while ((protocol = Logline.PROTOCOL[protocols.shift()])) { 64 | if (protocol.support) { 65 | Logline._initProtocol(protocol); 66 | return; 67 | } 68 | } 69 | 70 | util.throwError('protocols ' + protocols.join(', ').toLowerCase() + ' are not supported on this platform'); 71 | } 72 | } 73 | 74 | /** 75 | * get logs in range 76 | * if from and end is not defined, will fetch full log 77 | * @method get 78 | * @static 79 | * @param {String} [from] - time from 80 | * @param {String} [to] - time end 81 | * @param {Function} readyFn - function to call back with logs as parameter 82 | */ 83 | static get(from, to, readyFn) { 84 | Logline._checkProtocol(); 85 | 86 | switch (arguments.length) { 87 | case 1: 88 | readyFn = from; 89 | from = undefined; 90 | break; 91 | case 2: 92 | readyFn = to; 93 | to = undefined; 94 | break; 95 | case 3: 96 | default: 97 | break; 98 | } 99 | 100 | Logline._protocol.get(from, to, readyFn); 101 | } 102 | 103 | /** 104 | * read all logs 105 | * @method all 106 | * @static 107 | * @param {Function} readyFn - function to call back with logs as parameter 108 | */ 109 | static all(readyFn) { 110 | Logline.get(readyFn); 111 | } 112 | 113 | /** 114 | * clean up logs = keep limited logs 115 | * @method keep 116 | * @static 117 | * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3' 118 | * @return {Object} Logline 119 | */ 120 | static keep(daysToMaintain) { 121 | Logline._checkProtocol(); 122 | Logline._protocol.keep(daysToMaintain); 123 | return this; 124 | } 125 | 126 | /** 127 | * delete log database 128 | * @method clean 129 | * @static 130 | * @return {Object} Logline 131 | */ 132 | static clean() { 133 | Logline._checkProtocol(); 134 | Logline._protocol.clean(); 135 | return this; 136 | } 137 | 138 | /** 139 | * choose a protocol 140 | * @method using 141 | * @static 142 | * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL 143 | * @param {String} [database] - custome database name 144 | * @return {Object} Logline 145 | */ 146 | static using(protocol, database) { 147 | // protocol unavailable is not allowed 148 | if (-1 === [{{join protocols 'Logger' ', '}}].indexOf(protocol)) { 149 | util.throwError('specialfied protocol ' + (protocol ? (protocol + ' ') : '') + 'is not available'); 150 | } 151 | 152 | // once protocol is selected, it shall not be changed during runtime 153 | if (Logline._protocol) { 154 | return this; 155 | } 156 | 157 | Logline.database(database || Logline._database); 158 | Logline._initProtocol(protocol); 159 | return this; 160 | } 161 | 162 | /** 163 | * specialfy a custome database name, in case of any conflicts 164 | * @methd database 165 | * @static 166 | * @param {String} name - target database name 167 | */ 168 | static database(name) { 169 | Logline._database = name; 170 | } 171 | } 172 | 173 | // export protocols for modification and mounting 174 | Logline.PROTOCOL = { 175 | {{#each protocols}} 176 | {{#compare ../protocols.length @index}} 177 | {{upper this true}}: {{upper this false}}Logger, 178 | {{else}} 179 | {{upper this true}}: {{upper this false}}Logger 180 | {{/compare}} 181 | {{/each}} 182 | }; 183 | 184 | // export protocol interface for user custom implements 185 | Logline.INTERFACE = Object.freeze(Interface); 186 | 187 | // export Logline env, just like Unix Environment variables 188 | Logline.env = { 189 | verbose: true 190 | }; 191 | 192 | export default Logline; 193 | -------------------------------------------------------------------------------- /src/lib/config.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_CONFIG = { 2 | verbose: true 3 | }; 4 | 5 | let store = Object.assign({}, DEFAULT_CONFIG); 6 | 7 | export function get(key) { 8 | return key ? store[key] : store; 9 | } 10 | 11 | export function set(key, value) { 12 | let changes = {}; 13 | if (typeof key === 'string') { 14 | changes[key] = value; 15 | } else if (Object.prototype.toString.call(key) === '[object Object]') { 16 | changes = key; 17 | } 18 | Object.assign(store, changes); 19 | } 20 | 21 | let config = set; 22 | config.set = set; 23 | config.get = get; 24 | 25 | export default config; 26 | -------------------------------------------------------------------------------- /src/lib/pool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pool, for storage of async calling 3 | * @class Pool 4 | */ 5 | export default class Pool { 6 | /** 7 | * Pool constructor 8 | * @constructor 9 | */ 10 | constructor() { 11 | this._pool = []; 12 | } 13 | 14 | /** 15 | * add an procedure 16 | * @method push 17 | * @param {Function} handler - procedure handler 18 | * @param {Object} context - procedure context 19 | */ 20 | push(handler, context) { 21 | handler.context = context; 22 | this._pool.push(handler); 23 | } 24 | 25 | /** 26 | * consume pool 27 | * @method consume 28 | */ 29 | consume() { 30 | var handler; 31 | while ((handler = this._pool.shift())) { 32 | handler.call(handler.context); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/util.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | 3 | const HAS_CONSOLE = window.console; 4 | const LEVEL_CONSOLE_MAP = { 5 | INFO: 'log', 6 | WARN: 'warn', 7 | ERROR: 'error', 8 | CRITICAL: 'error' 9 | }; 10 | 11 | // throw out Errors, with global prefix 'Logline: ' ahead of err.message 12 | export function throwError(errMessage) { 13 | HAS_CONSOLE && console.error('Logline: ' + errMessage); 14 | } 15 | 16 | // print debug info in develper's console 17 | // TODO: if WechatFE/vConsole is detected, will not use %c feature, as it is not well supported 18 | export function debug(namespace, level, descriptor, data) { 19 | if (HAS_CONSOLE && config.get().verbose) { 20 | window.console[LEVEL_CONSOLE_MAP[level.toUpperCase()] || LEVEL_CONSOLE_MAP.INFO](`[${namespace}] ${level.toUpperCase()} ${descriptor}`, data || ''); 21 | } 22 | } 23 | 24 | // filter any function in a object 25 | export function filterFunction(obj) { 26 | var newObj = {}, i; 27 | 28 | if (typeof obj !== 'object') { 29 | return obj; 30 | } 31 | 32 | for (i in obj) { 33 | if (obj.hasOwnProperty(i)) { 34 | if (typeof obj[i] !== 'function') { 35 | newObj[i] = filterFunction(obj[i]); 36 | } 37 | } 38 | } 39 | return newObj; 40 | } 41 | -------------------------------------------------------------------------------- /src/logline.js: -------------------------------------------------------------------------------- 1 | import Interface from './protocols/interface'; 2 | import IndexeddbLogger from './protocols/indexeddb'; 3 | import LocalstorageLogger from './protocols/localstorage'; 4 | import WebsqlLogger from './protocols/websql'; 5 | import * as util from './lib/util'; 6 | import config from './lib/config'; 7 | 8 | 9 | class Logline { 10 | /** 11 | * Logline constructor 12 | * @constructor 13 | * @param {String} namespace - namespace to use 14 | * @return {Object Protocol Instance} 15 | */ 16 | constructor(namespace) { 17 | if (!(this instanceof Logline)) { 18 | return new Logline(namespace); 19 | } 20 | try { 21 | Logline._checkProtocol(); 22 | return new Logline._protocol(namespace); 23 | } catch (e) { 24 | return new Interface(namespace); 25 | } 26 | } 27 | 28 | /** 29 | * change config 30 | * @method config 31 | * @param {String|Object} key - config key, or config object 32 | * @param {Any} [value] - new config value 33 | * @return {Void} 34 | */ 35 | static get config() { 36 | return config; 37 | } 38 | 39 | /** 40 | * choose a protocol to initialize 41 | * @method _initProtocol 42 | * @private 43 | * @static 44 | * @param {Object Protocol Class} protocol - protocol to use, must under Logline.PROTOCOL 45 | * @return {Object} Logline 46 | */ 47 | static _initProtocol(protocol) { 48 | Logline._protocol = protocol; 49 | Logline._protocol.init(Logline._database || 'logline'); 50 | } 51 | 52 | /** 53 | * check protocol 54 | * if no protocol is chosen, will try to choose an available one automatically 55 | * if none of the protocols is available, an error will be thrown 56 | * @method _checkProtocol 57 | * @private 58 | * @static 59 | */ 60 | static _checkProtocol() { 61 | if (!Logline._protocol) { 62 | let protocols = Object.keys(Logline.PROTOCOL), protocol; 63 | while ((protocol = Logline.PROTOCOL[protocols.shift()])) { 64 | if (protocol.support) { 65 | Logline._initProtocol(protocol); 66 | return; 67 | } 68 | } 69 | 70 | util.throwError('protocols ' + protocols.join(', ').toLowerCase() + ' are not supported on this platform'); 71 | } 72 | } 73 | 74 | /** 75 | * get logs in range 76 | * if from and end is not defined, will fetch full log 77 | * @method get 78 | * @static 79 | * @param {String} [from] - time from 80 | * @param {String} [to] - time end 81 | * @param {Function} readyFn - function to call back with logs as parameter 82 | */ 83 | static get(from, to, readyFn) { 84 | Logline._checkProtocol(); 85 | 86 | switch (arguments.length) { 87 | case 1: 88 | readyFn = from; 89 | from = undefined; 90 | break; 91 | case 2: 92 | readyFn = to; 93 | to = undefined; 94 | break; 95 | case 3: 96 | default: 97 | break; 98 | } 99 | 100 | Logline._protocol.get(from, to, readyFn); 101 | } 102 | 103 | /** 104 | * read all logs 105 | * @method all 106 | * @static 107 | * @param {Function} readyFn - function to call back with logs as parameter 108 | */ 109 | static all(readyFn) { 110 | Logline.get(readyFn); 111 | } 112 | 113 | /** 114 | * clean up logs = keep limited logs 115 | * @method keep 116 | * @static 117 | * @param {String} daysToMaintain - specialfy days to keep, support human readable format such as '3d', '.3' 118 | * @return {Object} Logline 119 | */ 120 | static keep(daysToMaintain) { 121 | Logline._checkProtocol(); 122 | Logline._protocol.keep(daysToMaintain); 123 | return this; 124 | } 125 | 126 | /** 127 | * delete log database 128 | * @method clean 129 | * @static 130 | * @return {Object} Logline 131 | */ 132 | static clean() { 133 | Logline._checkProtocol(); 134 | Logline._protocol.clean(); 135 | return this; 136 | } 137 | 138 | /** 139 | * choose a protocol 140 | * @method using 141 | * @static 142 | * @param {Object Protocol Class} protocol - wanted protocol, should be on of Logline.PROTOCOL 143 | * @param {String} [database] - custome database name 144 | * @return {Object} Logline 145 | */ 146 | static using(protocol, database) { 147 | // protocol unavailable is not allowed 148 | if (-1 === [IndexeddbLogger, LocalstorageLogger, WebsqlLogger].indexOf(protocol)) { 149 | util.throwError('specialfied protocol ' + (protocol ? (protocol + ' ') : '') + 'is not available'); 150 | } 151 | 152 | // once protocol is selected, it shall not be changed during runtime 153 | if (Logline._protocol) { 154 | return this; 155 | } 156 | 157 | Logline.database(database || Logline._database); 158 | Logline._initProtocol(protocol); 159 | return this; 160 | } 161 | 162 | /** 163 | * specialfy a custome database name, in case of any conflicts 164 | * @methd database 165 | * @static 166 | * @param {String} name - target database name 167 | */ 168 | static database(name) { 169 | Logline._database = name; 170 | } 171 | } 172 | 173 | // export protocols for modification and mounting 174 | Logline.PROTOCOL = { 175 | INDEXEDDB: IndexeddbLogger, 176 | LOCALSTORAGE: LocalstorageLogger, 177 | WEBSQL: WebsqlLogger 178 | }; 179 | 180 | // export protocol interface for user custom implements 181 | Logline.INTERFACE = Object.freeze(Interface); 182 | 183 | // export Logline env, just like Unix Environment variables 184 | Logline.env = { 185 | verbose: true 186 | }; 187 | 188 | export default Logline; 189 | -------------------------------------------------------------------------------- /src/protocols/indexeddb.js: -------------------------------------------------------------------------------- 1 | import LoggerInterface from './interface'; 2 | import Pool from '../lib/pool'; 3 | import * as util from '../lib/util'; 4 | 5 | const READ_WRITE = 'readwrite'; 6 | 7 | /** 8 | * IndexedDB protocol 9 | * @class IndexedDBLogger 10 | */ 11 | export default class IndexedDBLogger extends LoggerInterface { 12 | /** 13 | * IndexedDB protocol constructor 14 | * @constructor 15 | * @param {String} namespace - namespace to use 16 | */ 17 | constructor(...args) { 18 | super(...args); 19 | } 20 | 21 | /** 22 | * add a log record 23 | * @method _reocrd 24 | * @private 25 | * @parma {String} level - log level 26 | * @param {String} descriptor - to speed up search and improve understanding 27 | * @param {Mixed} [data] - additional data 28 | */ 29 | _record(level, descriptor, data) { 30 | try { 31 | if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITED) { 32 | IndexedDBLogger._pool.push(() => this._record(level, descriptor, data)); 33 | if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITING) { 34 | IndexedDBLogger.init(); 35 | } 36 | return; 37 | } 38 | 39 | util.debug(this._namespace, level, descriptor, data); 40 | let transaction = IndexedDBLogger.db.transaction(['logs'], READ_WRITE || 'readwrite'); 41 | transaction.onerror = event => util.throwError(event.target.error); 42 | 43 | let store = transaction.objectStore('logs'); 44 | // should not contains any function in data 45 | // otherwise 'DOMException: Failed to execute 'add' on 'IDBObjectStore': An object could not be cloned.' will be thrown 46 | let request = store.add({ 47 | time: Date.now(), 48 | level: level, 49 | namespace: this._namespace, 50 | descriptor: descriptor, 51 | data: util.filterFunction(data) 52 | }); 53 | 54 | request.onerror = event => { 55 | IndexedDBLogger.status = LoggerInterface.STATUS.FAILED; 56 | util.throwError(event.target.error); 57 | }; 58 | } catch (e) { 59 | util.throwError('failed to write, ' + e.message); 60 | } 61 | } 62 | 63 | /** 64 | * initialize protocol 65 | * @method init 66 | * @static 67 | * @param {String} database - database name to use 68 | */ 69 | static init(database) { 70 | try { 71 | if (!IndexedDBLogger.support) { 72 | util.throwError('your platform does not support indexeddb protocol.'); 73 | } 74 | 75 | if (IndexedDBLogger.status) { 76 | return false; 77 | } 78 | 79 | IndexedDBLogger._pool = IndexedDBLogger._pool || new Pool(); 80 | IndexedDBLogger._database = database || 'logline'; 81 | IndexedDBLogger.status = super.STATUS.INITING; 82 | 83 | IndexedDBLogger.request = window.indexedDB.open(IndexedDBLogger._database); 84 | IndexedDBLogger.request.onerror = event => util.throwError('protocol indexeddb is prevented.'); 85 | IndexedDBLogger.request.onsuccess = event => { 86 | IndexedDBLogger.db = event.target.result; 87 | IndexedDBLogger.status = super.STATUS.INITED; 88 | IndexedDBLogger._pool.consume(); 89 | // globally handle db request errors 90 | IndexedDBLogger.db.onerror = event => util.throwError(event.target.error); 91 | }; 92 | IndexedDBLogger.request.onupgradeneeded = event => { 93 | // init dabasebase 94 | let db = event.target.result, store = db.createObjectStore('logs', { autoIncrement: true }); 95 | store.createIndex('namespace', 'namespace', { unique: false }); 96 | store.createIndex('level', 'level', { unique: false }); 97 | store.createIndex('descriptor', 'descriptor', { unique: false }); 98 | store.createIndex('data', 'data', { unique: false }); 99 | }; 100 | } catch (e) { 101 | util.throwError('failed init, ' + e.message); 102 | } 103 | } 104 | 105 | /** 106 | * get logs in range 107 | * if from and end is not defined, will fetch full log 108 | * @method get 109 | * @static 110 | * @param {String} [from] - time from, unix time stamp or falsy 111 | * @param {String} [to] - time end, unix time stamp or falsy 112 | * @param {Function} readyFn - function to call back with logs as parameter 113 | */ 114 | static get(from, to, readyFn) { 115 | try { 116 | if (IndexedDBLogger.status !== super.STATUS.INITED) { 117 | return IndexedDBLogger._pool.push(() => IndexedDBLogger.get(from, to, readyFn)); 118 | } 119 | 120 | from = LoggerInterface.transTimeFormat(from); 121 | to = LoggerInterface.transTimeFormat(to); 122 | 123 | let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY); 124 | if (!store) { 125 | return readyFn([]); 126 | } 127 | 128 | // IDBObjectStore.getAll is a non-standard API 129 | if (store.getAll) { 130 | let result, logs = []; 131 | store.getAll().onsuccess = event => { 132 | result = event.target.result; 133 | for (let i = 0; i < result.length; i++) { 134 | if ((from && result[i].time < from) || (to && result[i].time > to)) { 135 | continue; 136 | } 137 | logs.push(result[i]); 138 | } 139 | readyFn(logs); 140 | }; 141 | } else { 142 | let request = store.openCursor(), logs = []; 143 | request.onsuccess = event => { 144 | var cursor = event.target.result; 145 | if (cursor) { 146 | if ((from && cursor.value.time < from) || (to && cursor.value.time > to)) { 147 | return cursor.continue(); 148 | } 149 | 150 | logs.push({ 151 | time: cursor.value.time, 152 | level: cursor.value.level, 153 | namespace: cursor.value.namespace, 154 | descriptor: cursor.value.descriptor, 155 | data: cursor.value.data 156 | }); 157 | cursor.continue(); 158 | } 159 | else { 160 | readyFn(logs); 161 | } 162 | }; 163 | } 164 | } catch (e) { 165 | util.throwError('failed to get logs, ' + e.message); 166 | } 167 | } 168 | 169 | /** 170 | * clean logs = keep limited logs 171 | * @method keep 172 | * @static 173 | * @param {Number} daysToMaintain - keep logs within days 174 | */ 175 | static keep(daysToMaintain) { 176 | try { 177 | if (IndexedDBLogger.status !== super.STATUS.INITED) { 178 | return IndexedDBLogger._pool.push(() => IndexedDBLogger.keep(daysToMaintain)); 179 | } 180 | 181 | let store = IndexedDBLogger._getTransactionStore(READ_WRITE); 182 | if (!store) { 183 | return false; 184 | } 185 | if (!daysToMaintain) { 186 | let request = store.clear().onerror = event => util.throwError(event.target.error); 187 | } 188 | else { 189 | let range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000); 190 | let request = store.openCursor(); 191 | request.onsuccess = event => { 192 | let cursor = event.target.result; 193 | if (cursor && cursor.value.time < range) { 194 | store.delete(cursor.primaryKey); 195 | cursor.continue(); 196 | } 197 | }; 198 | request.onerror = event => util.throwError('unable to locate logs earlier than ' + daysToMaintain + 'd.'); 199 | } 200 | } catch (e) { 201 | util.throwError('failed to keep logs, ' + e.message); 202 | } 203 | } 204 | 205 | /** 206 | * delete log database 207 | * @method clean 208 | * @static 209 | */ 210 | static clean() { 211 | try { 212 | if (IndexedDBLogger.status !== super.STATUS.INITED) { 213 | return IndexedDBLogger._pool.push(() => IndexedDBLogger.clean()); 214 | } 215 | 216 | // database can be removed only after all connections are closed 217 | IndexedDBLogger.db.close(); 218 | let request = window.indexedDB.deleteDatabase(IndexedDBLogger._database); 219 | request.onerror = event => util.throwError(event.target.error); 220 | /* eslint no-unused-vars: "off" */ 221 | request.onsuccess = event => { 222 | delete IndexedDBLogger.status; 223 | delete IndexedDBLogger.db; 224 | }; 225 | } catch (e) { 226 | util.throwError('failed to cleanup logs, ' + e.message); 227 | } 228 | } 229 | 230 | /** 231 | * get internal transaction store 232 | * @method _getTransactionStore 233 | * @private 234 | * @static 235 | * @param {String} mode - transaction mode 236 | * @return {Object} - internal object store 237 | */ 238 | static _getTransactionStore(mode) { 239 | try { 240 | if (IndexedDBLogger.db) { 241 | let transaction = IndexedDBLogger.db.transaction(['logs'], mode || READ_WRITE); 242 | transaction.onerror = event => util.throwError(event.target.error); 243 | return transaction.objectStore('logs'); 244 | } 245 | else { 246 | util.throwError('log database is not created or connections are closed, considering init it.'); 247 | } 248 | } catch (e) { 249 | util.throwError('failed to generate new transaction, ' + e.message); 250 | return false; 251 | } 252 | } 253 | 254 | /** 255 | * detect support situation 256 | * @prop {Boolean} support 257 | */ 258 | static get support() { 259 | const support = !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange); 260 | return support; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/protocols/interface.js: -------------------------------------------------------------------------------- 1 | import * as util from '../lib/util'; 2 | 3 | /** 4 | * Logline Interface 5 | * @class Interface 6 | */ 7 | export default class Interface { 8 | /** 9 | * Logline constructor 10 | * @constructor 11 | * @param {String} namespace - namespace to use 12 | */ 13 | constructor(namespace) { 14 | this._namespace = namespace; 15 | } 16 | 17 | /** 18 | * add a log record 19 | * @method _reocrd 20 | * @private 21 | * @parma {String} level - log level 22 | * @param {String} descriptor - to speed up search and improve understanding 23 | * @param {Mixed} [data] - additional data 24 | */ 25 | _record(level, descriptor, data) { 26 | util.throwError('method _record is not implemented.'); 27 | } 28 | 29 | /** 30 | * add a level-info record 31 | * @method info 32 | * @param {String} descriptor - to speed up search and improve understanding 33 | * @param {Mixed} [data] - additional data 34 | */ 35 | info(...args) { 36 | this._record('info', ...args); 37 | } 38 | 39 | /** 40 | * add a level-warn record 41 | * @method warn 42 | * @param {String} descriptor - to speed up search and improve understanding 43 | * @param {Mixed} [data] - additional data 44 | */ 45 | warn(...args) { 46 | this._record('warn', ...args); 47 | } 48 | 49 | /** 50 | * add a level-error record 51 | * @method error 52 | * @param {String} descriptor - to speed up search and improve understanding 53 | * @param {Mixed} [data] - additional data 54 | */ 55 | error(...args) { 56 | this._record('error', ...args); 57 | } 58 | 59 | /** 60 | * add a level-critical record 61 | * @method critical 62 | * @param {String} descriptor - to speed up search and improve understanding 63 | * @param {Mixed} [data] - additional data 64 | */ 65 | critical(...args) { 66 | this._record('critical', ...args); 67 | } 68 | 69 | /** 70 | * initialize protocol 71 | * @method init 72 | * @static 73 | * @param {String} database - database name to use 74 | */ 75 | static init(database) { 76 | return true; 77 | } 78 | 79 | /** 80 | * transform human readable time string, such as '3d', '.3' and '1.2' into Unix timestamp 81 | * the default relative time is Date.now(), if no second parameter is provided 82 | * @method transTimeFormat 83 | * @static 84 | * @param {String} time - time string to transform 85 | * @param {Number} [relative] - relative time to compare, default Date.now() 86 | * @return {Number|NaN} timestamp transformed 87 | */ 88 | static transTimeFormat(time, relative) { 89 | // if falsy value or timestamp already, pass it through directly, 90 | if (!time || /^\d{13}$/.test(time)) { 91 | return +time; 92 | } 93 | // incase relative time isn't unix timestamp format, 94 | // neither a falsy value which will turned out to be Date.now() 95 | if (relative && !/^\d{13}$/.test(relative)) { 96 | throw new TypeError('relative time should be standard unix timestamp'); 97 | } 98 | 99 | return (relative || Date.now()) - time.replace(/d$/, '') * 24 * 3600 * 1000; 100 | } 101 | 102 | /** 103 | * get logs in range 104 | * if from and end is not defined, will fetch full log 105 | * @method get 106 | * @static 107 | * @param {String} from - time from, unix timestamp 108 | * @param {String} to - time end, unix timestamp 109 | * @param {Function} readyFn - function to call back with logs as parameter 110 | */ 111 | static get(from, to, readyFn) { 112 | util.throwError('method get is not implemented.'); 113 | } 114 | 115 | /** 116 | * clean logs = keep limited logs 117 | * @method keep 118 | * @static 119 | * @param {Number} daysToMaintain - keep logs within days 120 | */ 121 | static keep(daysToMaintain) { 122 | util.throwError('method keep is not implemented.'); 123 | } 124 | 125 | /** 126 | * delete log database 127 | * @method clean 128 | * @static 129 | */ 130 | static clean() { 131 | util.throwError('method clean is not implemented.'); 132 | } 133 | 134 | /** 135 | * protocol status map 136 | * @prop {Object} STATUS 137 | */ 138 | static get STATUS() { 139 | return { 140 | INITING: 1, 141 | INITED: 2, 142 | FAILED: 4 143 | }; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/protocols/localstorage.js: -------------------------------------------------------------------------------- 1 | import LoggerInterface from './interface'; 2 | import * as util from '../lib/util'; 3 | 4 | /** 5 | * Localstorage protocol 6 | * @class LocalStorageLogger 7 | */ 8 | export default class LocalStorageLogger extends LoggerInterface { 9 | /** 10 | * Localstorage protocol constructor 11 | * @constructor 12 | * @param {String} namespace - namespace to use 13 | */ 14 | constructor(...args) { 15 | super(...args); 16 | } 17 | 18 | /** 19 | * add a log record 20 | * @method _reocrd 21 | * @private 22 | * @parma {String} level - log level 23 | * @param {String} descriptor - to speed up search and improve understanding 24 | * @param {Mixed} [data] - additional data 25 | */ 26 | _record(level, descriptor, data) { 27 | var logs; 28 | try { 29 | logs = window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []; 30 | logs.push([ 31 | Date.now(), 32 | this._namespace, 33 | level, 34 | descriptor, 35 | data 36 | ]); 37 | util.debug(this._namespace, level, descriptor, data); 38 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs)); 39 | } catch (e) { 40 | window.localStorage.removeItem(LocalStorageLogger._database); 41 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([])); 42 | util.throwError('failed to write, may be localStorage is full, ' + e.message); 43 | } 44 | } 45 | 46 | /** 47 | * initialize protocol 48 | * @method init 49 | * @static 50 | * @param {String} database - database name to use 51 | */ 52 | static init(database) { 53 | try { 54 | if (!LocalStorageLogger.support) { 55 | util.throwError('your platform does not support localstorage protocol.'); 56 | } 57 | LocalStorageLogger._database = database || 'logline'; 58 | if (!window.localStorage.getItem(LocalStorageLogger._database)) { 59 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify([])); 60 | } 61 | LocalStorageLogger.status = super.STATUS.INITED; 62 | } catch (e) { 63 | util.throwError('failed to init, ' + e.message); 64 | } 65 | } 66 | 67 | /** 68 | * get logs in range 69 | * if from and end is not defined, will fetch full log 70 | * @method get 71 | * @static 72 | * @param {String} from - time from, unix time stamp or falsy 73 | * @param {String} to - time end, unix time stamp or falsy 74 | * @param {Function} readyFn - function to call back with logs as parameter 75 | */ 76 | static get(from, to, readyFn) { 77 | var logs, i; 78 | try { 79 | logs = JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)); 80 | 81 | from = LoggerInterface.transTimeFormat(from); 82 | to = LoggerInterface.transTimeFormat(to); 83 | 84 | for (i = 0; i < logs.length; i++) { 85 | if ((from && logs[i][0] < from) || (to && logs[i][0] > to)) { 86 | continue; 87 | } 88 | 89 | logs[i] = { 90 | time: logs[i][0], 91 | namespace: logs[i][1], 92 | level: logs[i][2], 93 | descriptor: logs[i][3], 94 | data: logs[i][4] 95 | }; 96 | } 97 | readyFn(logs); 98 | } catch (e) { 99 | util.throwError('failed to get, ' + e.message); 100 | readyFn([]); 101 | } 102 | } 103 | 104 | /** 105 | * clean logs = keep limited logs 106 | * @method keep 107 | * @static 108 | * @param {Number} daysToMaintain - keep logs within days 109 | */ 110 | static keep(daysToMaintain) { 111 | var logs; 112 | try { 113 | logs = !daysToMaintain ? [] : (window.localStorage.getItem(LocalStorageLogger._database) ? JSON.parse(window.localStorage.getItem(LocalStorageLogger._database)) : []).filter(log => { 114 | return log.time >= (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000); 115 | }); 116 | window.localStorage.setItem(LocalStorageLogger._database, JSON.stringify(logs)); 117 | } catch (e) { 118 | util.throwError('failed to keep, ' + e.message); 119 | } 120 | } 121 | 122 | /** 123 | * delete log database 124 | * @method clean 125 | * @static 126 | */ 127 | static clean() { 128 | try { 129 | delete LocalStorageLogger.status; 130 | window.localStorage.removeItem(LocalStorageLogger._database); 131 | } catch (e) { 132 | util.throwError('failed to clean, ' + e.message); 133 | } 134 | } 135 | 136 | /** 137 | * detect support situation 138 | * @prop {Boolean} support 139 | */ 140 | static get support() { 141 | return 'localStorage' in window; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/protocols/websql.js: -------------------------------------------------------------------------------- 1 | import LoggerInterface from './interface'; 2 | import Pool from '../lib/pool'; 3 | import * as util from '../lib/util'; 4 | 5 | /** 6 | * Websql protocol 7 | * @class WebsqlLogger 8 | */ 9 | export default class WebsqlLogger extends LoggerInterface { 10 | /** 11 | * Websql logline constructor 12 | * @constructor 13 | * @param {String} namespace - namespace to use 14 | */ 15 | constructor(...args) { 16 | super(...args); 17 | } 18 | 19 | /** 20 | * add a log record 21 | * @method _reocrd 22 | * @private 23 | * @parma {String} level - log level 24 | * @param {String} descriptor - to speed up search and improve understanding 25 | * @param {Mixed} [data] - additional data 26 | */ 27 | _record(level, descriptor, data) { 28 | if (WebsqlLogger.status !== LoggerInterface.STATUS.INITED) { 29 | WebsqlLogger._pool.push(() => this._record(level, descriptor, data)); 30 | if (WebsqlLogger.status !== LoggerInterface.STATUS.INITING) { 31 | WebsqlLogger.init(); 32 | } 33 | return; 34 | } 35 | 36 | try { 37 | util.debug(this._namespace, level, descriptor, data); 38 | WebsqlLogger._db.transaction(tx => { 39 | tx.executeSql( 40 | 'INSERT INTO logs (time, namespace, level, descriptor, data) VALUES(?, ?, ?, ? ,?)', 41 | [Date.now(), this._namespace, level, descriptor, (data === undefined || data === '') ? '' : (JSON.stringify(data) || '')], 42 | () => {/* empty func */}, 43 | (tx, e) => { util.throwError('write error, ' + e.message); } 44 | ); 45 | }); 46 | } catch (e) { util.throwError('error inserting record, ' + e.message); } 47 | } 48 | 49 | /** 50 | * initialize protocol 51 | * @method init 52 | * @static 53 | * @param {String} database - database name to use 54 | */ 55 | static init(database) { 56 | if (!WebsqlLogger.support) { 57 | util.throwError(new Error('your platform does not support websql protocol.')); 58 | } 59 | 60 | if (WebsqlLogger.status) { 61 | return false; 62 | } 63 | 64 | WebsqlLogger._pool = WebsqlLogger._pool || new Pool(); 65 | WebsqlLogger._database = database || 'logline'; 66 | WebsqlLogger.status = super.STATUS.INITING; 67 | 68 | try { 69 | WebsqlLogger._db = window.openDatabase(WebsqlLogger._database, '1.0', 'cats loves logs', 4.85 * 1024 * 1024); 70 | WebsqlLogger._db.transaction(tx => { 71 | tx.executeSql( 72 | 'CREATE TABLE IF NOT EXISTS logs (time, namespace, level, descriptor, data)', [], 73 | () => { 74 | WebsqlLogger.status = super.STATUS.INITED; 75 | WebsqlLogger._pool.consume(); 76 | }, 77 | (tx, e) => { 78 | WebsqlLogger.status = super.STATUS.FAILED; 79 | util.throwError('unable to create table, ' + e.message); 80 | } 81 | ); 82 | }); 83 | } catch (e) { util.throwError('unable to init log database, ' + e.message); } 84 | } 85 | 86 | /** 87 | * get logs in range 88 | * if from and end is not defined, will fetch full log 89 | * @method get 90 | * @static 91 | * @param {String} from - time from, unix time stamp or falsy 92 | * @param {String} to - time end, unix time stamp or falsy 93 | * @param {Function} readyFn - function to call back with logs as parameter 94 | */ 95 | static get(from, to, readyFn) { 96 | if (WebsqlLogger.status !== super.STATUS.INITED) { 97 | return WebsqlLogger._pool.push(() => WebsqlLogger.get(from, to, readyFn)); 98 | } 99 | 100 | from = LoggerInterface.transTimeFormat(from); 101 | to = LoggerInterface.transTimeFormat(to); 102 | 103 | try { 104 | WebsqlLogger._db.transaction(function(tx) { 105 | tx.executeSql( 106 | 'SELECT * FROM logs ORDER BY time DESC', [], 107 | (tx, res) => { 108 | var logs = [], line, index = res.rows.length, item; 109 | while (--index >= 0) { 110 | item = res.rows.item(index); 111 | if ((from && item.time < from) || (to && item.time > to)) { 112 | continue; 113 | } 114 | 115 | // in some devices, properties are configureable: false, writable: false 116 | // we need deep copy 117 | line = JSON.parse(JSON.stringify(item)); 118 | // incase data is an object, not a string 119 | try { line.data = JSON.parse(line.data); } 120 | catch (e) {/* leave line.data as it be */} 121 | logs.push(line); 122 | } 123 | readyFn(logs); 124 | }, 125 | (tx, e) => { util.throwError(e.message); } 126 | ); 127 | }); 128 | } catch (e) { util.throwError('unable to collect logs from database.'); } 129 | } 130 | 131 | /** 132 | * clean logs = keep limited logs 133 | * @method keep 134 | * @static 135 | * @param {Number} daysToMaintain - keep logs within days 136 | */ 137 | static keep(daysToMaintain) { 138 | if (WebsqlLogger.status !== super.STATUS.INITED) { 139 | return WebsqlLogger._pool.push(() => WebsqlLogger.keep(daysToMaintain)); 140 | } 141 | 142 | try { 143 | WebsqlLogger._db.transaction(function(tx) { 144 | if (daysToMaintain) { 145 | tx.executeSql( 146 | 'DELETE FROM logs WHERE time < ?', 147 | [Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000], 148 | function() { /* empty func */ }, 149 | function(tx, e) { util.throwError(e.message); } 150 | ); 151 | } 152 | else { 153 | tx.executeSql( 154 | 'DELETE FROM logs', [], 155 | () => {/* empty func */}, 156 | (tx, e) => { util.throwError(e.message); } 157 | ); 158 | } 159 | }); 160 | } catch (e) { util.throwError('unable to clean logs from database.'); } 161 | } 162 | 163 | /** 164 | * delete log database 165 | * @method clean 166 | * @static 167 | */ 168 | static clean() { 169 | if (WebsqlLogger.status !== super.STATUS.INITED) { 170 | WebsqlLogger._pool.push(() => WebsqlLogger.clean()); 171 | return; 172 | } 173 | 174 | try { 175 | WebsqlLogger._db.transaction(tx => { 176 | tx.executeSql( 177 | 'DROP TABLE logs', [], 178 | () => { 179 | delete WebsqlLogger.status; 180 | }, 181 | (tx, e) => { util.throwError(e.message); } 182 | ); 183 | }); 184 | } catch (e) { util.throwError('unable to clean log database.'); } 185 | } 186 | 187 | /** 188 | * detect support situation 189 | * @prop {Boolean} support 190 | */ 191 | static get support() { 192 | return 'openDatabase' in window; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | #mocha .test .html-error { 140 | overflow: auto; 141 | color: black; 142 | line-height: 1.5; 143 | display: block; 144 | float: left; 145 | clear: left; 146 | font: 12px/1.5 monaco, monospace; 147 | margin: 5px; 148 | padding: 15px; 149 | border: 1px solid #eee; 150 | max-width: 85%; /*(1)*/ 151 | max-width: -webkit-calc(100% - 42px); 152 | max-width: -moz-calc(100% - 42px); 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | max-height: 300px; 155 | word-wrap: break-word; 156 | border-bottom-color: #ddd; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-box-shadow: 0 1px 3px #eee; 159 | box-shadow: 0 1px 3px #eee; 160 | -webkit-border-radius: 3px; 161 | -moz-border-radius: 3px; 162 | border-radius: 3px; 163 | } 164 | 165 | #mocha .test .html-error pre.error { 166 | border: none; 167 | -webkit-border-radius: 0; 168 | -moz-border-radius: 0; 169 | border-radius: 0; 170 | -webkit-box-shadow: 0; 171 | -moz-box-shadow: 0; 172 | box-shadow: 0; 173 | padding: 0; 174 | margin: 0; 175 | margin-top: 18px; 176 | max-height: none; 177 | } 178 | 179 | /** 180 | * (1): approximate for browsers not supporting calc 181 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 182 | * ^^ seriously 183 | */ 184 | #mocha .test pre { 185 | display: block; 186 | float: left; 187 | clear: left; 188 | font: 12px/1.5 monaco, monospace; 189 | margin: 5px; 190 | padding: 15px; 191 | border: 1px solid #eee; 192 | max-width: 85%; /*(1)*/ 193 | max-width: -webkit-calc(100% - 42px); 194 | max-width: -moz-calc(100% - 42px); 195 | max-width: calc(100% - 42px); /*(2)*/ 196 | word-wrap: break-word; 197 | border-bottom-color: #ddd; 198 | -webkit-box-shadow: 0 1px 3px #eee; 199 | -moz-box-shadow: 0 1px 3px #eee; 200 | box-shadow: 0 1px 3px #eee; 201 | -webkit-border-radius: 3px; 202 | -moz-border-radius: 3px; 203 | border-radius: 3px; 204 | } 205 | 206 | #mocha .test h2 { 207 | position: relative; 208 | } 209 | 210 | #mocha .test a.replay { 211 | position: absolute; 212 | top: 3px; 213 | right: 0; 214 | text-decoration: none; 215 | vertical-align: middle; 216 | display: block; 217 | width: 15px; 218 | height: 15px; 219 | line-height: 15px; 220 | text-align: center; 221 | background: #eee; 222 | font-size: 15px; 223 | -webkit-border-radius: 15px; 224 | -moz-border-radius: 15px; 225 | border-radius: 15px; 226 | -webkit-transition:opacity 200ms; 227 | -moz-transition:opacity 200ms; 228 | -o-transition:opacity 200ms; 229 | transition: opacity 200ms; 230 | opacity: 0.3; 231 | color: #888; 232 | } 233 | 234 | #mocha .test:hover a.replay { 235 | opacity: 1; 236 | } 237 | 238 | #mocha-report.pass .test.fail { 239 | display: none; 240 | } 241 | 242 | #mocha-report.fail .test.pass { 243 | display: none; 244 | } 245 | 246 | #mocha-report.pending .test.pass, 247 | #mocha-report.pending .test.fail { 248 | display: none; 249 | } 250 | #mocha-report.pending .test.pass.pending { 251 | display: block; 252 | } 253 | 254 | #mocha-error { 255 | color: #c00; 256 | font-size: 1.5em; 257 | font-weight: 100; 258 | letter-spacing: 1px; 259 | } 260 | 261 | #mocha-stats { 262 | position: fixed; 263 | top: 15px; 264 | right: 10px; 265 | font-size: 12px; 266 | margin: 0; 267 | color: #888; 268 | z-index: 1; 269 | } 270 | 271 | #mocha-stats .progress { 272 | float: right; 273 | padding-top: 0; 274 | 275 | /** 276 | * Set safe initial values, so mochas .progress does not inherit these 277 | * properties from Bootstrap .progress (which causes .progress height to 278 | * equal line height set in Bootstrap). 279 | */ 280 | height: auto; 281 | -webkit-box-shadow: none; 282 | -moz-box-shadow: none; 283 | box-shadow: none; 284 | background-color: initial; 285 | } 286 | 287 | #mocha-stats em { 288 | color: black; 289 | } 290 | 291 | #mocha-stats a { 292 | text-decoration: none; 293 | color: inherit; 294 | } 295 | 296 | #mocha-stats a:hover { 297 | border-bottom: 1px solid #eee; 298 | } 299 | 300 | #mocha-stats li { 301 | display: inline-block; 302 | margin: 0 5px; 303 | list-style: none; 304 | padding-top: 11px; 305 | } 306 | 307 | #mocha-stats canvas { 308 | width: 40px; 309 | height: 40px; 310 | } 311 | 312 | #mocha code .comment { color: #ddd; } 313 | #mocha code .init { color: #2f6fad; } 314 | #mocha code .string { color: #5890ad; } 315 | #mocha code .keyword { color: #8a6343; } 316 | #mocha code .number { color: #2f6fad; } 317 | 318 | @media screen and (max-device-width: 480px) { 319 | #mocha { 320 | margin: 60px 0px; 321 | } 322 | 323 | #mocha #stats { 324 | position: absolute; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var assert = window.chai.assert; 2 | 3 | Logline.config({ 4 | verbose: false 5 | }); 6 | 7 | var readyTimer, repeated, isReady = function(readyFn) { 8 | clearInterval(readyTimer); 9 | repeated = 0; 10 | readyTimer = setInterval(function() { 11 | var ready = 0, i; 12 | if (repeated++ > 20) { 13 | clearInterval(readyTimer); 14 | } 15 | // console.group(); 16 | for (i = 0; i < Object.keys(window.Logline.PROTOCOL).length; i++) { 17 | // console.log(Object.keys(window.Logline.PROTOCOL)[i], window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].status); 18 | if (!window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].status) { 19 | ready++; 20 | } 21 | } 22 | // console.groupEnd(); 23 | if (ready === Object.keys(window.Logline.PROTOCOL).length) { 24 | clearInterval(readyTimer); 25 | readyFn(); 26 | } 27 | }); 28 | }; 29 | 30 | describe('Logline', function() { 31 | before(function() { 32 | // runs before all tests in this block 33 | }); 34 | 35 | after(function() { 36 | delete window.Logline._protocol; 37 | }); 38 | 39 | it('should correctly globally exposed', function(done) { 40 | assert.isFunction(window.Logline, 'should be a constructor'); 41 | assert.isObject(window.Logline.PROTOCOL, 'static interface using'); 42 | assert.isFunction(window.Logline.using, 'static interface using'); 43 | assert.isFunction(window.Logline.keep, 'static interface keep'); 44 | assert.isFunction(window.Logline.clean, 'static interface clean'); 45 | assert.isFunction(window.Logline.get, 'static interface get'); 46 | done(); 47 | }); 48 | 49 | it('should correctly transform human readable time string', function() { 50 | var now = Date.now(); 51 | assert.equal(window.Logline.INTERFACE.transTimeFormat('.3d', now), now - 1000 * 3600 * 24 * .3, 'humand readable string .3d is not correctly transformed'); 52 | assert.equal(window.Logline.INTERFACE.transTimeFormat(now), now, 'unix timestamp is not correctly passed through'); 53 | assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(undefined), 'falsy value `undefined` is not correctly passed through'); 54 | assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(null), 'falsy value `null` is not correctly passed through'); 55 | assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(NaN), 'falsy value `NaN` is not correctly passed through'); 56 | assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(false), 'falsy value `false` is not correctly passed through'); 57 | assert.isNotOk(window.Logline.INTERFACE.transTimeFormat(''), 'falsy value `\'\'` is not correctly pass through'); 58 | }); 59 | 60 | it('should be able to specialfy any available protocols', function(done) { 61 | for (var i = 0; i < Object.keys(window.Logline.PROTOCOL).length; i++) { 62 | if (window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].support) { 63 | window.Logline.using(window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]]); 64 | assert.equal(window.Logline._protocol, window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]], 'protocol should be properly setted'); 65 | window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].clean(); 66 | delete window.Logline._protocol; 67 | } 68 | } 69 | done(); 70 | }); 71 | 72 | it('should be able to create any available protocols instance', function(done) { 73 | isReady(function() { 74 | for (var i = 0; i < Object.keys(window.Logline.PROTOCOL).length; i++) { 75 | if (window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].support) { 76 | window.Logline.using(window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]]); 77 | var logger = new window.Logline('a'); 78 | assert.equal(window.Logline._protocol, window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]], 'protocol should be properly setted'); 79 | assert.instanceOf(logger, window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]], 'log instance should be correctly created'); 80 | window.Logline.PROTOCOL[Object.keys(window.Logline.PROTOCOL)[i]].clean(); 81 | delete window.Logline._protocol; 82 | } 83 | } 84 | done(); 85 | }); 86 | }); 87 | 88 | }); 89 | 90 | if (window.Logline.PROTOCOL.INDEXEDDB && window.Logline.PROTOCOL.INDEXEDDB.support) { 91 | describe('IndexedDBLogger', function() { 92 | before(function() { 93 | // window.Logline.using(window.Logline.PROTOCOL.INDEXEDDB); 94 | // window.Logline.keep(0); 95 | }); 96 | 97 | after(function() { 98 | window.Logline.clean(); 99 | delete window.Logline._protocol; 100 | }); 101 | 102 | 103 | 104 | it('should be able to add and get records', function(done) { 105 | isReady(function() { 106 | window.Logline.using(window.Logline.PROTOCOL.INDEXEDDB); 107 | window.Logline.keep(0); 108 | var logger = new window.Logline('test'); 109 | var randomVars = window.Math.random().toString(36).slice(2, 6); 110 | logger.info('info', randomVars[0]); 111 | // logger.info('warn', randomVars[1]); 112 | // logger.info('error', randomVars[2]); 113 | // logger.info('critical', randomVars[3]); 114 | 115 | window.Logline.all(function(logs) { 116 | assert.isArray(logs, 'logs collect from database'); 117 | assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); 118 | // assert.equal(logs[1].data, randomVars[1], 'record get from database is not the one we stored'); 119 | // assert.equal(logs[2].data, randomVars[2], 'record get from database is not the one we stored'); 120 | // assert.equal(logs[3].data, randomVars[3], 'record get from database is not the one we stored'); 121 | done(); 122 | }); 123 | }); 124 | }); 125 | 126 | it('should be able to keep the logs only we wanted', function(done) { 127 | done(); 128 | }); 129 | 130 | it('should be able to clean up the logs', function(done) { 131 | window.Logline.clean(); 132 | done(); 133 | }); 134 | }); 135 | } 136 | 137 | if (window.Logline.PROTOCOL.WEBSQL && window.Logline.PROTOCOL.WEBSQL.support) { 138 | describe('WebsqlLogger', function() { 139 | before(function() { 140 | // window.Logline.using(window.Logline.PROTOCOL.WEBSQL); 141 | // window.Logline.keep(0); 142 | }); 143 | 144 | afterEach(function() { 145 | window.Logline.clean(); 146 | }); 147 | 148 | after(function() { 149 | delete window.Logline._protocol; 150 | }); 151 | 152 | it('should be able to choose websql protocol and interfaces are accessable', function(done) { 153 | isReady(function() { 154 | window.Logline.using(window.Logline.PROTOCOL.WEBSQL); 155 | window.Logline.keep(0); 156 | var logger = new window.Logline('test'); 157 | assert.isFunction(logger.info, 'instance interface info'); 158 | assert.isFunction(logger.warn, 'instance interface warn'); 159 | assert.isFunction(logger.error, 'instance interface error'); 160 | assert.isFunction(logger.critical, 'instance interface critical'); 161 | 162 | var WebsqlLogger = window.Logline._protocol; 163 | assert.isFunction(WebsqlLogger.init, 'static interface init'); 164 | assert.isFunction(WebsqlLogger.get, 'static interface get'); 165 | assert.isFunction(WebsqlLogger.keep, 'static interface keep'); 166 | assert.isFunction(WebsqlLogger.clean, 'static interface keep'); 167 | done(); 168 | }); 169 | }); 170 | 171 | it('should be able to add and get records', function(done) { 172 | isReady(function() { 173 | window.Logline.using(window.Logline.PROTOCOL.WEBSQL); 174 | window.Logline.keep(0); 175 | var logger = new window.Logline('test'); 176 | var randomVars = window.Math.random().toString(36).slice(2,6); 177 | logger.info('info', randomVars[0]); 178 | logger.info('warn', randomVars[1]); 179 | logger.info('error', randomVars[2]); 180 | logger.info('critical', randomVars[3]); 181 | 182 | window.Logline.all(function(logs) { 183 | assert.isArray(logs, 'logs collect from database'); 184 | assert.isOk(logs.length === 4, 'logs collected is not equal to we acctually stored, which is 4, but get ' + logs.length); 185 | assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); 186 | assert.equal(logs[1].data, randomVars[1], 'record get from database is not the one we stored'); 187 | assert.equal(logs[2].data, randomVars[2], 'record get from database is not the one we stored'); 188 | assert.equal(logs[3].data, randomVars[3], 'record get from database is not the one we stored'); 189 | done(); 190 | }); 191 | }); 192 | }); 193 | 194 | it('should be able to keep the logs only we wanted', function(done) { 195 | isReady(function() { 196 | window.Logline.using(window.Logline.PROTOCOL.WEBSQL); 197 | window.Logline.keep(0); 198 | window.Logline.keep(1); 199 | window.Logline.keep(.1); 200 | window.Logline.keep('a'); 201 | window.Logline.keep({ a: 1 }); 202 | done(); 203 | }); 204 | }); 205 | 206 | it('should be able to clean up the logs', function(done) { 207 | isReady(function() { 208 | window.Logline.using(window.Logline.PROTOCOL.WEBSQL); 209 | window.Logline.keep(0); 210 | window.Logline.clean(); 211 | done(); 212 | }); 213 | }); 214 | 215 | }); 216 | } 217 | 218 | if (window.Logline.PROTOCOL.LOCALSTORAGE && window.Logline.PROTOCOL.LOCALSTORAGE.support) { 219 | describe('LocalStorageLogger', function() { 220 | before(function() { 221 | // window.Logline.using(window.Logline.PROTOCOL.LOCALSTORAGE); 222 | // window.Logline.keep(0); 223 | }); 224 | 225 | afterEach(function() { 226 | window.Logline.clean(); 227 | }); 228 | 229 | after(function() { 230 | delete window.Logline._protocol; 231 | }); 232 | 233 | it('should be able to choose localstorage protocol and interfaces are accessable', function(done) { 234 | isReady(function() { 235 | window.Logline.using(window.Logline.PROTOCOL.LOCALSTORAGE); 236 | window.Logline.keep(0); 237 | var testLogger = new window.Logline('test'); 238 | assert.isFunction(testLogger.info, 'prototype method `info` should be a function'); 239 | assert.isFunction(testLogger.warn, 'prototype method `warn` should be a function'); 240 | assert.isFunction(testLogger.error, 'prototype method `error` should be a function'); 241 | assert.isFunction(testLogger.critical, 'prototype method `critical` should be a function'); 242 | 243 | var LocalStorageLogger = window.Logline._protocol; 244 | assert.isFunction(LocalStorageLogger.init, 'method `init` should be a function'); 245 | assert.isFunction(LocalStorageLogger.get, 'method `get` should be a function'); 246 | assert.isFunction(LocalStorageLogger.keep, 'method `keep` should be a function'); 247 | assert.isFunction(LocalStorageLogger.clean, 'method `clean` should be a function'); 248 | done(); 249 | }); 250 | }); 251 | 252 | it('should be able to add and get records', function(done) { 253 | isReady(function() { 254 | window.Logline.using(window.Logline.PROTOCOL.LOCALSTORAGE); 255 | window.Logline.keep(0); 256 | var logger = new window.Logline('test'); 257 | var randomVars = window.Math.random().toString(36).slice(2, 6); 258 | logger.info('info', randomVars[0]); 259 | logger.info('warn', randomVars[1]); 260 | logger.info('error', randomVars[2]); 261 | logger.info('critical', randomVars[3]); 262 | 263 | window.Logline.all(function(logs) { 264 | assert.isArray(logs, 'logs collect from database should be an array'); 265 | assert.equal(logs.length, 4, 'record length should be 4, currently ' + logs.length); 266 | assert.equal(logs[0].data, randomVars[0], 'record get from database is not the one we stored'); 267 | assert.equal(logs[1].data, randomVars[1], 'record get from database is not the one we stored'); 268 | assert.equal(logs[2].data, randomVars[2], 'record get from database is not the one we stored'); 269 | assert.equal(logs[3].data, randomVars[3], 'record get from database is not the one we stored'); 270 | done(); 271 | }); 272 | }); 273 | }); 274 | 275 | it('should be able to keep the logs only we wanted', function() { 276 | isReady(function() { 277 | window.Logline.using(window.Logline.PROTOCOL.LOCALSTORAGE); 278 | window.Logline.keep(1); 279 | window.Logline.keep(.1); 280 | window.Logline.keep('a'); 281 | window.Logline.keep({ a: 1 }); 282 | }); 283 | }); 284 | 285 | it('should be able to clean up the logs', function() { 286 | isReady(function() { 287 | window.Logline.using(window.Logline.PROTOCOL.LOCALSTORAGE); 288 | window.Logline.clean(); 289 | if (window.localStorage.getItem('logline') !== null) { 290 | throw new Error('log database is not properly removed'); 291 | } 292 | }); 293 | }); 294 | 295 | }); 296 | } 297 | --------------------------------------------------------------------------------