Skip to content

Commit a8cb4bb

Browse files
authored
Merge pull request #24 from commenthol/fix-sonic-streams
Fix sonic streams
2 parents e295bb4 + 451e6c0 commit a8cb4bb

File tree

7 files changed

+150
-18
lines changed

7 files changed

+150
-18
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@
8383
},
8484
"devDependencies": {
8585
"@babel/cli": "^7.23.9",
86-
"@babel/core": "^7.23.9",
87-
"@babel/preset-env": "^7.23.9",
86+
"@babel/core": "^7.24.0",
87+
"@babel/preset-env": "^7.24.0",
8888
"assert": "^2.1.0",
8989
"babel-loader": "^9.1.3",
9090
"c8": "^9.1.0",
@@ -97,17 +97,17 @@
9797
"karma": "^6.4.3",
9898
"karma-chrome-launcher": "^3.2.0",
9999
"karma-coverage": "^2.2.1",
100-
"karma-firefox-launcher": "^2.1.2",
100+
"karma-firefox-launcher": "^2.1.3",
101101
"karma-mocha": "^2.0.1",
102102
"karma-sourcemap-loader": "^0.4.0",
103103
"karma-spec-reporter": "^0.0.36",
104104
"karma-webpack": "^5.0.1",
105105
"mocha": "^10.3.0",
106106
"npm-run-all2": "^6.1.2",
107107
"rimraf": "^5.0.5",
108-
"rollup": "^4.12.0",
108+
"rollup": "^4.12.1",
109109
"sinon": "^17.0.1",
110-
"typescript": "^5.3.3",
110+
"typescript": "^5.4.2",
111111
"webpack": "^5.90.3",
112112
"webpack-cli": "^5.1.4"
113113
},

src/Sonic.js

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
import SonicBoom from 'sonic-boom'
22

3+
/**
4+
* @typedef {object} SonicOptions
5+
* @property {number} [minLength=4096] min output buffer length
6+
* @property {number} [timeout=1000] flush timeout in ms
7+
*/
8+
39
const noop = () => {}
410

11+
/**
12+
* @param {NodeJS.WriteStream} stream
13+
* @returns {{fd: number, path: string }}
14+
*/
15+
const streamDescriptor = (stream) => {
16+
// @ts-expect-error
17+
const { fd, path } = typeof stream === 'string' ? { path: stream } : stream
18+
return { fd, path }
19+
}
20+
521
export class Sonic {
22+
/**
23+
* @param {NodeJS.WriteStream} stream
24+
* @param {SonicOptions} opts
25+
*/
626
constructor (stream, opts = {}) {
727
const { minLength = 4096, timeout = 1000 } = opts
828
this._timer = undefined
929
this._timeout = timeout
1030

11-
// @ts-expect-error
12-
const { fd, path } = typeof stream === 'string'
13-
? { path: stream }
14-
: stream
31+
const { fd, path } = streamDescriptor(stream)
1532

1633
this.stream = new SonicBoom({ fd, dest: path, minLength, sync: true })
1734
this.stream.on('error', filterBrokenPipe.bind(null, this.stream))
@@ -21,8 +38,12 @@ export class Sonic {
2138
})
2239
}
2340

