@fgv/ts-utils 5.1.0-30 → 5.1.0-31
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/packlets/logging/index.js +2 -0
- package/dist/packlets/logging/index.js.map +1 -1
- package/dist/packlets/logging/logger.js +16 -0
- package/dist/packlets/logging/logger.js.map +1 -1
- package/dist/packlets/logging/multiLogger.js +104 -0
- package/dist/packlets/logging/multiLogger.js.map +1 -0
- package/dist/packlets/logging/retainingLogger.js +161 -0
- package/dist/packlets/logging/retainingLogger.js.map +1 -0
- package/dist/ts-utils.d.ts +213 -1
- package/lib/packlets/logging/index.d.ts +2 -0
- package/lib/packlets/logging/index.d.ts.map +1 -1
- package/lib/packlets/logging/index.js +2 -0
- package/lib/packlets/logging/index.js.map +1 -1
- package/lib/packlets/logging/logger.d.ts +13 -0
- package/lib/packlets/logging/logger.d.ts.map +1 -1
- package/lib/packlets/logging/logger.js +16 -0
- package/lib/packlets/logging/logger.js.map +1 -1
- package/lib/packlets/logging/multiLogger.d.ts +54 -0
- package/lib/packlets/logging/multiLogger.d.ts.map +1 -0
- package/lib/packlets/logging/multiLogger.js +108 -0
- package/lib/packlets/logging/multiLogger.js.map +1 -0
- package/lib/packlets/logging/retainingLogger.d.ts +143 -0
- package/lib/packlets/logging/retainingLogger.d.ts.map +1 -0
- package/lib/packlets/logging/retainingLogger.js +165 -0
- package/lib/packlets/logging/retainingLogger.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { MessageLogLevel, Success } from '../base';
|
|
2
|
+
import { LoggerBase, ReporterLogLevel } from './logger';
|
|
3
|
+
/**
|
|
4
|
+
* A retained log record. Preserves the level and an ordering cursor so consumers
|
|
5
|
+
* can filter by severity and page incrementally.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export interface ILogRecord {
|
|
9
|
+
/**
|
|
10
|
+
* Monotonic 1-based sequence number, stable across ring eviction.
|
|
11
|
+
*/
|
|
12
|
+
readonly seq: number;
|
|
13
|
+
/**
|
|
14
|
+
* Milliseconds since epoch when the record was logged (from the injected clock).
|
|
15
|
+
*/
|
|
16
|
+
readonly timestamp: number;
|
|
17
|
+
/**
|
|
18
|
+
* The level the record was logged at.
|
|
19
|
+
*/
|
|
20
|
+
readonly level: MessageLogLevel;
|
|
21
|
+
/**
|
|
22
|
+
* The formatted message (same formatting {@link Logging.LoggerBase._format | LoggerBase._format} produces).
|
|
23
|
+
*/
|
|
24
|
+
readonly message: string;
|
|
25
|
+
/**
|
|
26
|
+
* The raw structured inputs `[message, ...parameters]` before formatting.
|
|
27
|
+
*/
|
|
28
|
+
readonly args?: readonly unknown[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Options for {@link Logging.RetainingLogger.getRecords | RetainingLogger.getRecords}.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export interface IGetRecordsOptions {
|
|
35
|
+
/**
|
|
36
|
+
* If supplied, only records whose level would be emitted at this threshold
|
|
37
|
+
* (per {@link Logging.shouldLog | shouldLog}) are returned.
|
|
38
|
+
*/
|
|
39
|
+
readonly minLevel?: ReporterLogLevel;
|
|
40
|
+
/**
|
|
41
|
+
* If supplied, only records with `seq > sinceSeq` are returned, enabling
|
|
42
|
+
* incremental paging from a previously-held cursor.
|
|
43
|
+
*/
|
|
44
|
+
readonly sinceSeq?: number;
|
|
45
|
+
/**
|
|
46
|
+
* If supplied, returns at most this many records — the most recent N (tail),
|
|
47
|
+
* still ordered oldest-first.
|
|
48
|
+
*/
|
|
49
|
+
readonly limit?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* An {@link Logging.ILogger | ILogger} that retains structured log records in a bounded
|
|
53
|
+
* most-recent-N ring, with a query API supporting min-severity filtering and
|
|
54
|
+
* since-cursor incremental paging.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* Records are captured via the {@link Logging.LoggerBase._logStructured | _logStructured} hook,
|
|
58
|
+
* so the full structured form (`level` + formatted `message` + raw `args`) is retained.
|
|
59
|
+
* The logger emits nowhere itself — pair it with a {@link Logging.MultiLogger | MultiLogger}
|
|
60
|
+
* to also feed a {@link Logging.ConsoleLogger | ConsoleLogger} or other durable sink.
|
|
61
|
+
*
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
export declare class RetainingLogger extends LoggerBase {
|
|
65
|
+
/**
|
|
66
|
+
* The fixed ring capacity — the maximum number of records retained before the
|
|
67
|
+
* oldest is overwritten. A positive integer (see the constructor normalization).
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
private readonly _capacity;
|
|
71
|
+
/**
|
|
72
|
+
* Injected clock used to timestamp records.
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
private readonly _now;
|
|
76
|
+
/**
|
|
77
|
+
* The backing store, used as a circular buffer. Grows lazily up to `_capacity`,
|
|
78
|
+
* after which records overwrite the oldest slot in place — eviction is O(1), with
|
|
79
|
+
* no `shift()` re-indexing regardless of capacity.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
private _buffer;
|
|
83
|
+
/**
|
|
84
|
+
* Index of the oldest retained record within `_buffer` once the ring is full.
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
private _head;
|
|
88
|
+
/**
|
|
89
|
+
* The number of valid records currently retained (`<= _capacity`).
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
private _count;
|
|
93
|
+
/**
|
|
94
|
+
* The most recently assigned sequence number; monotonic, stable across eviction
|
|
95
|
+
* and {@link Logging.RetainingLogger.clear | clear()}.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
private _lastSeq;
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new retaining logger.
|
|
101
|
+
* @param logLevel - The level of logging to retain. Defaults to `'detail'` to capture
|
|
102
|
+
* broadly and filter at query time.
|
|
103
|
+
* @param maxRecords - The maximum number of records to retain. Defaults to `1000`.
|
|
104
|
+
* @param now - Injected clock returning milliseconds since epoch. Defaults to `Date.now`.
|
|
105
|
+
*/
|
|
106
|
+
constructor(logLevel?: ReporterLogLevel, maxRecords?: number, now?: () => number);
|
|
107
|
+
/**
|
|
108
|
+
* The retained records, oldest-first.
|
|
109
|
+
*/
|
|
110
|
+
get records(): ReadonlyArray<ILogRecord>;
|
|
111
|
+
/**
|
|
112
|
+
* The most recently assigned sequence number. A client can hold this value and
|
|
113
|
+
* pass it as `sinceSeq` to page only records logged afterward.
|
|
114
|
+
*/
|
|
115
|
+
get lastSeq(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Returns retained records, oldest-first, optionally filtered.
|
|
118
|
+
* @param options - {@link Logging.IGetRecordsOptions | Filtering and paging options}.
|
|
119
|
+
* @returns The matching records, oldest-first.
|
|
120
|
+
*/
|
|
121
|
+
getRecords(options?: IGetRecordsOptions): ReadonlyArray<ILogRecord>;
|
|
122
|
+
/**
|
|
123
|
+
* Clears all retained records. Does NOT reset the sequence counter, so a client
|
|
124
|
+
* holding a `sinceSeq` cursor never re-sees a sequence number.
|
|
125
|
+
*/
|
|
126
|
+
clear(): void;
|
|
127
|
+
/**
|
|
128
|
+
* Materializes the ring into a plain oldest-first array.
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
private _toOrderedArray;
|
|
132
|
+
/**
|
|
133
|
+
* {@inheritDoc Logging.LoggerBase._logStructured}
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
protected _logStructured(level: MessageLogLevel, formatted: string, message: unknown, parameters: readonly unknown[]): void;
|
|
137
|
+
/**
|
|
138
|
+
* {@inheritDoc Logging.LoggerBase._log}
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
protected _log(message: string, __level: MessageLogLevel): Success<string | undefined>;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=retainingLogger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retainingLogger.d.ts","sourceRoot":"","sources":["../../../src/packlets/logging/retainingLogger.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAW,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAa,MAAM,UAAU,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IAEpC;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAoB;IAEnC;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAa;IAE1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAa;IAE3B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAa;IAE7B;;;;;;OAMG;gBAED,QAAQ,GAAE,gBAA2B,EACrC,UAAU,GAAE,MAAa,EACzB,GAAG,GAAE,MAAM,MAAyB;IAUtC;;OAEG;IACH,IAAW,OAAO,IAAI,aAAa,CAAC,UAAU,CAAC,CAE9C;IAED;;;OAGG;IACH,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED;;;;OAIG;IACI,UAAU,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAAC,UAAU,CAAC;IAgB1E;;;OAGG;IACI,KAAK,IAAI,IAAI;IAMpB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH,SAAS,CAAC,cAAc,CACtB,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,SAAS,OAAO,EAAE,GAC7B,IAAI;IAoBP;;;OAGG;IACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAGvF"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetainingLogger = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2020 Erik Fortune
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
const base_1 = require("../base");
|
|
26
|
+
const logger_1 = require("./logger");
|
|
27
|
+
/**
|
|
28
|
+
* An {@link Logging.ILogger | ILogger} that retains structured log records in a bounded
|
|
29
|
+
* most-recent-N ring, with a query API supporting min-severity filtering and
|
|
30
|
+
* since-cursor incremental paging.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Records are captured via the {@link Logging.LoggerBase._logStructured | _logStructured} hook,
|
|
34
|
+
* so the full structured form (`level` + formatted `message` + raw `args`) is retained.
|
|
35
|
+
* The logger emits nowhere itself — pair it with a {@link Logging.MultiLogger | MultiLogger}
|
|
36
|
+
* to also feed a {@link Logging.ConsoleLogger | ConsoleLogger} or other durable sink.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
class RetainingLogger extends logger_1.LoggerBase {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new retaining logger.
|
|
43
|
+
* @param logLevel - The level of logging to retain. Defaults to `'detail'` to capture
|
|
44
|
+
* broadly and filter at query time.
|
|
45
|
+
* @param maxRecords - The maximum number of records to retain. Defaults to `1000`.
|
|
46
|
+
* @param now - Injected clock returning milliseconds since epoch. Defaults to `Date.now`.
|
|
47
|
+
*/
|
|
48
|
+
constructor(logLevel = 'detail', maxRecords = 1000, now = () => Date.now()) {
|
|
49
|
+
super(logLevel);
|
|
50
|
+
/**
|
|
51
|
+
* The backing store, used as a circular buffer. Grows lazily up to `_capacity`,
|
|
52
|
+
* after which records overwrite the oldest slot in place — eviction is O(1), with
|
|
53
|
+
* no `shift()` re-indexing regardless of capacity.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
this._buffer = [];
|
|
57
|
+
/**
|
|
58
|
+
* Index of the oldest retained record within `_buffer` once the ring is full.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
this._head = 0;
|
|
62
|
+
/**
|
|
63
|
+
* The number of valid records currently retained (`<= _capacity`).
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
this._count = 0;
|
|
67
|
+
/**
|
|
68
|
+
* The most recently assigned sequence number; monotonic, stable across eviction
|
|
69
|
+
* and {@link Logging.RetainingLogger.clear | clear()}.
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
this._lastSeq = 0;
|
|
73
|
+
// The ring requires a positive integer capacity to be well-defined; a non-finite
|
|
74
|
+
// or sub-1 value would make the modular index arithmetic meaningless, so floor it
|
|
75
|
+
// to a sane minimum of 1 rather than crash on degenerate input.
|
|
76
|
+
this._capacity = Number.isFinite(maxRecords) && maxRecords >= 1 ? Math.floor(maxRecords) : 1;
|
|
77
|
+
this._now = now;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The retained records, oldest-first.
|
|
81
|
+
*/
|
|
82
|
+
get records() {
|
|
83
|
+
return this._toOrderedArray();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The most recently assigned sequence number. A client can hold this value and
|
|
87
|
+
* pass it as `sinceSeq` to page only records logged afterward.
|
|
88
|
+
*/
|
|
89
|
+
get lastSeq() {
|
|
90
|
+
return this._lastSeq;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Returns retained records, oldest-first, optionally filtered.
|
|
94
|
+
* @param options - {@link Logging.IGetRecordsOptions | Filtering and paging options}.
|
|
95
|
+
* @returns The matching records, oldest-first.
|
|
96
|
+
*/
|
|
97
|
+
getRecords(options) {
|
|
98
|
+
let result = this._toOrderedArray();
|
|
99
|
+
if ((options === null || options === void 0 ? void 0 : options.minLevel) !== undefined) {
|
|
100
|
+
const minLevel = options.minLevel;
|
|
101
|
+
result = result.filter((r) => (0, logger_1.shouldLog)(r.level, minLevel));
|
|
102
|
+
}
|
|
103
|
+
if ((options === null || options === void 0 ? void 0 : options.sinceSeq) !== undefined) {
|
|
104
|
+
const sinceSeq = options.sinceSeq;
|
|
105
|
+
result = result.filter((r) => r.seq > sinceSeq);
|
|
106
|
+
}
|
|
107
|
+
if ((options === null || options === void 0 ? void 0 : options.limit) !== undefined && result.length > options.limit) {
|
|
108
|
+
result = result.slice(result.length - options.limit);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Clears all retained records. Does NOT reset the sequence counter, so a client
|
|
114
|
+
* holding a `sinceSeq` cursor never re-sees a sequence number.
|
|
115
|
+
*/
|
|
116
|
+
clear() {
|
|
117
|
+
this._buffer = [];
|
|
118
|
+
this._head = 0;
|
|
119
|
+
this._count = 0;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Materializes the ring into a plain oldest-first array.
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
_toOrderedArray() {
|
|
126
|
+
const result = [];
|
|
127
|
+
for (let i = 0; i < this._count; i++) {
|
|
128
|
+
result.push(this._buffer[(this._head + i) % this._capacity]);
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* {@inheritDoc Logging.LoggerBase._logStructured}
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
_logStructured(level, formatted, message, parameters) {
|
|
137
|
+
this._lastSeq += 1;
|
|
138
|
+
const record = {
|
|
139
|
+
seq: this._lastSeq,
|
|
140
|
+
timestamp: this._now(),
|
|
141
|
+
level,
|
|
142
|
+
message: formatted,
|
|
143
|
+
args: [message, ...parameters]
|
|
144
|
+
};
|
|
145
|
+
if (this._count < this._capacity) {
|
|
146
|
+
// Still filling: append to the next free slot (head stays 0 during this phase).
|
|
147
|
+
this._buffer.push(record);
|
|
148
|
+
this._count += 1;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Full: overwrite the oldest record in place and advance the head.
|
|
152
|
+
this._buffer[this._head] = record;
|
|
153
|
+
this._head = (this._head + 1) % this._capacity;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* {@inheritDoc Logging.LoggerBase._log}
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
_log(message, __level) {
|
|
161
|
+
return (0, base_1.succeed)(message);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.RetainingLogger = RetainingLogger;
|
|
165
|
+
//# sourceMappingURL=retainingLogger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retainingLogger.js","sourceRoot":"","sources":["../../../src/packlets/logging/retainingLogger.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2020 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\nimport { MessageLogLevel, Success, succeed } from '../base';\nimport { LoggerBase, ReporterLogLevel, shouldLog } from './logger';\n\n/**\n * A retained log record. Preserves the level and an ordering cursor so consumers\n * can filter by severity and page incrementally.\n * @public\n */\nexport interface ILogRecord {\n /**\n * Monotonic 1-based sequence number, stable across ring eviction.\n */\n readonly seq: number;\n /**\n * Milliseconds since epoch when the record was logged (from the injected clock).\n */\n readonly timestamp: number;\n /**\n * The level the record was logged at.\n */\n readonly level: MessageLogLevel;\n /**\n * The formatted message (same formatting {@link Logging.LoggerBase._format | LoggerBase._format} produces).\n */\n readonly message: string;\n /**\n * The raw structured inputs `[message, ...parameters]` before formatting.\n */\n readonly args?: readonly unknown[];\n}\n\n/**\n * Options for {@link Logging.RetainingLogger.getRecords | RetainingLogger.getRecords}.\n * @public\n */\nexport interface IGetRecordsOptions {\n /**\n * If supplied, only records whose level would be emitted at this threshold\n * (per {@link Logging.shouldLog | shouldLog}) are returned.\n */\n readonly minLevel?: ReporterLogLevel;\n /**\n * If supplied, only records with `seq > sinceSeq` are returned, enabling\n * incremental paging from a previously-held cursor.\n */\n readonly sinceSeq?: number;\n /**\n * If supplied, returns at most this many records — the most recent N (tail),\n * still ordered oldest-first.\n */\n readonly limit?: number;\n}\n\n/**\n * An {@link Logging.ILogger | ILogger} that retains structured log records in a bounded\n * most-recent-N ring, with a query API supporting min-severity filtering and\n * since-cursor incremental paging.\n *\n * @remarks\n * Records are captured via the {@link Logging.LoggerBase._logStructured | _logStructured} hook,\n * so the full structured form (`level` + formatted `message` + raw `args`) is retained.\n * The logger emits nowhere itself — pair it with a {@link Logging.MultiLogger | MultiLogger}\n * to also feed a {@link Logging.ConsoleLogger | ConsoleLogger} or other durable sink.\n *\n * @public\n */\nexport class RetainingLogger extends LoggerBase {\n /**\n * The fixed ring capacity — the maximum number of records retained before the\n * oldest is overwritten. A positive integer (see the constructor normalization).\n * @internal\n */\n private readonly _capacity: number;\n\n /**\n * Injected clock used to timestamp records.\n * @internal\n */\n private readonly _now: () => number;\n\n /**\n * The backing store, used as a circular buffer. Grows lazily up to `_capacity`,\n * after which records overwrite the oldest slot in place — eviction is O(1), with\n * no `shift()` re-indexing regardless of capacity.\n * @internal\n */\n private _buffer: ILogRecord[] = [];\n\n /**\n * Index of the oldest retained record within `_buffer` once the ring is full.\n * @internal\n */\n private _head: number = 0;\n\n /**\n * The number of valid records currently retained (`<= _capacity`).\n * @internal\n */\n private _count: number = 0;\n\n /**\n * The most recently assigned sequence number; monotonic, stable across eviction\n * and {@link Logging.RetainingLogger.clear | clear()}.\n * @internal\n */\n private _lastSeq: number = 0;\n\n /**\n * Creates a new retaining logger.\n * @param logLevel - The level of logging to retain. Defaults to `'detail'` to capture\n * broadly and filter at query time.\n * @param maxRecords - The maximum number of records to retain. Defaults to `1000`.\n * @param now - Injected clock returning milliseconds since epoch. Defaults to `Date.now`.\n */\n public constructor(\n logLevel: ReporterLogLevel = 'detail',\n maxRecords: number = 1000,\n now: () => number = () => Date.now()\n ) {\n super(logLevel);\n // The ring requires a positive integer capacity to be well-defined; a non-finite\n // or sub-1 value would make the modular index arithmetic meaningless, so floor it\n // to a sane minimum of 1 rather than crash on degenerate input.\n this._capacity = Number.isFinite(maxRecords) && maxRecords >= 1 ? Math.floor(maxRecords) : 1;\n this._now = now;\n }\n\n /**\n * The retained records, oldest-first.\n */\n public get records(): ReadonlyArray<ILogRecord> {\n return this._toOrderedArray();\n }\n\n /**\n * The most recently assigned sequence number. A client can hold this value and\n * pass it as `sinceSeq` to page only records logged afterward.\n */\n public get lastSeq(): number {\n return this._lastSeq;\n }\n\n /**\n * Returns retained records, oldest-first, optionally filtered.\n * @param options - {@link Logging.IGetRecordsOptions | Filtering and paging options}.\n * @returns The matching records, oldest-first.\n */\n public getRecords(options?: IGetRecordsOptions): ReadonlyArray<ILogRecord> {\n let result: ReadonlyArray<ILogRecord> = this._toOrderedArray();\n if (options?.minLevel !== undefined) {\n const minLevel = options.minLevel;\n result = result.filter((r) => shouldLog(r.level, minLevel));\n }\n if (options?.sinceSeq !== undefined) {\n const sinceSeq = options.sinceSeq;\n result = result.filter((r) => r.seq > sinceSeq);\n }\n if (options?.limit !== undefined && result.length > options.limit) {\n result = result.slice(result.length - options.limit);\n }\n return result;\n }\n\n /**\n * Clears all retained records. Does NOT reset the sequence counter, so a client\n * holding a `sinceSeq` cursor never re-sees a sequence number.\n */\n public clear(): void {\n this._buffer = [];\n this._head = 0;\n this._count = 0;\n }\n\n /**\n * Materializes the ring into a plain oldest-first array.\n * @internal\n */\n private _toOrderedArray(): ILogRecord[] {\n const result: ILogRecord[] = [];\n for (let i = 0; i < this._count; i++) {\n result.push(this._buffer[(this._head + i) % this._capacity]);\n }\n return result;\n }\n\n /**\n * {@inheritDoc Logging.LoggerBase._logStructured}\n * @internal\n */\n protected _logStructured(\n level: MessageLogLevel,\n formatted: string,\n message: unknown,\n parameters: readonly unknown[]\n ): void {\n this._lastSeq += 1;\n const record: ILogRecord = {\n seq: this._lastSeq,\n timestamp: this._now(),\n level,\n message: formatted,\n args: [message, ...parameters]\n };\n if (this._count < this._capacity) {\n // Still filling: append to the next free slot (head stays 0 during this phase).\n this._buffer.push(record);\n this._count += 1;\n } else {\n // Full: overwrite the oldest record in place and advance the head.\n this._buffer[this._head] = record;\n this._head = (this._head + 1) % this._capacity;\n }\n }\n\n /**\n * {@inheritDoc Logging.LoggerBase._log}\n * @internal\n */\n protected _log(message: string, __level: MessageLogLevel): Success<string | undefined> {\n return succeed(message);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-utils",
|
|
3
|
-
"version": "5.1.0-
|
|
3
|
+
"version": "5.1.0-31",
|
|
4
4
|
"description": "Assorted Typescript Utilities",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@rushstack/heft-jest-plugin": "1.2.6",
|
|
55
55
|
"eslint-plugin-tsdoc": "~0.5.2",
|
|
56
56
|
"typedoc": "~0.28.16",
|
|
57
|
-
"@fgv/heft-dual-rig": "5.1.0-
|
|
58
|
-
"@fgv/typedoc-compact-theme": "5.1.0-
|
|
57
|
+
"@fgv/heft-dual-rig": "5.1.0-31",
|
|
58
|
+
"@fgv/typedoc-compact-theme": "5.1.0-31"
|
|
59
59
|
},
|
|
60
60
|
"repository": {
|
|
61
61
|
"type": "git",
|