@holz/console-backend 0.8.0-rc.3 → 0.8.2-rc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Jesse Gibson
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holz/console-backend",
3
- "version": "0.8.0-rc.3",
3
+ "version": "0.8.2-rc.0",
4
4
  "description": "A console backend for Holz",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,8 +22,7 @@
22
22
  "license": "MIT",
23
23
  "sideEffects": false,
24
24
  "files": [
25
- "dist",
26
- "src"
25
+ "dist"
27
26
  ],
28
27
  "keywords": [
29
28
  "holz-backend",
@@ -37,10 +36,10 @@
37
36
  "test:types": "tsc"
38
37
  },
39
38
  "peerDependencies": {
40
- "@holz/core": "^0.8.0-rc.3"
39
+ "@holz/core": "^0.8.0"
41
40
  },
42
41
  "devDependencies": {
43
- "@holz/core": "^0.8.0-rc.3",
42
+ "@holz/core": "^0.8.2-rc.0",
44
43
  "@types/node": "^22.0.0",
45
44
  "@vitest/coverage-v8": "^3.0.8",
46
45
  "typescript": "^5.8.2",
@@ -49,5 +48,5 @@
49
48
  "vite-tsconfig-paths": "^5.1.4",
50
49
  "vitest": "^3.0.8"
51
50
  },
