Skip to content

Commit b12e328

Browse files
committedSep 26, 2017
chore: initial commit
0 parents  commit b12e328

14 files changed

+4331
-0
lines changed
 

‎.babelrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"presets": [
3+
"es2015",
4+
"stage-2"
5+
]
6+
}

‎.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/*{.,-}min.js

‎.eslintrc

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"parserOptions": {
3+
"ecmaVersion": 6,
4+
"sourceType": "module",
5+
"ecmaFeatures": {
6+
"forOf": true,
7+
"jsx": true,
8+
"es6": true,
9+
"experimentalObjectRestSpread" : true
10+
}
11+
},
12+
"env": {
13+
"browser": true,
14+
"es6": true,
15+
"mocha": true
16+
},
17+
"rules": {
18+
"camelcase": [2, { "properties": "never" }],
19+
"comma-dangle": [2, "always-multiline"],
20+
"eqeqeq": 2, // requires === and !===
21+
"jsx-quotes": [2, "prefer-double"],
22+
"quotes": [2, "single", { "allowTemplateLiterals": true }],
23+
"no-eval": 2,
24+
"no-irregular-whitespace": 2,
25+
"no-trailing-spaces": 2,
26+
"no-unsafe-finally": 2,
27+
"no-console": 2,
28+
"no-const-assign": 2,
29+
"prefer-const": 2,
30+
"no-undef": 2,
31+
"no-unused-vars": [2, { "argsIgnorePattern": "^_", "varsIgnorePattern": "(^_|React)" } ],
32+
"semi": [2, "always"],
33+
"eol-last": 1,
34+
"mocha/no-exclusive-tests": "error"
35+
},
36+
"plugins": [
37+
"import",
38+
"mocha"
39+
],
40+
"globals": {
41+
"__DEVSERVER__": true,
42+
"__DEVCLIENT__": true,
43+
"Buffer": true,
44+
"process": true,
45+
"global": true,
46+
"expect": true,
47+
"module": true,
48+
"require": true,
49+
"describe": true,
50+
"sinon": true,
51+
"logger": true
52+
}
53+
}

‎.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
npm-debug.log
3+
.tmp
4+
.idea
5+
public
6+
compiled
7+
.swp
8+
.vscode
9+
.env
10+
.DS_Store
11+
coverage

‎package.json

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "jv",
3+
"version": "0.1.0",
4+
"description": "JSON Log Viewer",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "babel-node --presets es2015,stage-2 -- src/index.js",
8+
"test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --timeout 5000 --require test/support/test-helper.js test/*.test.js test/**/*.test.js",
9+
"test:watch": "NODE_ENV=test ./node_modules/.bin/mocha --watch --compilers js:babel-core/register --timeout 5000 --require test/support/test-helper.js test/*.test.js test/**/*.test.js"
10+
},
11+
"author": "Felipe Coury",
12+
"license": "MIT",
13+
"engines": {
14+
"node": ">=8.0.0"
15+
},
16+
"dependencies": {
17+
"babel-cli": "^6.26.0",
18+
"babel-core": "^6.26.0",
19+
"babel-preset-es2015": "^6.24.1",
20+
"babel-preset-stage-2": "^6.24.1",
21+
"blessed": "^0.1.81",
22+
"blessed-contrib": "^4.8.5",
23+
"lodash": "^4.17.4"
24+
},
25+
"devDependencies": {
26+
"chai": "3.5.0",
27+
"chai-as-promised": "7.0.0",
28+
"eslint": "3.16.0",
29+
"eslint-plugin-import": "2.2.0",
30+
"eslint-plugin-mocha": "^4.11.0",
31+
"istanbul": "0.4.5",
32+
"karma": "1.5.0",
33+
"karma-chai": "0.1.0",
34+
"karma-mocha": "1.3.0",
35+
"karma-sinon": "1.0.5",
36+
"mocha": "https://github.com/gistia/mocha.git",
37+
"mocha-istanbul": "0.3.0",
38+
"nodemon": "1.11.0",
39+
"sinon": "2.3.5",
40+
"sinon-chai": "2.11.0",
41+
"timekeeper": "1.0.0"
42+
}
43+
}

‎src/browser.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import blessed from 'blessed';
2+
import contrib from 'blessed-contrib';
3+
import _ from 'lodash';
4+
5+
export const newBrowser = (rawData) => {
6+
const def = parseData(rawData);
7+
8+
const table = contrib.table({
9+
keys: true,
10+
fg: 'white',
11+
selectedFg: 'white',
12+
selectedBg: 'blue',
13+
interactive: true,
14+
label: 'Logs',
15+
width: '100%',
16+
height: '100%',
17+
border: { type: 'line', fg: 'cyan' },
18+
columnSpacing: 3,
19+
columnWidth: def.columnWidth,
20+
});
21+
22+
table.setData({ headers: def.headers, data: def.data });
23+
24+
return table;
25+
};
26+
27+
const levelColors = {
28+
debug: s => `{cyan-fg}${s}{/cyan-fg}`,
29+
info: s => `{#ffff94-fg}{bold}${s}{/bold}{/#ffff94-fg}`,
30+
warn: s => `{orange-fg}${s}{/orange-fg}`,
31+
error: s => `{red-fg}${s}{/red-fg}`,
32+
};
33+
34+
const formatLevel = (level) => blessed.parseTags(levelColors[level](level));
35+
36+
const fixLevels = (data) => data.map(d => [d[0], formatLevel(d[1]), d[2]]);
37+
38+
const formatRow = (row) => {
39+
return [
40+
row.timestamp,
41+
row.level,
42+
row.message,
43+
];
44+
};
45+
46+
export const parseData = (rawData) => {
47+
const headers = ['Timestamp', 'Level', 'Message'];
48+
const data = rawData.map(formatRow);
49+
const columnWidth = data.reduce((arr, value) => {
50+
return arr.map((v, index) => Math.max(value[index].length, v));
51+
}, [0, 0, 0]);
52+
53+
return { columnWidth, headers, data: fixLevels(data), rawData };
54+
};

