├── .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 |
26 |
27 |
28 |
Not Found
29 |
32 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/template/src/500/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Internal Server Error - ThinkJS
6 |
11 |
12 |
13 |
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 |
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 | []()
4 | []()
5 | []()
6 | []()
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 | 
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",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
38 |