@holz/console-backend 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/holz-console-backend.cjs +1 -1
- package/dist/holz-console-backend.js +29 -32
- package/package.json +4 -4
- package/src/__tests__/__snapshots__/console-backend.test.ts.snap +12 -12
- package/src/__tests__/console-backend.test.ts +28 -6
- package/src/__tests__/time-delta.test.ts +29 -0
- package/src/console-backend.ts +27 -33
- package/src/index.ts +1 -1
- package/src/time-delta.ts +33 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=1e3,c=60*a,u=60*c,l=24*u,f=(s,r=s)=>{const e=s.getTime()-r.getTime(),t=Math.abs(e),n=e<0?"":"+";return t>=l?`${n}${Math.round(e/l)}d`:t>=u?`${n}${Math.round(e/u)}h`:t>=c?`${n}${Math.round(e/c)}m`:t>=a?`${n}${Math.round(e/a)}s`:`${n}${e}ms`};function $(s={}){const r=s.console??console;let e;return t=>{const n=new Date,i=[{include:!0,format:"%s",values:[t.message]},{include:Object.keys(t.context).length>0,format:"%o",values:[t.context]},{include:!0,format:"%c%s",values:["color: gray",f(n,e)]},{include:t.origin.length>0,format:"%c%s",values:["color: rgba(128, 128, 128, 0.6); font-style: italic",t.origin.join(":")]}].filter(o=>o.include),m=i.map(o=>o.format).join(" "),d=i.flatMap(o=>o.values);r[t.level](m,...d),e=n}}exports.createConsoleBackend=$;
|
|
@@ -1,43 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
const i = (s, r = s) => {
|
|
2
|
+
const e = s.getTime() - r.getTime(), t = Math.abs(e), n = e < 0 ? "" : "+";
|
|
3
|
+
return t >= 864e5 ? `${n}${Math.round(e / 864e5)}d` : t >= 36e5 ? `${n}${Math.round(e / 36e5)}h` : t >= 6e4 ? `${n}${Math.round(e / 6e4)}m` : t >= 1e3 ? `${n}${Math.round(e / 1e3)}s` : `${n}${e}ms`;
|
|
4
|
+
};
|
|
5
|
+
function l(s = {}) {
|
|
6
|
+
const r = s.console ?? console;
|
|
7
|
+
let e;
|
|
8
|
+
return (t) => {
|
|
9
|
+
const n = new Date(), c = [
|
|
8
10
|
{
|
|
9
11
|
include: !0,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
style: `color: ${n.level === c.Debug ? "gray" : "unset"}`
|
|
12
|
+
format: "%s",
|
|
13
|
+
values: [t.message]
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
|
-
include: Object.keys(
|
|
16
|
-
|
|
16
|
+
include: Object.keys(t.context).length > 0,
|
|
17
|
+
format: "%o",
|
|
17
18
|
// Chrome hides object content with `%O`.
|
|
18
|
-
|
|
19
|
-
style: ""
|
|
19
|
+
values: [t.context]
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
include:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
include: !0,
|
|
23
|
+
format: "%c%s",
|
|
24
|
+
values: ["color: gray", i(n, e)]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
include: t.origin.length > 0,
|
|
28
|
+
format: "%c%s",
|
|
29
|
+
values: [
|
|
30
|
+
"color: rgba(128, 128, 128, 0.6); font-style: italic",
|
|
31
|
+
t.origin.join(":")
|
|
32
|
+
]
|
|
26
33
|
}
|
|
27
|
-
].filter((o) => o.include),
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
]), a = l[n.level];
|
|
31
|
-
this.console[a](r, ...s);
|
|
32
|
-
}
|
|
34
|
+
].filter((o) => o.include), a = c.map((o) => o.format).join(" "), u = c.flatMap((o) => o.values);
|
|
35
|
+
r[t.level](a, ...u), e = n;
|
|
36
|
+
};
|
|
33
37
|
}
|
|
34
|
-
const l = {
|
|
35
|
-
[c.Debug]: "debug",
|
|
36
|
-
[c.Info]: "info",
|
|
37
|
-
[c.Warn]: "warn",
|
|
38
|
-
[c.Error]: "error"
|
|
39
|
-
};
|
|
40
38
|
export {
|
|
41
|
-
|
|
42
|
-
i as default
|
|
39
|
+
l as createConsoleBackend
|
|
43
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holz/console-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A console backend for Holz",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/holz-console-backend.cjs",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"test:types": "tsc"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@holz/core": "0.
|
|
42
|
+
"@holz/core": "^0.5.0",
|
|
43
43
|
"@types/node": "^18.14.0",
|
|
44
|
-
"@vitest/coverage-c8": "0.28.5",
|
|
45
|
-
"typescript": "4.9.5",
|
|
44
|
+
"@vitest/coverage-c8": "^0.28.5",
|
|
45
|
+
"typescript": "^4.9.5",
|
|
46
46
|
"vite": "^4.0.0",
|
|
47
47
|
"vitest": "^0.28.5"
|
|
48
48
|
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`Console backend > avoids changing debug messages 1`] = `
|
|
4
4
|
[
|
|
5
|
-
"%
|
|
6
|
-
"color: gray",
|
|
5
|
+
"%s %o %c%s %c%s",
|
|
7
6
|
"just spam",
|
|
8
|
-
"",
|
|
9
7
|
{
|
|
10
8
|
"id": 1234,
|
|
11
9
|
},
|
|
10
|
+
"color: gray",
|
|
11
|
+
"+0ms",
|
|
12
12
|
"color: rgba(128, 128, 128, 0.6); font-style: italic",
|
|
13
13
|
"ns-1:ns-2",
|
|
14
14
|
]
|
|
@@ -16,13 +16,13 @@ exports[`Console backend > avoids changing debug messages 1`] = `
|
|
|
16
16
|
|
|
17
17
|
exports[`Console backend > avoids changing error messages 1`] = `
|
|
18
18
|
[
|
|
19
|
-
"%
|
|
20
|
-
"color: unset",
|
|
19
|
+
"%s %o %c%s %c%s",
|
|
21
20
|
"some error message",
|
|
22
|
-
"",
|
|
23
21
|
{
|
|
24
22
|
"id": 1234,
|
|
25
23
|
},
|
|
24
|
+
"color: gray",
|
|
25
|
+
"+0ms",
|
|
26
26
|
"color: rgba(128, 128, 128, 0.6); font-style: italic",
|
|
27
27
|
"ns-1:ns-2",
|
|
28
28
|
]
|
|
@@ -30,13 +30,13 @@ exports[`Console backend > avoids changing error messages 1`] = `
|
|
|
30
30
|
|
|
31
31
|
exports[`Console backend > avoids changing info messages 1`] = `
|
|
32
32
|
[
|
|
33
|
-
"%
|
|
34
|
-
"color: unset",
|
|
33
|
+
"%s %o %c%s %c%s",
|
|
35
34
|
"a little info",
|
|
36
|
-
"",
|
|
37
35
|
{
|
|
38
36
|
"id": 1234,
|
|
39
37
|
},
|
|
38
|
+
"color: gray",
|
|
39
|
+
"+0ms",
|
|
40
40
|
"color: rgba(128, 128, 128, 0.6); font-style: italic",
|
|
41
41
|
"ns-1:ns-2",
|
|
42
42
|
]
|
|
@@ -44,13 +44,13 @@ exports[`Console backend > avoids changing info messages 1`] = `
|
|
|
44
44
|
|
|
45
45
|
exports[`Console backend > avoids changing warn messages 1`] = `
|
|
46
46
|
[
|
|
47
|
-
"%
|
|
48
|
-
"color: unset",
|
|
47
|
+
"%s %o %c%s %c%s",
|
|
49
48
|
"a warning",
|
|
50
|
-
"",
|
|
51
49
|
{
|
|
52
50
|
"id": 1234,
|
|
53
51
|
},
|
|
52
|
+
"color: gray",
|
|
53
|
+
"+0ms",
|
|
54
54
|
"color: rgba(128, 128, 128, 0.6); font-style: italic",
|
|
55
55
|
"ns-1:ns-2",
|
|
56
56
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { format } from 'util';
|
|
2
2
|
import { createLogger } from '@holz/core';
|
|
3
3
|
import type { MinimalConsole } from '../console-backend';
|
|
4
|
-
import
|
|
4
|
+
import { createConsoleBackend } from '../console-backend';
|
|
5
5
|
|
|
6
6
|
class MockConsole implements MinimalConsole {
|
|
7
7
|
private fmt = (level: string, ...strings: Array<unknown>) => {
|
|
@@ -18,9 +18,19 @@ class MockConsole implements MinimalConsole {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe('Console backend', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.useFakeTimers({
|
|
23
|
+
now: new Date('2020-01-01T00:00:00.000Z'),
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.useRealTimers();
|
|
29
|
+
});
|
|
30
|
+
|
|
21
31
|
it('prints messages to the console', () => {
|
|
22
32
|
const output = new MockConsole();
|
|
23
|
-
const backend =
|
|
33
|
+
const backend = createConsoleBackend({ console: output });
|
|
24
34
|
|
|
25
35
|
const logger = createLogger(backend);
|
|
26
36
|
logger.info('hello world');
|
|
@@ -32,7 +42,7 @@ describe('Console backend', () => {
|
|
|
32
42
|
|
|
33
43
|
it('includes the log namespace', () => {
|
|
34
44
|
const output = new MockConsole();
|
|
35
|
-
const backend =
|
|
45
|
+
const backend = createConsoleBackend({ console: output });
|
|
36
46
|
const logger = createLogger(backend)
|
|
37
47
|
.namespace('my-lib')
|
|
38
48
|
.namespace('MyClass');
|
|
@@ -46,7 +56,7 @@ describe('Console backend', () => {
|
|
|
46
56
|
|
|
47
57
|
it('includes the log context', () => {
|
|
48
58
|
const output = new MockConsole();
|
|
49
|
-
const backend =
|
|
59
|
+
const backend = createConsoleBackend({ console: output });
|
|
50
60
|
const logger = createLogger(backend);
|
|
51
61
|
|
|
52
62
|
logger.info('creating session', { sessionId: 3109 });
|
|
@@ -61,7 +71,7 @@ describe('Console backend', () => {
|
|
|
61
71
|
|
|
62
72
|
it('does not include the log context if it is empty', () => {
|
|
63
73
|
const output = new MockConsole();
|
|
64
|
-
const backend =
|
|
74
|
+
const backend = createConsoleBackend({ console: output });
|
|
65
75
|
const logger = createLogger(backend);
|
|
66
76
|
|
|
67
77
|
logger.warn('activating death ray', {});
|
|
@@ -72,6 +82,18 @@ describe('Console backend', () => {
|
|
|
72
82
|
expect(output.print).not.toHaveBeenCalledWith(expect.stringContaining('}'));
|
|
73
83
|
});
|
|
74
84
|
|
|
85
|
+
it('prints the time since the last log', () => {
|
|
86
|
+
const output = new MockConsole();
|
|
87
|
+
const backend = createConsoleBackend({ console: output });
|
|
88
|
+
const logger = createLogger(backend);
|
|
89
|
+
|
|
90
|
+
logger.info('first message');
|
|
91
|
+
vi.advanceTimersByTime(1000);
|
|
92
|
+
logger.info('second message');
|
|
93
|
+
|
|
94
|
+
expect(output.print).toHaveBeenCalledWith(expect.stringContaining('+1s'));
|
|
95
|
+
});
|
|
96
|
+
|
|
75
97
|
// Snapshot the exact output of each log level. This mostly prevents
|
|
76
98
|
// regressions while doing innocent refactors.
|
|
77
99
|
it.each([
|
|
@@ -86,7 +108,7 @@ describe('Console backend', () => {
|
|
|
86
108
|
],
|
|
87
109
|
])('avoids changing %s messages', (method, message, namespace, pipe) => {
|
|
88
110
|
const output = new MockConsole();
|
|
89
|
-
const backend =
|
|
111
|
+
const backend = createConsoleBackend({ console: output });
|
|
90
112
|
const logger = namespace.reduce(
|
|
91
113
|
(logger, ns) => logger.namespace(ns),
|
|
92
114
|
createLogger(backend)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { timeDelta } from '../time-delta';
|
|
2
|
+
|
|
3
|
+
describe('timeDelta', () => {
|
|
4
|
+
it('formats the difference in human-readable time', () => {
|
|
5
|
+
expect(timeDelta(new Date(0), new Date(0))).toBe('+0ms');
|
|
6
|
+
expect(timeDelta(new Date(1), new Date(0))).toBe('+1ms');
|
|
7
|
+
expect(timeDelta(new Date(999), new Date(0))).toBe('+999ms');
|
|
8
|
+
expect(timeDelta(new Date(1_000), new Date(0))).toBe('+1s');
|
|
9
|
+
|
|
10
|
+
expect(timeDelta(new Date(1_499), new Date(0))).toBe('+1s');
|
|
11
|
+
expect(timeDelta(new Date(1_500), new Date(0))).toBe('+2s');
|
|
12
|
+
|
|
13
|
+
// Not quite a full minute. Kinda weird, but only if you think about it.
|
|
14
|
+
expect(timeDelta(new Date(59_999), new Date(0))).toBe('+60s');
|
|
15
|
+
|
|
16
|
+
expect(timeDelta(new Date(60_000), new Date(0))).toBe('+1m');
|
|
17
|
+
expect(timeDelta(new Date(3_600_000), new Date(0))).toBe('+1h');
|
|
18
|
+
expect(timeDelta(new Date(86_400_000), new Date(0))).toBe('+1d');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('uses the current time if the last point is unknown', () => {
|
|
22
|
+
expect(timeDelta(new Date(0))).toBe('+0ms');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles negative time differences', () => {
|
|
26
|
+
expect(timeDelta(new Date(0), new Date(1))).toBe('-1ms');
|
|
27
|
+
expect(timeDelta(new Date(0), new Date(60_000))).toBe('-1m');
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/console-backend.ts
CHANGED
|
@@ -1,60 +1,54 @@
|
|
|
1
1
|
import type { Log, LogProcessor } from '@holz/core';
|
|
2
|
-
import {
|
|
2
|
+
import { timeDelta } from './time-delta';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A backend that pretty-prints logs to a browser console, or any
|
|
6
6
|
* remote-attached console.
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
8
|
+
export function createConsoleBackend(options: Options = {}): LogProcessor {
|
|
9
|
+
const output = options.console ?? console;
|
|
10
|
+
let lastTimestamp: Date;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
return (log: Log) => {
|
|
13
|
+
const now = new Date();
|
|
14
14
|
|
|
15
|
-
processLog(log: Log) {
|
|
16
15
|
const segments = [
|
|
17
16
|
{
|
|
18
17
|
include: true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
style: `color: ${log.level === LogLevel.Debug ? 'gray' : 'unset'}`,
|
|
18
|
+
format: '%s',
|
|
19
|
+
values: [log.message],
|
|
22
20
|
},
|
|
23
21
|
{
|
|
24
22
|
include: Object.keys(log.context).length > 0,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
format: '%o', // Chrome hides object content with `%O`.
|
|
24
|
+
values: [log.context],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
include: true,
|
|
28
|
+
format: '%c%s',
|
|
29
|
+
values: ['color: gray', timeDelta(now, lastTimestamp)],
|
|
28
30
|
},
|
|
29
31
|
{
|
|
30
32
|
include: log.origin.length > 0,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
format: '%c%s',
|
|
34
|
+
values: [
|
|
35
|
+
'color: rgba(128, 128, 128, 0.6); font-style: italic',
|
|
36
|
+
log.origin.join(':'),
|
|
37
|
+
],
|
|
34
38
|
},
|
|
35
39
|
].filter((segment) => segment.include);
|
|
36
40
|
|
|
37
|
-
const
|
|
38
|
-
const values = segments.flatMap((segment) =>
|
|
39
|
-
segment.style,
|
|
40
|
-
segment.content,
|
|
41
|
-
]);
|
|
41
|
+
const format = segments.map((segment) => segment.format).join(' ');
|
|
42
|
+
const values = segments.flatMap<unknown>((segment) => segment.values);
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
|
|
44
|
+
// Browsers have UIs for filtering by log level. Leverage that.
|
|
45
|
+
output[log.level](format, ...values);
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// Track the time spent between logs.
|
|
48
|
+
lastTimestamp = now;
|
|
49
|
+
};
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
// Browsers have UIs for filtering by log level. Leverage that.
|
|
51
|
-
const LOG_METHOD: Record<LogLevel, keyof MinimalConsole> = {
|
|
52
|
-
[LogLevel.Debug]: 'debug',
|
|
53
|
-
[LogLevel.Info]: 'info',
|
|
54
|
-
[LogLevel.Warn]: 'warn',
|
|
55
|
-
[LogLevel.Error]: 'error',
|
|
56
|
-
};
|
|
57
|
-
|
|
58
52
|
interface Options {
|
|
59
53
|
console?: MinimalConsole;
|
|
60
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createConsoleBackend } from './console-backend';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const second = 1_000;
|
|
2
|
+
const minute = 60 * second;
|
|
3
|
+
const hour = 60 * minute;
|
|
4
|
+
const day = 24 * hour;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Formats the difference between two dates in human-readable time.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by the `ms` package. Thanks TJ.
|
|
10
|
+
*/
|
|
11
|
+
export const timeDelta = (now: Date, then: Date = now) => {
|
|
12
|
+
const offset = now.getTime() - then.getTime();
|
|
13
|
+
const distance = Math.abs(offset);
|
|
14
|
+
const sign = offset < 0 ? '' : '+';
|
|
15
|
+
|
|
16
|
+
if (distance >= day) {
|
|
17
|
+
return `${sign}${Math.round(offset / day)}d`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (distance >= hour) {
|
|
21
|
+
return `${sign}${Math.round(offset / hour)}h`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (distance >= minute) {
|
|
25
|
+
return `${sign}${Math.round(offset / minute)}m`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (distance >= second) {
|
|
29
|
+
return `${sign}${Math.round(offset / second)}s`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${sign}${offset}ms`;
|
|
33
|
+
};
|