‎src/index.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import blessed from 'blessed';
2+
import _ from 'lodash';
3+
4+
import { readLog } from './log';
5+
import { newBrowser } from './browser';
6+
7+
const screen = blessed.screen({ smartCSR: true });
8+
9+
const browser = newBrowser(readLog('/Users/fcoury/code/workflow-engine/logs/workflow-engine.log.2017-09-25'));
10+
browser.focus();
11+
12+
screen.append(browser);
13+
screen.key(['escape', 'q', 'C-c'], function(_ch, _key) {
14+
return process.exit(0);
15+
});
16+
17+
screen.render();

‎src/log.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import fs from 'fs';
2+
import _ from 'lodash';
3+
4+
export function readLog(file, reader=fs) {
5+
const contents = reader.readFileSync(file).toString();
6+
const lines = contents.split('\n').filter(line => line).map(line => JSON.parse(line));
7+
8+
return lines.map(line => {
9+
const result = _.pick(line, ['timestamp', 'level', 'message']);
10+
const data = _.omit(line, ['timestamp', 'level', 'message']);
11+
return Object.assign({}, result, { data });
12+
});
13+
};

‎test/browser.test.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { parseFixture } from './support/fixtures';
2+
3+
import { parseData } from '../src/browser';
4+
5+
describe('formatData', () => {
6+
let tableDef, data;
7+
8+
before(() => {
9+
const contents = parseFixture('workflow-engine.log.2017-09-25');
10+
tableDef = parseData(contents);
11+
data = tableDef.data;
12+
});
13+
14+
describe('headers', () => {
15+
it('is set', () => {
16+
expect(tableDef.headers).to.eql(['Timestamp', 'Level', 'Message']);
17+
});
18+
});
19+
20+
describe('columnWidth', () => {
21+
it('sets to the maximum width', () => {
22+
const { columnWidth } = tableDef;
23+
expect(columnWidth[0]).to.eql(24);
24+
expect(columnWidth[1]).to.eql(5);
25+
expect(columnWidth[2]).to.eql(5325);
26+
});
27+
});
28+
29+
describe('data', () => {
30+
it('has only 3 columns', () => {
31+
expect(data[0].length).to.eql(3);
32+
});
33+
34+
it('extracts timestamp', () => {
35+
expect(data[0][0]).to.eql('2017-09-25T22:48:38.035Z');
36+
});
37+
38+
it('extracts level', () => {
39+
expect(data[0][1]).to.eql('{cyan-fg}debug{/cyan-fg}');
40+
});
41+
42+
it('extracts message', () => {
43+
expect(data[0][2]).to.eql('Updated instance \'hemo\' with attributes');
44+
});
45+
});
46+
47+
describe('rawData', () => {
48+
it('keeps object', () => {
49+
expect(tableDef.rawData[0].data.to.currentState).to.eql('resultPending');
50+
});
51+
});
52+
});

‎test/fixtures/workflow-engine.log.2017-09-25

+74
Large diffs are not rendered by default.

‎test/log.test.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { loadFixture } from './support/fixtures';
2+
import { readLog } from '../src/log';
3+
4+
describe('readLog', () => {
5+
let entries;
6+
7+
before(() => {
8+
const contents = loadFixture('workflow-engine.log.2017-09-25');
9+
const stubFS = { readFileSync: () => contents };
10+
entries = readLog('', stubFS);
11+
});
12+
13+
it('sets timestamp', () => {
14+
expect(entries[0].timestamp).to.eql('2017-09-25T22:48:38.035Z');
15+
});
16+
17+
it('sets level', () => {
18+
expect(entries[0].level).to.eql('debug');
19+
});
20+
21+
it('sets message', () => {
22+
expect(entries[0].message).to.eql('Updated instance \'hemo\' with attributes');
23+
});
24+
25+
it('sets data', () => {
26+
expect(entries[0].data.to.type).to.eql('workflow-instance');
27+
});
28+
});

‎test/support/fixtures.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import fs from 'fs';
2+
import { readLog } from '../../src/log';
3+
4+
export const loadFixture = (name) => {
5+
return fs.readFileSync(`./test/fixtures/${name}`).toString();
6+
};
7+
8+
export const parseFixture = (name) => {
9+
const contents = loadFixture(name);
10+
const stubFS = { readFileSync: () => contents };
11+
return readLog('', stubFS);
12+
};

‎test/support/test-helper.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const chai = require('chai');
2+
const chaiAsPromised = require('chai-as-promised');
3+
const sinon = require('sinon');
4+
const sinonChai = require('sinon-chai');
5+
6+
chai.use(sinonChai);
7+
chai.use(chaiAsPromised);
8+
9+
global.expect = chai.expect;
10+
global.sinon = sinon;

‎yarn.lock

+3,957
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.