52
- "stableVersion": "0.7.0"
53
- }
51
+ "gitHead": "59a13334498d9c3f0952235ed6cb59e493558824"
52
+ }
@@ -1,85 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`Console backend > avoids changing debug messages 1`] = `
4
- [
5
- "%s %o %c%s %c%s",
6
- "just spam",
7
- {
8
- "id": 1234,
9
- },
10
- "color: gray",
11
- "+0ms",
12
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
13
- "ns-1:ns-2",
14
- ]
15
- `;
16
-
17
- exports[`Console backend > avoids changing error messages 1`] = `
18
- [
19
- "%s %o %c%s %c%s",
20
- "some error message",
21
- {
22
- "id": 1234,
23
- },
24
- "color: gray",
25
- "+0ms",
26
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
27
- "ns-1:ns-2",
28
- ]
29
- `;
30
-
31
- exports[`Console backend > avoids changing fatal messages 1`] = `
32
- [
33
- "%s %o %c%s %c%s",
34
- "a fatal error",
35
- {
36
- "id": 1234,
37
- },
38
- "color: gray",
39
- "+0ms",
40
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
41
- "ns-1:ns-2",
42
- ]
43
- `;
44
-
45
- exports[`Console backend > avoids changing info messages 1`] = `
46
- [
47
- "%s %o %c%s %c%s",
48
- "a little info",
49
- {
50
- "id": 1234,
51
- },
52
- "color: gray",
53
- "+0ms",
54
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
55
- "ns-1:ns-2",
56
- ]
57
- `;
58
-
59
- exports[`Console backend > avoids changing trace messages 1`] = `
60
- [
61
- "%s %o %c%s %c%s",
62
- "noise",
63
- {
64
- "id": 1234,
65
- },
66
- "color: gray",
67
- "+0ms",
68
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
69
- "ns-1:ns-2",
70
- ]
71
- `;
72
-
73
- exports[`Console backend > avoids changing warn messages 1`] = `
74
- [
75
- "%s %o %c%s %c%s",
76
- "a warning",
77
- {
78
- "id": 1234,
79
- },
80
- "color: gray",
81
- "+0ms",
82
- "color: rgba(128, 128, 128, 0.6); font-style: italic",
83
- "ns-1:ns-2",
84
- ]
85
- `;
@@ -1,137 +0,0 @@
1
- import { format } from 'util';
2
- import { createLogger } from '@holz/core';
3
- import type { MinimalConsole } from '../console-backend';
4
- import { createConsoleBackend } from '../console-backend';
5
-
6
- class MockConsole implements MinimalConsole {
7
- private fmt = (level: string, ...strings: Array<unknown>) => {
8
- this.print(format(level, ...strings));
9
- };
10
-
11
- trace = vi.fn(this.fmt);
12
- debug = vi.fn(this.fmt);
13
- info = vi.fn(this.fmt);
14
- warn = vi.fn(this.fmt);
15
- error = vi.fn(this.fmt);
16
-
17
- // Approximation of what gets printed.
18
- print = vi.fn();
19
- }
20
-
21
- describe('Console backend', () => {
22
- beforeEach(() => {
23
- vi.useFakeTimers({
24
- now: new Date('2020-01-01T00:00:00.000Z'),
25
- });
26
- });
27
-
28
- afterEach(() => {
29
- vi.useRealTimers();
30
- });
31
-
32
- it('prints messages to the console', () => {
33
- const output = new MockConsole();
34
- const backend = createConsoleBackend({ console: output });
35
-
36
- const logger = createLogger(backend);
37
- logger.info('hello world');
38
-
39
- expect(output.print).toHaveBeenCalledWith(
40
- expect.stringContaining('hello world'),
41
- );
42
- });
43
-
44
- it('includes the log namespace', () => {
45
- const output = new MockConsole();
46
- const backend = createConsoleBackend({ console: output });
47
- const logger = createLogger(backend)
48
- .namespace('my-lib')
49
- .namespace('MyClass');
50
-
51
- logger.debug('initialized');
52
-
53
- expect(output.print).toHaveBeenCalledWith(
54
- expect.stringContaining('my-lib:MyClass'),
55
- );
56
- });
57
-
58
- it('includes the log context', () => {
59
- const output = new MockConsole();
60
- const backend = createConsoleBackend({ console: output });
61
- const logger = createLogger(backend);
62
-
63
- logger.info('creating session', { sessionId: 3109 });
64
-
65
- // Hard to test without replicating the implementation.
66
- expect(output.print).toHaveBeenCalledWith(
67
- expect.stringContaining('sessionId'),
68
- );
69
-
70
- expect(output.print).toHaveBeenCalledWith(expect.stringContaining('3109'));
71
- });
72
-
73
- it('does not include the log context if it is empty', () => {
74
- const output = new MockConsole();
75
- const backend = createConsoleBackend({ console: output });
76
- const logger = createLogger(backend);
77
-
78
- logger.warn('activating death ray', {});
79
-
80
- // Hard to test without replicating the implementation.
81
- expect(output.print).not.toHaveBeenCalledWith(expect.stringContaining('{'));
82
-
83
- expect(output.print).not.toHaveBeenCalledWith(expect.stringContaining('}'));
84
- });
85
-
86
- it('prints the time since the last log', () => {
87
- const output = new MockConsole();
88
- const backend = createConsoleBackend({ console: output });
89
- const logger = createLogger(backend);
90
-
91
- logger.info('first message');
92
- vi.advanceTimersByTime(1000);
93
- logger.info('second message');
94
-
95
- expect(output.print).toHaveBeenCalledWith(expect.stringContaining('+1s'));
96
- });
97
-
98
- // Snapshot the exact output of each log level. This mostly prevents
99
- // regressions while doing innocent refactors.
100
- it.each([
101
- ['trace' as const, 'noise', ['ns-1', 'ns-2'], 'trace' as const],
102
- ['debug' as const, 'just spam', ['ns-1', 'ns-2'], 'debug' as const],
103
- ['info' as const, 'a little info', ['ns-1', 'ns-2'], 'info' as const],
104
- ['warn' as const, 'a warning', ['ns-1', 'ns-2'], 'warn' as const],
105
- [
106
- 'error' as const,
107
- 'some error message',
108
- ['ns-1', 'ns-2'],
109
- 'error' as const,
110
- ],
111
- ['fatal' as const, 'a fatal error', ['ns-1', 'ns-2'], 'error' as const],
112
- ])('avoids changing %s messages', (method, message, namespace, pipe) => {
113
- const output = new MockConsole();
114
- const backend = createConsoleBackend({ console: output });
115
- const logger = namespace.reduce(
116
- (logger, ns) => logger.namespace(ns),
117
- createLogger(backend),
118
- );
119
-
120
- logger[method](message, { id: 1234 });
121
-
122
- expect(output[pipe].mock.calls[0]).toMatchSnapshot();
123
- });
124
-
125
- it('includes the error object', () => {
126
- const output = new MockConsole();
127
- const backend = createConsoleBackend({ console: output });
128
- const logger = createLogger(backend).namespace('hi');
129
-
130
- const error = new Error('boom');
131
- logger.error('an error occurred', { error });
132
-
133
- expect(output.print).toHaveBeenCalledWith(
134
- expect.stringContaining('Error: boom'),
135
- );
136
- });
137
- });
@@ -1,29 +0,0 @@
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
- });
@@ -1,81 +0,0 @@
1
- import { level, type LogLevel, type Log, type LogProcessor } from '@holz/core';
2
- import { timeDelta } from './time-delta';
3
-
4
- /**
5
- * A backend that pretty-prints logs to a browser console, or any
6
- * remote-attached console.
7
- */
8
- export const createConsoleBackend = (options: Options = {}): LogProcessor => {
9
- const output = options.console ?? console;
10
- let lastTimestamp: Date;
11
-
12
- return (log: Log) => {
13
- const time = new Date(log.timestamp);
14
- const { error, ...plainContext } = log.context;
15
-
16
- const segments = [
17
- {
18
- include: true,
19
- format: '%s',
20
- values: [log.message],
21
- },
22
- {
23
- include: Object.keys(plainContext).length > 0,
24
- format: '%o', // Chrome hides object content with `%O`.
25
- values: [plainContext],
26
- },
27
- {
28
- include: true,
29
- format: '%c%s',
30
- values: ['color: gray', timeDelta(time, lastTimestamp)],
31
- },
32
- {
33
- include: log.origin.length > 0,
34
- format: '%c%s',
35
- values: [
36
- 'color: rgba(128, 128, 128, 0.6); font-style: italic',
37
- log.origin.join(':'),
38
- ],
39
- },
40
- {
41
- include: error,
42
- format: '%o',
43
- values: [error],
44
- },
45
- ].filter((segment) => segment.include);
46
-
47
- const format = segments.map((segment) => segment.format).join(' ');
48
- const values = segments.flatMap<unknown>((segment) => segment.values);
49
-
50
- // Browsers have UIs for filtering by log level. Leverage that.
51
- output[sink[log.level]](format, ...values);
52
-
53
- // Track the time spent between logs.
54
- lastTimestamp = time;
55
- };
56
- };
57
-
58
- const sink: Record<LogLevel, keyof MinimalConsole> = {
59
- [level.trace]: 'trace',
60
- [level.debug]: 'debug',
61
- [level.info]: 'info',
62
- [level.warn]: 'warn',
63
- [level.error]: 'error',
64
- [level.fatal]: 'error',
65
- };
66
-
67
- interface Options {
68
- console?: MinimalConsole;
69
- }
70
-
71
- /**
72
- * A subset of the Console interface. Must support printf-style interpolation.
73
- * @see https://console.spec.whatwg.org/#formatting-specifiers
74
- *
75
- * Note that `fatal` has no corresponding equivalent. It will be downgraded to
76
- * `error` when printed.
77
- */
78
- export type MinimalConsole = Pick<
79
- Console,
80
- 'trace' | 'debug' | 'info' | 'warn' | 'error'
81
- >;
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { createConsoleBackend } from './console-backend';
package/src/time-delta.ts DELETED
@@ -1,33 +0,0 @@
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
- };