├── .eslintrc ├── template ├── src │ ├── 404 │ │ ├── index.js │ │ ├── index.html │ │ └── style.css │ ├── 500 │ │ ├── index.html │ │ ├── index.js │ │ └── style.css │ ├── images │ │ ├── 404.png │ │ ├── 500.png │ │ └── logo.png │ └── lib │ │ └── prettify.min.js ├── 404.html └── 500.html ├── lib ├── helper.js ├── index.js └── tracer.js ├── .travis.yml ├── test ├── 200.js ├── 404.js └── 500.js ├── .gitignore ├── LICENSE ├── package.json ├── webpack.config.js └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "think" 3 | } -------------------------------------------------------------------------------- /template/src/404/index.js: -------------------------------------------------------------------------------- 1 | import './style.css'; -------------------------------------------------------------------------------- /template/src/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkjs/think-trace/HEAD/template/src/images/404.png -------------------------------------------------------------------------------- /template/src/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkjs/think-trace/HEAD/template/src/images/500.png -------------------------------------------------------------------------------- /template/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkjs/think-trace/HEAD/template/src/images/logo.png -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | exports.htmlentities = function(text) { 2 | return text 3 | .replace(//g, '>') 5 | .replace(/'/g, '''); 6 | }; 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | sudo: false 5 | script: 6 | - "npm test" 7 | after_success: 8 | - 'npm install coveralls && ./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' -------------------------------------------------------------------------------- /test/200.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const test = require('ava'); 3 | const Trace = require('../lib'); 4 | 5 | test('200', async t => { 6 | let ctx = { 7 | res: { 8 | statusCode: 200 9 | } 10 | }; 11 | 12 | let next = () => { 13 | ctx.body = 'Hello World!'; 14 | }; 15 | 16 | await Trace()(ctx, next); 17 | t.true(ctx.body === 'Hello World!'); 18 | }); 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | run.js 39 | .vscode 40 | template/404.js 41 | template/500.js 42 | template/404.css 43 | template/500.css -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ThinkJS 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. 22 | -------------------------------------------------------------------------------- /template/src/404/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Not Found - ThinkJS 6 | 11 | 12 | 13 |
14 |
15 | 19 | 24 |
25 |
26 |
27 |
28 |

Not Found

29 |
30 |
{{errMsg}}
31 |
32 |
33 | 38 | 39 | -------------------------------------------------------------------------------- /template/src/500/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Internal Server Error - ThinkJS 6 | 11 | 12 | 13 |
14 |
15 | 20 | 25 |
26 |
27 |
28 |
29 |

Internal Server Error

30 |
31 | 38 | 39 | -------------------------------------------------------------------------------- /test/404.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const test = require('ava'); 3 | const Trace = require('../lib'); 4 | 5 | const filename = `${__dirname}/notfound.html`; 6 | 7 | test.before('404', () => { 8 | try { 9 | fs.statSync(filename); 10 | fs.unlinkSync(filename); 11 | } catch (e) { 12 | 13 | } finally { 14 | fs.writeFileSync(filename, '{{errMsg}}', {encoding: 'utf-8'}); 15 | } 16 | }); 17 | 18 | test('404', async t => { 19 | t.plan(2); 20 | const ctx = { 21 | path: '/index', 22 | res: { 23 | statusCode: 404 24 | }, 25 | throw(statusCode, msg) { 26 | const err = new Error(msg); 27 | err.status = statusCode; 28 | throw err; 29 | }, 30 | response: { 31 | is() { 32 | return false; 33 | } 34 | } 35 | }; 36 | const next = (instance) => { 37 | return true; 38 | }; 39 | 40 | try { 41 | await Trace({ 42 | templates: {404: filename}, 43 | error() {} 44 | })(ctx, next); 45 | } catch (e) { 46 | 47 | } 48 | 49 | t.is(ctx.body, 'Error: url `/index` not found.'); 50 | 51 | try { 52 | await Trace({ 53 | debug: false, 54 | templates: {404: filename}, 55 | error() {} 56 | })(ctx, next); 57 | } catch (e) { 58 | 59 | } 60 | t.is(ctx.body, ''); 61 | }); 62 | 63 | test.after('404', () => fs.unlinkSync(filename)); 64 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const statuses = require('statuses'); 2 | const helper = require('think-helper'); 3 | const sourceMapSupport = require('source-map-support'); 4 | const Tracer = require('./tracer'); 5 | 6 | module.exports = function(opts, app) { 7 | const tracer = new Tracer(opts); 8 | 9 | // source map support for compiled file 10 | if (opts && opts.sourceMap !== false) { 11 | sourceMapSupport.install(); 12 | } 13 | 14 | let errorCallback; 15 | if (opts && helper.isFunction(opts.error)) { 16 | errorCallback = opts.error; 17 | } else { 18 | errorCallback = console.error.bind(console); 19 | } 20 | 21 | if (app) { 22 | app.think.beforeStartServer(() => tracer.getTemplateContent()); 23 | } 24 | 25 | return (ctx, next) => { 26 | const beforeTrace = app ? Promise.resolve() : tracer.getTemplateContent(); 27 | 28 | return beforeTrace.then(next).then(() => { 29 | if (ctx.res.statusCode !== 404) { 30 | return true; 31 | } 32 | 33 | return ctx.throw(404, `url \`${ctx.path}\` not found.`); 34 | }).catch(err => { 35 | if (errorCallback(err, ctx) === false) { 36 | return Promise.resolve(); 37 | } 38 | 39 | // default to 500 40 | if (typeof err.status !== 'number' || !statuses[err.status]) { 41 | err.status = 500; 42 | } 43 | 44 | // set status to forbidden reset status 200 during set body 45 | ctx.status = err.status; 46 | 47 | return tracer.run(ctx, err); 48 | }); 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "think-trace", 3 | "version": "1.0.18", 4 | "description": "Error trace for thinkjs 3.x", 5 | "main": "./lib", 6 | "scripts": { 7 | "compile": "webpack -p", 8 | "eslint": "eslint ./lib", 9 | "test": "npm run eslint && npm run test-cov", 10 | "test-cov": "nyc ava test/ && nyc report --reporter=html", 11 | "preversion": "npm test", 12 | "postversion": "git push" 13 | }, 14 | "dependencies": { 15 | "source-map-support": "^0.4.18", 16 | "stack-trace": "^0.0.9", 17 | "statuses": "^1.4.0", 18 | "think-helper": "^1.1.3" 19 | }, 20 | "devDependencies": { 21 | "ava": "^0.18.2", 22 | "babel-core": "^6.26.0", 23 | "babel-eslint": "^7.2.3", 24 | "babel-loader": "^6.4.1", 25 | "babel-preset-es2015": "^6.24.1", 26 | "css-loader": "^0.27.3", 27 | "eslint": "^4.13.1", 28 | "eslint-config-think": "^1.0.2", 29 | "extract-text-webpack-plugin": "^2.1.2", 30 | "file-loader": "^0.10.1", 31 | "html-webpack-plugin": "^2.30.1", 32 | "nyc": "^10.3.2", 33 | "raw-loader": "^0.5.1", 34 | "style-loader": "^0.14.1", 35 | "url-loader": "^0.5.9", 36 | "webpack": "^2.7.0", 37 | "webpack-zepto": "0.0.1", 38 | "zepto": "^1.2.0" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/thinkjs/think-trace.git" 43 | }, 44 | "keywords": [ 45 | "stack", 46 | "trace", 47 | "node", 48 | "thinkjs", 49 | "error" 50 | ], 51 | "author": "lizheming", 52 | "license": "MIT", 53 | "bugs": { 54 | "url": "https://github.com/thinkjs/think-trace/issues" 55 | }, 56 | "homepage": "https://github.com/thinkjs/think-trace#readme" 57 | } 58 | -------------------------------------------------------------------------------- /template/src/404/style.css: -------------------------------------------------------------------------------- 1 | body, 2 | div, 3 | dl, 4 | dt, 5 | dd, 6 | ul, 7 | ol, 8 | li, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | pre, 16 | code { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | a { 21 | text-decoration: none; 22 | } 23 | .clearfix { 24 | clear: both; 25 | zoom: 1; 26 | } 27 | .clearfix:after { 28 | clear: both; 29 | content: ''; 30 | display: block; 31 | height: 0; 32 | visibility: hidden; 33 | } 34 | body { 35 | background: #fff; 36 | font: 12px/1.5 \5FAE\8F6F\96C5\9ED1,arial,sans-serif; 37 | } 38 | .wrap { 39 | width: 1000px; 40 | margin: 0 auto; 41 | } 42 | .header { 43 | position: relative; 44 | height: 76px; 45 | background: #ffffff; 46 | border-bottom: 1px solid #dfdfdf; 47 | box-shadow: 0 1px 2px rgba(0,0,0,0.1); 48 | } 49 | .logo { 50 | float: left; 51 | width: 146px; 52 | height: 55px; 53 | margin-top: 10px; 54 | } 55 | .headr { 56 | float: right; 57 | margin-top: 20px; 58 | } 59 | .headr a { 60 | font-size: 16px; 61 | position: relative; 62 | top: 10px; 63 | color: #888; 64 | } 65 | .footer { 66 | border-top: 1px solid #e8e8e8; 67 | margin-top: 40px; 68 | text-align: center; 69 | line-height: 40px; 70 | font-size: 14px; 71 | } 72 | .footer a { 73 | color: #428bca; 74 | } 75 | h1 { 76 | font-weight: normal; 77 | text-align: center; 78 | font-size: 26px; 79 | margin-top: 30px; 80 | color: #666; 81 | line-height: 30px; 82 | height: 30px; 83 | } 84 | .error-msg { 85 | text-align: center; 86 | font-size: 16px; 87 | margin-top: 30px; 88 | color: #666; 89 | min-height: 80px; 90 | word-wrap: break-all; 91 | } 92 | 93 | .wrap-bg { 94 | margin-top:20px; 95 | height:313px; 96 | background:url(../images/404.png) top center no-repeat; 97 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | const base = path.join(__dirname, 'template'); 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | module.exports = { 8 | entry: { 9 | '404': path.join(base, 'src/404'), 10 | '500': path.join(base, 'src/500') 11 | }, 12 | output: { 13 | path: base, 14 | filename: '[name].js' 15 | }, 16 | resolve: { 17 | alias: { 18 | 'zepto': path.join(__dirname, 'node_modules/webpack-zepto/index.js') 19 | } 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | loader: 'babel-loader', 26 | options: { 27 | cacheDirectory: true, 28 | presets: [ 29 | ['es2015', {loose: true, module: false}] 30 | ] 31 | }, 32 | exclude: /node_modules/ 33 | }, 34 | { 35 | test: /\.css?$/, 36 | use: ExtractTextPlugin.extract({ 37 | fallback: 'style-loader', 38 | use: 'css-loader' 39 | }) 40 | }, 41 | { 42 | test: /\.(jpg|png)$/, 43 | use: [ 44 | 'url-loader' 45 | ] 46 | } 47 | ] 48 | }, 49 | plugins: [ 50 | new webpack.ProvidePlugin({ 51 | $: 'zepto' 52 | }), 53 | new ExtractTextPlugin({ 54 | filename: getPath => getPath('[name].css'), 55 | allChunks: true 56 | }), 57 | new HtmlWebpackPlugin({ 58 | template: path.join(base, 'src/404/index.html'), 59 | filename: path.join(base, '404.html'), 60 | chunks: ['404'], 61 | inject: false, 62 | minify: {html5: false} 63 | }), 64 | new HtmlWebpackPlugin({ 65 | template: path.join(base, 'src/500/index.html'), 66 | filename: path.join(base, '500.html'), 67 | chunks: ['500'], 68 | inject: false, 69 | minify: {html5: false} 70 | }) 71 | ] 72 | }; -------------------------------------------------------------------------------- /test/500.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const test = require('ava'); 3 | const Trace = require('../lib'); 4 | 5 | const filename = `${__dirname}/test.html`; 6 | 7 | test.before('500', () => { 8 | try { 9 | fs.statSync(filename); 10 | fs.unlinkSync(filename); 11 | } catch (e) { 12 | 13 | } finally { 14 | fs.writeFileSync(filename, '{{errMsg}}{{error}}', {encoding: 'utf-8'}); 15 | } 16 | }); 17 | 18 | test('500', async t => { 19 | t.plan(2); 20 | 21 | const ctx = { 22 | res: { 23 | statusCode: 200 24 | }, 25 | req: { 26 | 'content-type': 'html;charset=utf-8' 27 | }, 28 | throw(statusCode, msg) { 29 | const err = new Error(msg); 30 | err.status = statusCode; 31 | throw err; 32 | }, 33 | response: { 34 | is() { 35 | return false; 36 | } 37 | } 38 | }; 39 | const next = (instance) => { 40 | throw new Error('normal trace test'); 41 | }; 42 | 43 | try { 44 | await Trace({ 45 | templates: {500: filename} 46 | })(ctx, next); 47 | } catch (e) { 48 | 49 | } 50 | const result = ctx.body; 51 | t.true(result.includes('normal trace test')); 52 | 53 | try { 54 | await Trace({ 55 | debug: false, 56 | templates: {500: filename} 57 | })(ctx, next); 58 | } catch (e) { 59 | 60 | } 61 | 62 | t.is(ctx.body, '[]'); 63 | }); 64 | 65 | test.after('500', () => fs.unlinkSync(filename)); 66 | 67 | test('500 not exist file', async t => { 68 | const ctx = { 69 | res: { 70 | statusCode: 200 71 | }, 72 | response: { 73 | is() { 74 | return false; 75 | } 76 | } 77 | }; 78 | const next = (instance) => { 79 | throw new Error('normal trace test'); 80 | }; 81 | 82 | try { 83 | await Trace({ 84 | templates: { 85 | 404: __dirname + 'notexist.html', 86 | 500: __dirname + 'notexist.html' 87 | } 88 | })(ctx, next); 89 | } catch (e) { 90 | 91 | } 92 | const result = ctx.body; 93 | t.true(result.includes('thinkjs')); 94 | }); 95 | -------------------------------------------------------------------------------- /template/src/500/index.js: -------------------------------------------------------------------------------- 1 | import $ from 'zepto'; 2 | import './style.css'; 3 | 4 | global.App = class App { 5 | constructor(errMsg, stacks) { 6 | this.state = {activeStack: 0}; 7 | this.setState({errMsg, stacks}); 8 | this.bindEvent(); 9 | } 10 | 11 | setState(state) { 12 | for(let i in state) { 13 | this.state[i] = state[i]; 14 | } 15 | this.render(); 16 | } 17 | 18 | bindEvent() { 19 | let that = this; 20 | $('.wrap').on('click', 'li:not(.stack-code)', function() { 21 | that.setState({activeStack: $(this).attr('data-index')/1}); 22 | }); 23 | } 24 | 25 | render() { 26 | let {errMsg, stacks, activeStack} = this.state; 27 | if( !errMsg && !stacks.length ) { 28 | return true; 29 | } 30 | 31 | let html = ` 32 |
${errMsg}
33 | 34 | 55 | `; 56 | $('body>.wrap').html(html); 57 | this._afterRender(); 58 | } 59 | 60 | highlightCurrentLine(_, frame) { 61 | var $frame = $(frame); 62 | var activeLineNumber = $frame.attr('data-activeline'); 63 | var $lines = $frame.find('.linenums li'); 64 | var firstLine = +($lines[0].value); 65 | 66 | var activeLine = activeLineNumber - firstLine; 67 | var preActiveLine = activeLine - 1; 68 | var nextActiveLine = activeLine + 1; 69 | 70 | if( $lines[preActiveLine] ) { $($lines[preActiveLine]).addClass('current'); } 71 | if( $lines[nextActiveLine]) { $($lines[nextActiveLine]).addClass('current'); } 72 | $($lines[activeLine]).addClass('current active'); 73 | } 74 | 75 | _afterRender() { 76 | prettyPrint(); 77 | $('pre').each(this.highlightCurrentLine.bind(this)); 78 | } 79 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # think-trace 2 | 3 | [![npm](https://img.shields.io/npm/v/think-trace.svg?style=flat-square)]() 4 | [![Travis](https://img.shields.io/travis/thinkjs/think-trace.svg?style=flat-square)]() 5 | [![Coveralls](https://img.shields.io/coveralls/thinkjs/think-trace/master.svg?style=flat-square)]() 6 | [![David](https://img.shields.io/david/thinkjs/think-trace.svg?style=flat-square)]() 7 | 8 | think-trace is an error handler for ThinkJS 3.x and koa2. It provides a pretty error web interface that helps you debug your web project. 9 | 10 | ![](https://p1.ssl.qhimg.com/t0105986ac7dfc1c197.png) 11 | 12 | ## Installation 13 | 14 | ``` 15 | npm install think-trace 16 | ``` 17 | 18 | ## How To Use 19 | 20 | 21 | ### Koa2 22 | 23 | ```js 24 | const traceMiddleware = require('think-trace'); 25 | app.use(traceMiddleware({ 26 | sourceMap: false, 27 | error: err => console.error(err) 28 | })); 29 | ``` 30 | 31 | ### ThinkJS3.x 32 | 33 | Modify `src/config/middleware.js`: 34 | 35 | ```js 36 | const trace = require('think-trace'); 37 | 38 | module.exports = [ 39 | { 40 | handle: trace, 41 | options: { 42 | sourceMap: false, 43 | error(err, ctx) { 44 | return console.error(err); 45 | } 46 | } 47 | } 48 | ]; 49 | ``` 50 | 51 | ## Options 52 | 53 | - `sourceMap`: Whether your project has source map support, default is `true`. 54 | - `debug`: Whether show error detail in web, default is `true`. 55 | - `ctxLineNumbers`: How long you want show error line context, default is `10`. 56 | - `contentType`: Due to think-trace can't get content-type while application throw error, you should set content type by yourself by this parameter. Default value is `ctx => 'html';`. You can set json content type like this: 57 | ```js 58 | { 59 | contentType(ctx) { 60 | // All request url starts of /api or request header contains `X-Requested-With: XMLHttpRequest` will output json error 61 | const APIRequest = /^\/api/.test(ctx.request.path); 62 | const AJAXRequest = ctx.is('X-Requested-With', 'XMLHttpRequest'); 63 | 64 | return APIRequest || AJAXRequest ? 'json' : 'html'; 65 | } 66 | } 67 | ``` 68 | - `error`: callback function when catch error, it receives Error object and ctx as parameter. 69 | - `templates`: error status template path, if you want to specific. You can set `templates` as a path string, then module will read all status file named like `404.html`, `502.html` as your customed status page. Or you can set `templates` as an object, for example: 70 | ```js 71 | { 72 | options: { 73 | //basic set as string, then put 404.html, 500.html into error folder 74 | templates: path.join(__dirname, 'error'), 75 | 76 | //customed set as object 77 | templates: { 78 | 404: path.join(__dirname, 'error/404.html'), 79 | 500: path.join(__dirname, 'error/500.html'), 80 | 502: path.join(__dirname, 'error/502.html') 81 | } 82 | } 83 | } 84 | ``` 85 | Also you can set templates as function, function should return error template file path or error template file path object. 86 | 87 | ## Contributing 88 | 89 | Contributions welcome! 90 | 91 | ## License 92 | 93 | [MIT](https://github.com/thinkjs/think-trace/blob/master/LICENSE) 94 | -------------------------------------------------------------------------------- /template/src/500/style.css: -------------------------------------------------------------------------------- 1 | body, 2 | div, 3 | dl, 4 | dt, 5 | dd, 6 | ul, 7 | li, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | pre, 15 | code { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | a { 20 | text-decoration: none; 21 | } 22 | .clearfix { 23 | clear: both; 24 | zoom: 1; 25 | } 26 | .clearfix:after { 27 | clear: both; 28 | content: ''; 29 | display: block; 30 | height: 0; 31 | visibility: hidden; 32 | } 33 | body { 34 | background: #fff; 35 | font: 12px/1.5 "Helvetica Neue", \5FAE\8F6F\96C5\9ED1,helvetica,arial,sans-serif; 36 | color: #131313; 37 | padding: 0; 38 | margin: 0; 39 | max-height: 100%; 40 | text-rendering: optimizeLegibility; 41 | } 42 | .wrap { 43 | width: 1000px; 44 | margin: 0 auto; 45 | } 46 | .header { 47 | position: relative; 48 | height: 76px; 49 | background: #ffffff; 50 | border-bottom: 1px solid #dfdfdf; 51 | box-shadow: 0 1px 2px rgba(0,0,0,0.1); 52 | } 53 | .logo { 54 | float: left; 55 | width: 146px; 56 | height: 55px; 57 | margin-top: 10px; 58 | } 59 | .headr { 60 | float: right; 61 | margin-top: 20px; 62 | } 63 | .headr a { 64 | font-size: 16px; 65 | position: relative; 66 | top: 10px; 67 | color: #888; 68 | } 69 | .footer { 70 | border-top: 1px solid #e8e8e8; 71 | margin-top: 40px; 72 | text-align: center; 73 | line-height: 40px; 74 | font-size: 14px; 75 | } 76 | .footer a { 77 | color: #428bca; 78 | } 79 | h1 { 80 | font-weight: normal; 81 | text-align: center; 82 | font-size: 26px; 83 | margin-top: 30px; 84 | color: #666; 85 | line-height: 30px; 86 | height: 30px; 87 | } 88 | .error-msg { 89 | text-align: center; 90 | font-size: 16px; 91 | margin-top: 30px; 92 | color: #666; 93 | min-height: 80px; 94 | word-wrap: break-all; 95 | } 96 | /* prettify code style 97 | Uses the Doxy theme as a base */ 98 | pre .str, code .str { color: #BCD42A; } /* string */ 99 | pre .kwd, code .kwd { color: #4bb1b1; font-weight: bold; } /* keyword*/ 100 | pre .com, code .com { color: #888; font-weight: bold; } /* comment */ 101 | pre .typ, code .typ { color: #ef7c61; } /* type */ 102 | pre .lit, code .lit { color: #BCD42A; } /* literal */ 103 | pre .pun, code .pun { color: #fff; font-weight: bold; } /* punctuation */ 104 | pre .pln, code .pln { color: #e9e4e5; } /* plaintext */ 105 | pre .tag, code .tag { color: #4bb1b1; } /* html/xml tag */ 106 | pre .htm, code .htm { color: #dda0dd; } /* html tag */ 107 | pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */ 108 | pre .atn, code .atn { color: #ef7c61; font-weight: normal;} /* html/xml attribute name */ 109 | pre .atv, code .atv { color: #bcd42a; } /* html/xml attribute value */ 110 | pre .dec, code .dec { color: #606; } /* decimal */ 111 | pre.prettyprint, code.prettyprint { 112 | font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 113 | background: #333; 114 | color: #e9e4e5; 115 | } 116 | pre.prettyprint { 117 | white-space: pre-wrap; 118 | padding: 10px; 119 | margin: 0; 120 | box-shadow: 0 3px 0 rgba(0, 0, 0, .05), 0 10px 30px rgba(0, 0, 0, .05), inset 0 0 1px 0 rgba(255, 255, 255, .07); 121 | } 122 | 123 | pre.prettyprint a, code.prettyprint a { 124 | text-decoration:none; 125 | } 126 | 127 | .linenums li { 128 | color: #A5A5A5; 129 | } 130 | 131 | .linenums li.current{ 132 | background: rgba(255, 100, 100, .07); 133 | padding-top: 4px; 134 | padding-left: 1px; 135 | } 136 | .linenums li.current.active { 137 | background: rgba(255, 100, 100, .17); 138 | } 139 | .stacks-header { 140 | font-size: 2em; 141 | padding: 50px 0; 142 | word-break: break-all; 143 | font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 144 | } 145 | ul.stacks { 146 | padding:0; 147 | margin: 0; 148 | list-style-type: none; 149 | } 150 | ul.stacks>li { 151 | padding: 14px; 152 | cursor: pointer; 153 | transition: all 0.1s ease; 154 | background: #eeeeee; 155 | } 156 | ul.stacks>li:not(:last-child) { 157 | border-bottom: 1px solid rgba(0, 0, 0, .05); 158 | } 159 | ul.stacks>li:not(.active):hover { 160 | background: #BEE9EA; 161 | } 162 | ul.stacks>li.active { 163 | box-shadow: inset -5px 0 0 0 #4288CE; 164 | color: #4288CE; 165 | } 166 | ul.stacks>li.stack-code { 167 | display: none; 168 | 169 | } 170 | ul.stacks>li.active.animation-hide + .stack-code{ 171 | transform: scaleY(0); 172 | } 173 | ul.stacks>li.active+.stack-code { 174 | display: list-item; 175 | background: #FFF; 176 | border-left: 1px solid #eee; 177 | border-right: 1px solid #eee; 178 | animation: scaleShow; 179 | } 180 | ul.stacks>li:last-child { 181 | border-bottom: 1px solid #eee; 182 | } 183 | @keyframes sacleShow { 184 | from { 185 | transform: scaleY(0); 186 | } 187 | to { 188 | transform: scaleY(1); 189 | } 190 | } 191 | ul.stacks .stack-info { 192 | margin-bottom: 10px; 193 | } 194 | ul.stacks .stack-info .stack-index{ 195 | font-size: 11px; 196 | color: #a29d9d; 197 | background-color: rgba(0, 0, 0, .05); 198 | height: 18px; 199 | width: 18px; 200 | line-height: 18px; 201 | border-radius: 100%; 202 | text-align: center; 203 | display: inline-block; 204 | } 205 | ul.stacks .stack-info .stack-function { 206 | font-size: 14px; 207 | } 208 | ul.stacks .stack-file { 209 | font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 210 | word-wrap: break-word; 211 | color: #a29d9d; 212 | } 213 | .wrap-bg { 214 | margin-top:20px; 215 | height:313px; 216 | background:url(../images/500.png) top center no-repeat; 217 | } -------------------------------------------------------------------------------- /lib/tracer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const helper = require('think-helper'); 4 | const stackTrace = require('stack-trace'); 5 | const { htmlentities } = require('./helper'); 6 | 7 | const readFileAsync = helper.promisify(fs.readFile); 8 | const DEFAULT_404_TEMPLATE = path.join(__dirname, '../template/404.html'); 9 | const DEFAULT_500_TEMPLATE = path.join(__dirname, '../template/500.html'); 10 | 11 | module.exports = class Tracer { 12 | constructor(opts = {}) { 13 | this.ctxLineNumbers = opts.ctxLineNumbers || 10; 14 | this.debug = opts.debug !== undefined ? opts.debug : true; 15 | 16 | if (helper.isFunction(opts.templates)) { 17 | this.templatesPath = opts.templates; 18 | } else { 19 | if (helper.isString(opts.templates)) { 20 | opts.templates = this.readTemplatesPath(opts.templates); 21 | } 22 | this.templatesPath = helper.extend({ 23 | 404: DEFAULT_404_TEMPLATE, 24 | 500: DEFAULT_500_TEMPLATE 25 | }, opts.templates); 26 | } 27 | 28 | this.templates = {}; 29 | this.contentType = opts.contentType; 30 | } 31 | 32 | /** 33 | * get error template path from error directory 34 | * @param {String} dir directory 35 | */ 36 | readTemplatesPath(dir) { 37 | const templates = {}; 38 | try { 39 | fs.readdirSync(dir) 40 | .forEach(file => { 41 | const match = file.match(/^(\d{3})\./); 42 | if (helper.isArray(match)) { 43 | templates[match[1]] = path.join(dir, file); 44 | } 45 | }); 46 | } catch (e) { 47 | console.log(e); // eslint-disable-line no-console 48 | } 49 | return templates; 50 | } 51 | 52 | /** 53 | * get error template file content 54 | */ 55 | async getTemplateContent() { 56 | if (!helper.isEmpty(this.templates)) { 57 | return Promise.resolve(); 58 | } 59 | 60 | let templates = this.templatesPath; 61 | if (helper.isFunction(templates)) { 62 | templates = await this.templatesPath(); 63 | if (helper.isString(templates)) { 64 | templates = this.readTemplatesPath(templates); 65 | } 66 | templates = helper.extend({ 67 | 404: DEFAULT_404_TEMPLATE, 68 | 500: DEFAULT_500_TEMPLATE 69 | }, templates); 70 | } 71 | 72 | const readFilesAsync = Object.keys(templates).map(status => 73 | readFileAsync(templates[status], { encoding: 'utf-8' }) 74 | .catch(() => 75 | readFileAsync(status !== '404' ? DEFAULT_500_TEMPLATE : DEFAULT_404_TEMPLATE, { encoding: 'utf-8' }) 76 | ).then(template => { 77 | this.templates[status] = template; 78 | }) 79 | ); 80 | return Promise.all(readFilesAsync); 81 | } 82 | 83 | /** 84 | * get File content by stack path and lineNumber 85 | * @param {*object} line stack object for every stack 86 | */ 87 | getFile(line) { 88 | const filename = line.getFileName(); 89 | const lineNumber = line.getLineNumber(); 90 | 91 | return readFileAsync(filename, { encoding: 'utf-8' }).then(data => { 92 | let content = data.split('\n'); 93 | const startLineNumber = Math.max(0, lineNumber - this.ctxLineNumbers); 94 | const endLineNumber = Math.min(lineNumber + this.ctxLineNumbers, data.length); 95 | content = content.slice(startLineNumber, endLineNumber); 96 | 97 | line.content = content.join('\n'); 98 | line.startLineNumber = Math.max(0, startLineNumber) + 1; 99 | 100 | return line; 101 | }).catch(() => { }); 102 | } 103 | 104 | /** 105 | * render error page 106 | * @param {*Array} stacks stacke tracer array 107 | */ 108 | renderError(template = this.templates[500], stacks, err) { 109 | let error; 110 | if (this.debug) { 111 | error = JSON.stringify(stacks, null, '\t'); 112 | } else { 113 | error = '[]'; 114 | err = ''; 115 | } 116 | 117 | error = htmlentities(error); 118 | return template.replace('{{error}}', error).replace( 119 | '{{errMsg}}', 120 | err.toString() 121 | .replace(/[\r\n]+/g, '
') 122 | .replace(/\\/g, '\\\\') 123 | .replace(/"/g, '\\"') 124 | ); 125 | } 126 | 127 | /** 128 | * render 404 not found page 129 | * @param {*Error} err Error instance 130 | */ 131 | renderNotFound(ctx, err) { 132 | if (!this.debug) { 133 | err = ''; 134 | } 135 | 136 | return this.templates[404] 137 | .replace('{{errMsg}}', htmlentities(err.toString())); 138 | } 139 | 140 | /** 141 | * @param {*object} ctx koa ctx object 142 | * @param {*Error} err Error instance 143 | */ 144 | run(ctx, err) { 145 | this.ctx = ctx; 146 | ctx.type = helper.isFunction(this.contentType) ? this.contentType(ctx) : 'html'; 147 | 148 | const isJson = helper.isFunction(ctx.response.is) && ctx.response.is('json'); 149 | const isJsonp = helper.isFunction(ctx.isJsonp) && ctx.isJsonp(); 150 | if (isJson || isJsonp) { 151 | if (!helper.isFunction(ctx.json)) { 152 | ctx.json = res => { ctx.body = JSON.stringify(res) }; 153 | } 154 | 155 | const customField = think && helper.isFunction(think.config); 156 | const errnoField = customField ? think.config('errnoField') : 'errno'; 157 | const errmsgField = customField ? think.config('errmsgField') : 'errmsg'; 158 | 159 | return (isJsonp ? ctx.jsonp : ctx.json).bind(ctx)({ 160 | [errnoField]: ctx.status, 161 | [errmsgField]: err.message 162 | }); 163 | } 164 | 165 | // 404 not found error 166 | if (ctx.status === 404) { 167 | ctx.body = this.renderNotFound(ctx, err); 168 | return true; 169 | } 170 | 171 | const stack = stackTrace.parse(err); 172 | return Promise.all( 173 | stack.map(this.getFile.bind(this)) 174 | ).then(stacks => stacks.filter(stack => stack)) 175 | .then(stacks => { 176 | const template = this.templates[ctx.status]; 177 | ctx.body = this.renderError(template, stack, err); 178 | }); 179 | } 180 | }; 181 | -------------------------------------------------------------------------------- /template/src/lib/prettify.min.js: -------------------------------------------------------------------------------- 1 | !function(){var e=null;window.PR_SHOULD_USE_CONTINUATION=!0,function(){function t(e){function t(e){var t=e.charCodeAt(0);if(92!==t)return t;var i=e.charAt(1);return(t=u[i])?t:i>="0"&&"7">=i?parseInt(e.substring(1),8):"u"===i||"x"===i?parseInt(e.substring(2),16):e.charCodeAt(1)}function i(e){return 32>e?(16>e?"\\x0":"\\x")+e.toString(16):(e=String.fromCharCode(e),"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e)}function n(e){var n=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],r="^"===n[0],s=["["];r&&s.push("^");for(var r=r?1:0,o=n.length;o>r;++r){var a=n[r];if(/\\[bdsw]/i.test(a))s.push(a);else{var l,a=t(a);o>r+2&&"-"===n[r+1]?(l=t(n[r+2]),r+=2):l=a,e.push([a,l]),65>l||a>122||(65>l||a>90||e.push([32|Math.max(65,a),32|Math.min(l,90)]),97>l||a>122||e.push([-33&Math.max(97,a),-33&Math.min(l,122)]))}}for(e.sort(function(e,t){return e[0]-t[0]||t[1]-e[1]}),n=[],o=[],r=0;e.length>r;++r)a=e[r],a[0]<=o[1]+1?o[1]=Math.max(o[1],a[1]):n.push(o=a);for(r=0;n.length>r;++r)a=n[r],s.push(i(a[0])),a[1]>a[0]&&(a[1]+1>a[0]&&s.push("-"),s.push(i(a[1])));return s.push("]"),s.join("")}function r(e){for(var t=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),r=t.length,a=[],l=0,h=0;r>l;++l){var c=t[l];"("===c?++h:"\\"===c.charAt(0)&&(c=+c.substring(1))&&(h>=c?a[c]=-1:t[l]=i(c))}for(l=1;a.length>l;++l)-1===a[l]&&(a[l]=++s);for(h=l=0;r>l;++l)c=t[l],"("===c?(++h,a[h]||(t[l]="(?:")):"\\"===c.charAt(0)&&(c=+c.substring(1))&&h>=c&&(t[l]="\\"+a[c]);for(l=0;r>l;++l)"^"===t[l]&&"^"!==t[l+1]&&(t[l]="");if(e.ignoreCase&&o)for(l=0;r>l;++l)c=t[l],e=c.charAt(0),c.length>=2&&"["===e?t[l]=n(c):"\\"!==e&&(t[l]=c.replace(/[A-Za-z]/g,function(e){return e=e.charCodeAt(0),"["+String.fromCharCode(-33&e,32|e)+"]"}));return t.join("")}for(var s=0,o=!1,a=!1,l=0,h=e.length;h>l;++l){var c=e[l];if(c.ignoreCase)a=!0;else if(/[a-z]/i.test(c.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){o=!0,a=!1;break}}for(var u={b:8,t:9,n:10,v:11,f:12,r:13},d=[],l=0,h=e.length;h>l;++l){if(c=e[l],c.global||c.multiline)throw Error(""+c);d.push("(?:"+r(c)+")")}return RegExp(d.join("|"),a?"gi":"g")}function i(e,t){function i(e){var l=e.nodeType;if(1==l){if(!n.test(e.className)){for(l=e.firstChild;l;l=l.nextSibling)i(l);l=e.nodeName.toLowerCase(),("br"===l||"li"===l)&&(r[a]="\n",o[a<<1]=s++,o[1|a++<<1]=e)}}else(3==l||4==l)&&(l=e.nodeValue,l.length&&(l=t?l.replace(/\r\n?/g,"\n"):l.replace(/[\t\n\r ]+/g," "),r[a]=l,o[a<<1]=s,s+=l.length,o[1|a++<<1]=e))}var n=/(?:^|\s)nocode(?:\s|$)/,r=[],s=0,o=[],a=0;return i(e),{a:r.join("").replace(/\n$/,""),d:o}}function n(e,t,i,n){t&&(e={a:t,e:e},i(e),n.push.apply(n,e.g))}function r(e){for(var t=void 0,i=e.firstChild;i;i=i.nextSibling)var n=i.nodeType,t=1===n?t?e:i:3===n?x.test(i.nodeValue)?e:t:t;return t===e?void 0:t}function s(i,r){function s(e){for(var t=e.e,i=[t,"pln"],c=0,u=e.a.match(o)||[],d={},p=0,f=u.length;f>p;++p){var m,g=u[p],y=d[g],v=void 0;if("string"==typeof y)m=!1;else{var b=a[g.charAt(0)];if(b)v=g.match(b[1]),y=b[0];else{for(m=0;l>m;++m)if(b=r[m],v=g.match(b[1])){y=b[0];break}v||(y="pln")}!(m=y.length>=5&&"lang-"===y.substring(0,5))||v&&"string"==typeof v[1]||(m=!1,y="src"),m||(d[g]=y)}if(b=c,c+=g.length,m){m=v[1];var L=g.indexOf(m),x=L+m.length;v[2]&&(x=g.length-v[2].length,L=x-m.length),y=y.substring(5),n(t+b,g.substring(0,L),s,i),n(t+b+L,m,h(y,m),i),n(t+b+x,g.substring(x),s,i)}else i.push(t+b,y)}e.g=i}var o,a={};(function(){for(var n=i.concat(r),s=[],l={},h=0,c=n.length;c>h;++h){var u=n[h],d=u[3];if(d)for(var p=d.length;--p>=0;)a[d.charAt(p)]=u;u=u[1],d=""+u,l.hasOwnProperty(d)||(s.push(u),l[d]=e)}s.push(/[\S\s]/),o=t(s)})();var l=r.length;return s}function o(t){var i=[],n=[];t.tripleQuotedStrings?i.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,e,"'\""]):t.multiLineStrings?i.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,e,"'\"`"]):i.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,e,"\"'"]),t.verbatimStrings&&n.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,e]);var r=t.hashComments;if(r&&(t.cStyleComments?(r>1?i.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,e,"#"]):i.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,e,"#"]),n.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,e])):i.push(["com",/^#[^\n\r]*/,e,"#"])),t.cStyleComments&&(n.push(["com",/^\/\/[^\n\r]*/,e]),n.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,e])),r=t.regexLiterals){var o=(r=r>1?"":"\n\r")?".":"[\\S\\s]";n.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+r+"])(?:[^/\\x5B\\x5C"+r+"]|\\x5C"+o+"|\\x5B(?:[^\\x5C\\x5D"+r+"]|\\x5C"+o+")*(?:\\x5D|$))+/")+")")])}return(r=t.types)&&n.push(["typ",r]),r=(""+t.keywords).replace(/^ | $/g,""),r.length&&n.push(["kwd",RegExp("^(?:"+r.replace(/[\s,]+/g,"|")+")\\b"),e]),i.push(["pln",/^\s+/,e," \r\n "]),r="^.[^\\s\\w.$@'\"`/\\\\]*",t.regexLiterals&&(r+="(?!s*/)"),n.push(["lit",/^@[$_a-z][\w$@]*/i,e],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,e],["pln",/^[$_a-z][\w$@]*/i,e],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,e,"0123456789"],["pln",/^\\[\S\s]?/,e],["pun",RegExp(r),e]),s(i,n)}function a(e,t,i){function n(e){var t=e.nodeType;if(1!=t||s.test(e.className)){if((3==t||4==t)&&i){var l=e.nodeValue,h=l.match(o);h&&(t=l.substring(0,h.index),e.nodeValue=t,(l=l.substring(h.index+h[0].length))&&e.parentNode.insertBefore(a.createTextNode(l),e.nextSibling),r(e),t||e.parentNode.removeChild(e))}}else if("br"===e.nodeName)r(e),e.parentNode&&e.parentNode.removeChild(e);else for(e=e.firstChild;e;e=e.nextSibling)n(e)}function r(e){function t(e,i){var n=i?e.cloneNode(!1):e,r=e.parentNode;if(r){var r=t(r,1),s=e.nextSibling;r.appendChild(n);for(var o=s;o;o=s)s=o.nextSibling,r.appendChild(o)}return n}for(;!e.nextSibling;)if(e=e.parentNode,!e)return;for(var i,e=t(e.nextSibling,0);(i=e.parentNode)&&1===i.nodeType;)e=i;h.push(e)}for(var s=/(?:^|\s)nocode(?:\s|$)/,o=/\r\n?|\n/,a=e.ownerDocument,l=a.createElement("li");e.firstChild;)l.appendChild(e.firstChild);for(var h=[l],c=0;h.length>c;++c)n(h[c]);t===(0|t)&&h[0].setAttribute("value",t);var u=a.createElement("ol");u.className="linenums";for(var t=Math.max(0,0|t-1)||0,c=0,d=h.length;d>c;++c)l=h[c],l.className="L"+(c+t)%10,l.firstChild||l.appendChild(a.createTextNode(" ")),u.appendChild(l);e.appendChild(u)}function l(e,t){for(var i=t.length;--i>=0;){var n=t[i];w.hasOwnProperty(n)?u.console&&console.warn("cannot override language handler %s",n):w[n]=e}}function h(e,t){return e&&w.hasOwnProperty(e)||(e=/^\s*=+s[1],t=/\n/g,o=e.a,a=o.length,n=0,l=e.d,c=l.length,r=0,d=e.g,p=d.length,f=0;d[p]=a;var m,g;for(g=m=0;p>g;)d[g]!==d[g+2]?(d[m++]=d[g++],d[m++]=d[g++]):g+=2;for(p=m,g=m=0;p>g;){for(var y=d[g],v=d[g+1],b=g+2;p>=b+2&&d[b+1]===v;)b+=2;d[m++]=y,d[m++]=v,g=b}d.length=m;var L,x=e.c;x&&(L=x.style.display,x.style.display="none");try{for(;c>r;){var C,w=l[r+2]||a,S=d[f+2]||a,b=Math.min(w,S),O=l[r+1];if(1!==O.nodeType&&(C=o.substring(n,b))){s&&(C=C.replace(t,"\r")),O.nodeValue=C;var _=O.ownerDocument,E=_.createElement("span");E.className=d[f+1];var k=O.parentNode;k.replaceChild(E,O),E.appendChild(O),w>n&&(l[r+1]=O=_.createTextNode(o.substring(b,w)),k.insertBefore(O,E.nextSibling))}n=b,n>=w&&(r+=2),n>=S&&(f+=2)}}finally{x&&(x.style.display=L)}}catch(T){u.console&&console.log(T&&T.stack||T)}}var u=window,d=["break,continue,do,else,for,if,return,while"],p=[[d,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],f=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],m=[p,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],g=[m,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],p=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],y=[d,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],v=[d,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],b=[d,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],d=[d,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],L=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,x=/\S/,C=o({keywords:[f,g,p,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",y,v,d],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),w={};l(C,["default-code"]),l(s([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]),l(s([["pln",/^\s+/,e," \r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,e,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]),l(s([],[["atv",/^[\S\s]+/]]),["uq.val"]),l(o({keywords:f,hashComments:!0,cStyleComments:!0,types:L}),["c","cc","cpp","cxx","cyc","m"]),l(o({keywords:"null,true,false"}),["json"]),l(o({keywords:g,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:L}),["cs"]),l(o({keywords:m,cStyleComments:!0}),["java"]),l(o({keywords:d,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]),l(o({keywords:y,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]),l(o({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]),l(o({keywords:v,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]),l(o({keywords:p,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]),l(o({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]),l(o({keywords:b,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]),l(s([],[["str",/^[\S\s]+/]]),["regex"]);var S=u.PR={createSimpleLexer:s,registerLangHandler:l,sourceDecorator:o,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:u.prettyPrintOne=function(e,t,i){var n=document.createElement("div");return n.innerHTML="
"+e+"
",n=n.firstChild,i&&a(n,i,!0),c({h:t,j:i,c:n,i:1}),n.innerHTML},prettyPrint:u.prettyPrint=function(t,i){function n(){for(var i=u.PR_SHOULD_USE_CONTINUATION?f.now()+250:1/0;l.length>g&&i>f.now();g++){for(var s=l[g],h=w,d=s;d=d.previousSibling;){var p=d.nodeType,S=(7===p||8===p)&&d.nodeValue;if(S?!/^\??prettify\b/.test(S):3!==p||/\S/.test(d.nodeValue))break;if(S){h={},S.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(e,t,i){h[t]=i});break}}if(d=s.className,(h!==w||v.test(d))&&!b.test(d)){for(p=!1,S=s.parentNode;S;S=S.parentNode)if(C.test(S.tagName)&&S.className&&v.test(S.className)){p=!0;break}if(!p){if(s.className+=" prettyprinted",p=h.lang,!p){var O,p=d.match(y);!p&&(O=r(s))&&x.test(O.tagName)&&(p=O.className.match(y)),p&&(p=p[1])}if(L.test(s.tagName))S=1;else var S=s.currentStyle,_=o.defaultView,S=(S=S?S.whiteSpace:_&&_.getComputedStyle?_.getComputedStyle(s,e).getPropertyValue("white-space"):0)&&"pre"===S.substring(0,3);_=h.linenums,(_="true"===_||+_)||(_=(_=d.match(/\blinenums\b(?::(\d+))?/))?_[1]&&_[1].length?+_[1]:!0:!1),_&&a(s,_,S),m={h:p,c:s,j:_,i:S},c(m)}}}l.length>g?setTimeout(n,250):"function"==typeof t&&t()}for(var s=i||document.body,o=s.ownerDocument||document,s=[s.getElementsByTagName("pre"),s.getElementsByTagName("code"),s.getElementsByTagName("xmp")],l=[],h=0;s.length>h;++h)for(var d=0,p=s[h].length;p>d;++d)l.push(s[h][d]);var s=e,f=Date;f.now||(f={now:function(){return+new Date}});var m,g=0,y=/\blang(?:uage)?-([\w.]+)(?!\S)/,v=/\bprettyprint\b/,b=/\bprettyprinted\b/,L=/pre|xmp/i,x=/^code$/i,C=/^(?:pre|code|xmp)$/i,w={};n()}};"function"==typeof define&&define.amd&&define("google-code-prettify",[],function(){return S})}()}(); -------------------------------------------------------------------------------- /template/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Not Found - ThinkJS 6 | 11 | 12 | 13 |
14 |
15 | 19 | 24 |
25 |
26 |
27 |
28 |

Not Found

29 |
30 |
{{errMsg}}
31 |
32 |
33 | 38 | 39 | -------------------------------------------------------------------------------- /template/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Internal Server Error - ThinkJS 6 | 11 | 12 | 13 |
14 |
15 | 20 | 25 |
26 |
27 |
28 |
29 |

Internal Server Error

30 |
31 | 274 | 275 | --------------------------------------------------------------------------------