41+
/**
42+
* @param {string} data
43+
* @returns {boolean}
44+
*/
2445
write (data) {
25-
this.stream.write(data)
46+
const isWritten = this.stream.write(data)
2647

2748
if (!this._timer) {
2849
this._timer = setTimeout(() => {
@@ -31,7 +52,7 @@ export class Sonic {
3152
}, this._timeout)
3253
}
3354

34-
return true
55+
return isWritten
3556
}
3657

3758
flush () {
@@ -59,3 +80,48 @@ function filterBrokenPipe (stream, err) {
5980
}
6081
stream.removeListener('error', filterBrokenPipe)
6182
}
83+
84+
/**
85+
* maintains sonic streams by stream and options
86+
*/
87+
export class SonicStreams extends Map {
88+
/**
89+
* @param {Record<string,any>} opts
90+
* @returns {string}
91+
*/
92+
static hash (opts) {
93+
return 'sonic!' + Object.keys(opts || {})
94+
.sort()
95+
.map((key) => `${key}:${opts[key]}`)
96+
.join('!')
97+
}
98+
99+
/**
100+
* @param {NodeJS.WriteStream} stream
101+
* @param {SonicOptions} [opts]
102+
* @returns {Sonic}
103+
*/
104+
use (stream, opts = {}) {
105+
const streamHash = SonicStreams.hash(streamDescriptor(stream))
106+
const optsHash = SonicStreams.hash(opts)
107+
let streamRecord = this.get(streamHash)
108+
if (streamRecord) {
109+
const sonic = streamRecord.get(optsHash)
110+
if (sonic) {
111+
return sonic
112+
}
113+
}
114+
streamRecord =
115+
streamRecord ||
116+
(() => {
117+
const map = new Map()
118+
this.set(streamHash, map)
119+
return map
120+
})()
121+
const newSonic = new Sonic(stream, opts)
122+
streamRecord.set(optsHash, newSonic)
123+
return newSonic
124+
}
125+
}
126+
127+
export const sonicStreams = new SonicStreams()

src/node.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { LogBase } from './LogBase.js'
1717
import { wrapConsole } from './wrapConsole.js'
1818
import { wrapDebug } from './wrapDebug.js'
19-
import { Sonic } from './Sonic.js'
19+
import { Sonic, sonicStreams } from './Sonic.js'
2020
import { errSerializer } from './serializers/err.js'
2121
import { Format } from './Format.js'
2222

@@ -99,11 +99,15 @@ export class Log extends LogBase {
9999
this.color = selectColor(name, colorFn)
100100
this.levColors = levelColors(colorFn)
101101
// noop for TS
102-
this.opts = { ..._opts, ...this.opts }
102+
this.opts = {
103+
stream: process.stderr,
104+
..._opts,
105+
...this.opts
106+
}
103107
this.toJson = toJson
104108

105109
this.stream = this.opts.sonic
106-
? new Sonic(this.opts.stream, {
110+
? sonicStreams.use(this.opts.stream, {
107111
minLength: this.opts.sonicLength,
108112
timeout: this.opts.sonicFlushMs
109113
})

test/Sonic.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
import assert from 'assert'
22
import fs from 'fs'
3+
import { Log } from '../src/index.js'
34
import { Sonic } from '../src/Sonic.js'
45

6+
const defaultOpts = Log.options()
7+
8+
const reset = () => {
9+
Log.options(defaultOpts)
10+
Log.reset()
11+
}
12+
513
describe('Sonic', function () {
14+
before(reset)
15+
after(reset)
16+
17+
it('issue#23 shall reuse same sonic stream', function () {
18+
for (let i = 0; i < 50; i++) {
19+
const l = new Log(`log:${i}`, {
20+
level: 'INFO',
21+
sonic: true,
22+
sonicFlushMs: 10
23+
})
24+
l.info('hello')
25+
}
26+
})
27+
628
it('shall throw if no stream is setup', function () {
729
assert.throws(() => {
830
new Sonic() // eslint-disable-line no-new

test/node.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const reset = () => {
2222

2323
describe('Log node', function () {
2424
describe('options', function () {
25+
before(reset)
2526
after(reset)
2627

2728
it('should get default options', function () {

types/Sonic.d.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,49 @@
11
export class Sonic {
2-
constructor(stream: any, opts?: {});
2+
/**
3+
* @param {NodeJS.WriteStream} stream
4+
* @param {SonicOptions} opts
5+
*/
6+
constructor(stream: NodeJS.WriteStream, opts?: SonicOptions);
37
_timer: any;
4-
_timeout: any;
8+
_timeout: number;
59
stream: SonicBoom;
6-
write(data: any): boolean;
10+
/**
11+
* @param {string} data
12+
* @returns {boolean}
13+
*/
14+
write(data: string): boolean;
715
flush(): void;
816
destroy(): void;
917
}
18+
/**
19+
* maintains sonic streams by stream and options
20+
*/
21+
export class SonicStreams extends Map<any, any> {
22+
/**
23+
* @param {Record<string,any>} opts
24+
* @returns {string}
25+
*/
26+
static hash(opts: Record<string, any>): string;
27+
constructor();
28+
constructor(entries?: readonly (readonly [any, any])[] | null | undefined);
29+
constructor();
30+
constructor(iterable?: Iterable<readonly [any, any]> | null | undefined);
31+
/**
32+
* @param {NodeJS.WriteStream} stream
33+
* @param {SonicOptions} [opts]
34+
* @returns {Sonic}
35+
*/
36+
use(stream: NodeJS.WriteStream, opts?: SonicOptions | undefined): Sonic;
37+
}
38+
export const sonicStreams: SonicStreams;
39+
export type SonicOptions = {
40+
/**
41+
* min output buffer length
42+
*/
43+
minLength?: number | undefined;
44+
/**
45+
* flush timeout in ms
46+
*/
47+
timeout?: number | undefined;
48+
};
1049
import SonicBoom from 'sonic-boom';

types/node.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class Log extends LogBase {
8787
/**
8888
* stream writer
8989
*/
90-
stream?: NodeJS.WriteStream | undefined;
90+
stream: NodeJS.WriteStream;
9191
/**
9292
* use sonic (default for production use)
9393
*/

0 commit comments

Comments
 (0)