@logtape/logtape 0.1.0-dev.9 → 0.1.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/README.md CHANGED
@@ -3,16 +3,357 @@
3
3
  LogTape
4
4
  =======
5
5
 
6
+ [![JSR][JSR badge]][JSR]
7
+ [![npm][npm badge]][npm]
6
8
  [![GitHub Actions][GitHub Actions badge]][GitHub Actions]
7
9
  [![Codecov][Codecov badge]][Codecov]
8
10
 
11
+ > [!NOTE]
12
+ > LogTape is still in the early stage of development. The API is not stable
13
+ > yet. Please be careful when using it in production.
14
+
9
15
  LogTape is a simple logging library for Deno/Node.js/Bun/browsers. It is
10
16
  designed to be used for both applications and libraries.
11
17
 
12
18
  Currently, LogTape provides only few sinks, but you can easily add your own
13
19
  sinks.
14
20
 
21
+ ![](./screenshots/web-console.png)
22
+ ![](./screenshots/terminal-console.png)
23
+
24
+ [JSR]: https://jsr.io/@logtape/logtape
25
+ [JSR badge]: https://jsr.io/badges/@logtape/logtape
26
+ [npm]: https://www.npmjs.com/package/@logtape/logtape
27
+ [npm badge]: https://img.shields.io/npm/v/@logtape/logtape?logo=npm
15
28
  [GitHub Actions]: https://github.com/dahlia/logtape/actions/workflows/main.yaml
16
29
  [GitHub Actions badge]: https://github.com/dahlia/logtape/actions/workflows/main.yaml/badge.svg
17
30
  [Codecov]: https://codecov.io/gh/dahlia/logtape
18
31
  [Codecov badge]: https://codecov.io/gh/dahlia/logtape/graph/badge.svg?token=yOejfcuX7r
32
+
33
+
34
+ Installation
35
+ ------------
36
+
37
+ ### Deno
38
+
39
+ ~~~~ sh
40
+ deno add @logtape/logtape
41
+ ~~~~
42
+
43
+ ### Node.js
44
+
45
+ ~~~~ sh
46
+ npm add @logtape/logtape
47
+ ~~~~
48
+
49
+ ### Bun
50
+
51
+ ~~~~ sh
52
+ bun add @logtape/logtape
53
+ ~~~~
54
+
55
+
56
+ Quick start
57
+ -----------
58
+
59
+ Set up LogTape in the entry point of your application (if you are composing
60
+ a library, you should not set up LogTape in the library itself; it is up to
61
+ the application to set up LogTape):
62
+
63
+ ~~~~ typescript
64
+ import { configure, getConsoleSink } from "@logtape/logtape";
65
+
66
+ configure({
67
+ sinks: { console: getConsoleSink() },
68
+ filters: {},
69
+ loggers: [
70
+ { category: "my-app", level: "debug", sinks: ["console"] }
71
+ ]
72
+ });
73
+ ~~~~
74
+
75
+ And then you can use LogTape in your application or library:
76
+
77
+ ~~~~ typescript
78
+ import { getLogger } from "@logtape/logtape";
79
+
80
+ const logger = getLogger(["my-app", "my-module"]);
81
+
82
+ export function myFunc(value: number): void {
83
+ logger.debug `Hello, ${value}!`;
84
+ }
85
+ ~~~~
86
+
87
+
88
+ How to log
89
+ ----------
90
+
91
+ There are total 5 log levels: `debug`, `info`, `warning`, `error`, `fatal` (in
92
+ the order of verbosity). You can log messages with the following syntax:
93
+
94
+ ~~~~ typescript
95
+ logger.debug `This is a debug message with ${value}.`;
96
+ logger.info `This is an info message with ${value}.`;
97
+ logger.warn `This is a warning message with ${value}.`;
98
+ logger.error `This is an error message with ${value}.`;
99
+ logger.fatal `This is a fatal message with ${value}.`;
100
+ ~~~~
101
+
102
+ You can also log messages with a function call:
103
+
104
+ ~~~~ typescript
105
+ logger.debug("This is a debug message with {value}.", { value });
106
+ logger.info("This is an info message with {value}.", { value });
107
+ logger.warn("This is a warning message with {value}.", { value });
108
+ logger.error("This is an error message with {value}.", { value });
109
+ logger.fatal("This is a fatal message with {value}.", { value });
110
+ ~~~~
111
+
112
+ Sometimes, values to be logged are expensive to compute. In such cases, you
113
+ can use a function to defer the computation so that it is only computed when
114
+ the log message is actually logged:
115
+
116
+ ~~~~ typescript
117
+ logger.debug(l => l`This is a debug message with ${computeValue()}.`);
118
+ logger.debug("Or you can use a function call: {value}.", () => {
119
+ return { value: computeValue() };
120
+ });
121
+ ~~~~
122
+
123
+
124
+ Categories
125
+ ----------
126
+
127
+ LogTape uses a hierarchical category system to manage loggers. A category is
128
+ a list of strings. For example, `["my-app", "my-module"]` is a category.
129
+
130
+ When you log a message, it is dispatched to all loggers whose categories are
131
+ prefixes of the category of the logger. For example, if you log a message
132
+ with the category `["my-app", "my-module", "my-submodule"]`, it is dispatched
133
+ to loggers whose categories are `["my-app", "my-module"]` and `["my-app"]`.
134
+
135
+ This behavior allows you to control the verbosity of log messages by setting
136
+ the log level of loggers at different levels of the category hierarchy.
137
+
138
+ Here's an example of setting log levels for different categories:
139
+
140
+ ~~~~ typescript
141
+ import { configure, getConsoleSink } from "@logtape/logtape";
142
+
143
+ configure({
144
+ sinks: {
145
+ console: getConsoleSink(),
146
+ },
147
+ filters: {},
148
+ loggers: [
149
+ { category: ["my-app"], level: "info", sinks: ["console"] },
150
+ { category: ["my-app", "my-module"], level: "debug", sinks: ["console"] },
151
+ ],
152
+ })
153
+ ~~~~
154
+
155
+
156
+ Sinks
157
+ -----
158
+
159
+ A sink is a destination of log messages. LogTape currently provides a few
160
+ sinks: console and stream. However, you can easily add your own sinks.
161
+ The signature of a sink is:
162
+
163
+ ~~~~ typescript
164
+ export type Sink = (record: LogRecord) => void;
165
+ ~~~~
166
+
167
+ Here's a simple example of a sink that writes log messages to console:
168
+
169
+ ~~~~ typescript
170
+ import { configure } from "@logtape/logtape";
171
+
172
+ configure({
173
+ sinks: {
174
+ console(record) {
175
+ console.log(record.message);
176
+ }
177
+ },
178
+ // Omitted for brevity
179
+ });
180
+ ~~~~
181
+
182
+ ### Console sink
183
+
184
+ Of course, you don't have to implement your own console sink because LogTape
185
+ provides a console sink:
186
+
187
+ ~~~~ typescript
188
+ import { configure, getConsoleSink } from "@logtape/logtape";
189
+
190
+ configure({
191
+ sinks: {
192
+ console: getConsoleSink(),
193
+ },
194
+ // Omitted for brevity
195
+ });
196
+ ~~~~
197
+
198
+ See also [`getConsoleSink()`] function and [`ConsoleSinkOptions`] interface
199
+ in the API reference for more details.
200
+
201
+ [`getConsoleSink()`]: https://jsr.io/@logtape/logtape/doc/~/getConsoleSink
202
+ [`ConsoleSinkOptions`]: https://jsr.io/@logtape/logtape/doc/~/ConsoleSinkOptions
203
+
204
+ ### Stream sink
205
+
206
+ Another built-in sink is a stream sink. It writes log messages to
207
+ a [`WritableStream`]. Here's an example of a stream sink that writes log
208
+ messages to the standard error:
209
+
210
+ ~~~~ typescript
211
+ // Deno:
212
+ configure({
213
+ sinks: {
214
+ stream: getStreamSink(Deno.stderr.writable),
215
+ },
216
+ // Omitted for brevity
217
+ });
218
+ ~~~~
219
+
220
+ ~~~~ typescript
221
+ // Node.js:
222
+ import stream from "node:stream";
223
+
224
+ configure({
225
+ sinks: {
226
+ stream: getStreamSink(stream.Writable.toWeb(process.stderr)),
227
+ },
228
+ // Omitted for brevity
229
+ });
230
+ ~~~~
231
+
232
+ > [!NOTE]
233
+ > Here we use `WritableStream` from the Web Streams API. If you are using
234
+ > Node.js, you cannot directly pass `process.stderr` to `getStreamSink` because
235
+ > `process.stderr` is not a `WritableStream` but a [`Writable`], which is a
236
+ > Node.js stream. You can use [`Writable.toWeb()`] method to convert a Node.js
237
+ > stream to a `WritableStream`.
238
+
239
+ See also [`getStreamSink()`] function and [`StreamSinkOptions`] interface
240
+ in the API reference for more details.
241
+
242
+ [`WritableStream`]: https://developer.mozilla.org/en-US/docs/Web/API/WritableStream
243
+ [`Writable`]: https://nodejs.org/api/stream.html#class-streamwritable
244
+ [`Writable.toWeb()`]: https://nodejs.org/api/stream.html#streamwritabletowebstreamwritable
245
+ [`getStreamSink()`]: https://jsr.io/@logtape/logtape/doc/~/getStreamSink
246
+ [`StreamSinkOptions`]: https://jsr.io/@logtape/logtape/doc/~/StreamSinkOptions
247
+
248
+ ### File sink
249
+
250
+ > [!NOTE]
251
+ > File sink is unavailable in the browser environment.
252
+
253
+ LogTape provides a file sink as well. Here's an example of a file sink that
254
+ writes log messages to a file:
255
+
256
+ ~~~~ typescript
257
+ import { getFileSink } from "@logtape/logtape";
258
+
259
+ configure({
260
+ sinks: {
261
+ file: getFileSink("my-app.log"),
262
+ },
263
+ // Omitted for brevity
264
+ });
265
+ ~~~~
266
+
267
+ See also [`getFileSink()`] function and [`FileSinkOptions`] interface
268
+ in the API reference for more details.
269
+
270
+ [`getFileSink()`]: https://jsr.io/@logtape/logtape/doc/~/getFileSink
271
+ [`FileSinkOptions`]: https://jsr.io/@logtape/logtape/doc/~/FileSinkOptions
272
+
273
+ ### Text formatter
274
+
275
+ A stream sink and a file sink write log messages in a plain text format.
276
+ You can customize the format by providing a text formatter. The type of a
277
+ text formatter is:
278
+
279
+ ~~~~ typescript
280
+ export type TextFormatter = (record: LogRecord) => string;
281
+ ~~~~
282
+
283
+ Here's an example of a text formatter that writes log messages in a JSON format:
284
+
285
+ ~~~~ typescript
286
+ configure({
287
+ sinks: {
288
+ stream: getStreamSink(Deno.stderr.writable, {
289
+ formatter: JSON.stringify,
290
+ }),
291
+ },
292
+ // Omitted for brevity
293
+ })
294
+ ~~~~
295
+
296
+ ### Disposable sink
297
+
298
+ A disposable sink is a sink that can be disposed of. They are automatically
299
+ disposed of when the configuration is reset or the program exits. The type
300
+ of a disposable sink is: `Sink & Disposable`. You can create a disposable
301
+ sink by defining a `[Symbol.dispose]` method:
302
+
303
+ ~~~~ typescript
304
+ const disposableSink: Sink & Disposable = (record: LogRecord) => {
305
+ console.log(record.message);
306
+ };
307
+ disposableSink[Symbol.dispose] = () => {
308
+ console.log("Disposed!");
309
+ };
310
+ ~~~~
311
+
312
+
313
+ Testing
314
+ -------
315
+
316
+ Here are some tips for testing your application or library with LogTape.
317
+
318
+ ### Reset configuration
319
+
320
+ You can reset the configuration of LogTape to its initial state. This is
321
+ useful when you want to reset the configuration between tests. For example,
322
+ the following code shows how to reset the configuration after a test
323
+ (regardless of whether the test passes or fails) in Deno:
324
+
325
+ ~~~~ typescript
326
+ import { configure, reset } from "@logtape/logtape";
327
+
328
+ Deno.test("my test", async (t) => {
329
+ await t.step("set up", () => {
330
+ configure({ /* ... */ });
331
+ });
332
+
333
+ await t.step("run test", () => {
334
+ // Run the test
335
+ });
336
+
337
+ await t.step("tear down", () => {
338
+ reset();
339
+ });
340
+ });
341
+ ~~~~
342
+
343
+ ### Buffer sink
344
+
345
+ For testing purposes, you may want to collect log messages in memory. Although
346
+ LogTape does not provide a built-in buffer sink, you can easily implement it:
347
+
348
+ ~~~~ typescript
349
+ import { type LogRecord, configure } from "@logtape/logtape";
350
+
351
+ const buffer: LogRecord[] = [];
352
+
353
+ configure({
354
+ sinks: {
355
+ buffer: buffer.push.bind(buffer),
356
+ },
357
+ // Omitted for brevity
358
+ });
359
+ ~~~~
package/esm/config.js CHANGED
@@ -1,10 +1,21 @@
1
+ import * as dntShim from "./_dnt.shims.js";
1
2
  import { toFilter } from "./filter.js";
2
3
  import { LoggerImpl } from "./logger.js";
3
4
  import { getConsoleSink } from "./sink.js";
5
+ /**
6
+ * Whether the loggers are configured.
7
+ */
4
8
  let configured = false;
9
+ /**
10
+ * Disposables to dispose when resetting the configuration.
11
+ */
12
+ const disposables = new Set();
5
13
  /**
6
14
  * Configure the loggers with the specified configuration.
7
15
  *
16
+ * Note that if the given sinks or filters are disposable, they will be
17
+ * disposed when the configuration is reset, or when the process exits.
18
+ *
8
19
  * @example
9
20
  * ```typescript
10
21
  * configure({
@@ -42,8 +53,8 @@ export function configure(config) {
42
53
  if (configured && !config.reset) {
43
54
  throw new ConfigError("Already configured; if you want to reset, turn on the reset flag.");
44
55
  }
56
+ reset();
45
57
  configured = true;
46
- LoggerImpl.getLogger([]).resetDescendants();
47
58
  let metaConfigured = false;
48
59
  for (const cfg of config.loggers) {
49
60
  if (cfg.category.length === 0 ||
@@ -73,6 +84,20 @@ export function configure(config) {
73
84
  logger.filters.push(toFilter(filter));
74
85
  }
75
86
  }
87
+ for (const sink of Object.values(config.sinks)) {
88
+ if (Symbol.dispose in sink)
89
+ disposables.add(sink);
90
+ }
91
+ for (const filter of Object.values(config.filters)) {
92
+ if (filter != null && typeof filter !== "string" && Symbol.dispose in filter)
93
+ disposables.add(filter);
94
+ }
95
+ if ("process" in dntShim.dntGlobalThis) { // @ts-ignore: It's fine to use process in Node
96
+ process.on("exit", dispose);
97
+ }
98
+ else { // @ts-ignore: It's fine to addEventListener() on the browser/Deno
99
+ addEventListener("unload", dispose);
100
+ }
76
101
  const meta = LoggerImpl.getLogger(["logtape", "meta"]);
77
102
  if (!metaConfigured) {
78
103
  meta.sinks.push(getConsoleSink());
@@ -90,9 +115,18 @@ export function configure(config) {
90
115
  * Reset the configuration. Mostly for testing purposes.
91
116
  */
92
117
  export function reset() {
118
+ dispose();
93
119
  LoggerImpl.getLogger([]).resetDescendants();
94
120
  configured = false;
95
121
  }
122
+ /**
123
+ * Dispose of the disposables.
124
+ */
125
+ export function dispose() {
126
+ for (const disposable of disposables)
127
+ disposable[Symbol.dispose]();
128
+ disposables.clear();
129
+ }
96
130
  /**
97
131
  * A configuration error.
98
132
  */
@@ -0,0 +1,32 @@
1
+ import * as dntShim from "./_dnt.shims.js";
2
+ import fs from "node:fs";
3
+ import { webDriver } from "./filesink.web.js";
4
+ import { getFileSink as getBaseFileSink, } from "./sink.js";
5
+ /**
6
+ * A Node.js-specific file sink driver.
7
+ */
8
+ export const nodeDriver = {
9
+ openSync(path) {
10
+ return fs.openSync(path, "a");
11
+ },
12
+ writeSync: fs.writeSync,
13
+ flushSync: fs.fsyncSync,
14
+ closeSync: fs.closeSync,
15
+ };
16
+ /**
17
+ * Get a file sink.
18
+ *
19
+ * Note that this function is unavailable in the browser.
20
+ *
21
+ * @param path A path to the file to write to.
22
+ * @param options The options for the sink.
23
+ * @returns A sink that writes to the file. The sink is also a disposable
24
+ * object that closes the file when disposed.
25
+ */
26
+ export function getFileSink(path, options = {}) {
27
+ if ("document" in dntShim.dntGlobalThis) {
28
+ return getBaseFileSink(path, { ...options, ...webDriver });
29
+ }
30
+ return getBaseFileSink(path, { ...options, ...nodeDriver });
31
+ }
32
+ // cSpell: ignore filesink
@@ -0,0 +1,12 @@
1
+ function notImplemented() {
2
+ throw new Error("File sink is not available in the browser.");
3
+ }
4
+ /**
5
+ * A browser-specific file sink driver. All methods throw an error.
6
+ */
7
+ export const webDriver = {
8
+ openSync: notImplemented,
9
+ writeSync: notImplemented,
10
+ flushSync: notImplemented,
11
+ closeSync: notImplemented,
12
+ };
package/esm/formatter.js CHANGED
@@ -29,7 +29,7 @@ const inspect = eval(`(
29
29
  * The default text formatter. This formatter formats log records as follows:
30
30
  *
31
31
  * ```
32
- * 2023-11-14 22:13:20.000 +00:00 [INF] Hello, world!
32
+ * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!
33
33
  * ```
34
34
  *
35
35
  * @param record The log record to format.
@@ -44,7 +44,8 @@ export function defaultTextFormatter(record) {
44
44
  else
45
45
  msg += inspect(record.message[i]);
46
46
  }
47
- return `${ts.toISOString().replace("T", " ").replace("Z", " +00:00")} [${levelAbbreviations[record.level]}] ${msg}\n`;
47
+ const category = record.category.join("\xb7");
48
+ return `${ts.toISOString().replace("T", " ").replace("Z", " +00:00")} [${levelAbbreviations[record.level]}] ${category}: ${msg}\n`;
48
49
  }
49
50
  /**
50
51
  * The styles for the log level in the console.
@@ -52,9 +53,9 @@ export function defaultTextFormatter(record) {
52
53
  const logLevelStyles = {
53
54
  "debug": "background-color: gray; color: white;",
54
55
  "info": "background-color: white; color: black;",
55
- "warning": "background-color: orange;",
56
- "error": "background-color: red;",
57
- "fatal": "background-color: maroon;",
56
+ "warning": "background-color: orange; color: black;",
57
+ "error": "background-color: red; color: white;",
58
+ "fatal": "background-color: maroon; color: white;",
58
59
  };
59
60
  /**
60
61
  * The default console formatter.
@@ -74,8 +75,11 @@ export function defaultConsoleFormatter(record) {
74
75
  values.push(record.message[i]);
75
76
  }
76
77
  }
78
+ const date = new Date(record.timestamp);
79
+ const time = `${date.getUTCHours().toString().padStart(2, "0")}:${date.getUTCMinutes().toString().padStart(2, "0")}:${date.getUTCSeconds().toString().padStart(2, "0")}.${date.getUTCMilliseconds().toString().padStart(3, "0")}`;
77
80
  return [
78
- `%c${record.level.toUpperCase()}%c %c${record.category.join("\xb7")} %c${msg}`,
81
+ `%c${time} %c${levelAbbreviations[record.level]}%c %c${record.category.join("\xb7")} %c${msg}`,
82
+ "color: gray;",
79
83
  logLevelStyles[record.level],
80
84
  "background-color: default;",
81
85
  "color: gray;",
package/esm/mod.js CHANGED
@@ -1,5 +1,7 @@
1
- export { ConfigError, configure, } from "./config.js";
1
+ export { ConfigError, configure, reset, } from "./config.js";
2
+ export { getFileSink } from "./filesink.node.js";
2
3
  export { getLevelFilter, toFilter, } from "./filter.js";
3
- export { defaultConsoleFormatter, } from "./formatter.js";
4
+ export { defaultConsoleFormatter, defaultTextFormatter, } from "./formatter.js";
4
5
  export { getLogger } from "./logger.js";
5
- export { getConsoleSink } from "./sink.js";
6
+ export { getConsoleSink, getStreamSink, } from "./sink.js";
7
+ // cSpell: ignore filesink
package/esm/sink.js CHANGED
@@ -20,28 +20,29 @@ import { defaultConsoleFormatter, defaultTextFormatter, } from "./formatter.js";
20
20
  * ```
21
21
  *
22
22
  * @param stream The stream to write to.
23
- * @param formatter The text formatter to use. Defaults to
24
- * {@link defaultTextFormatter}.
25
- * @param encoder The text encoder to use. Defaults to an instance of
26
- * {@link TextEncoder}.
23
+ * @param options The options for the sink.
27
24
  * @returns A sink that writes to the stream.
28
25
  */
29
- export function getStreamSink(stream, formatter = defaultTextFormatter, encoder = new TextEncoder()) {
26
+ export function getStreamSink(stream, options = {}) {
27
+ const formatter = options.formatter ?? defaultTextFormatter;
28
+ const encoder = options.encoder ?? new TextEncoder();
30
29
  const writer = stream.getWriter();
31
- return (record) => {
30
+ const sink = (record) => {
32
31
  const bytes = encoder.encode(formatter(record));
33
32
  writer.ready.then(() => writer.write(bytes));
34
33
  };
34
+ sink[Symbol.dispose] = () => writer.close();
35
+ return sink;
35
36
  }
36
37
  /**
37
38
  * A console sink factory that returns a sink that logs to the console.
38
39
  *
39
- * @param formatter A console formatter. Defaults to
40
- * {@link defaultConsoleFormatter}.
41
- * @param console The console to log to. Defaults to {@link console}.
40
+ * @param options The options for the sink.
42
41
  * @returns A sink that logs to the console.
43
42
  */
44
- export function getConsoleSink(formatter = defaultConsoleFormatter, console = globalThis.console) {
43
+ export function getConsoleSink(options = {}) {
44
+ const formatter = options.formatter ?? defaultConsoleFormatter;
45
+ const console = options.console ?? globalThis.console;
45
46
  return (record) => {
46
47
  const args = formatter(record);
47
48
  if (record.level === "debug")
@@ -57,3 +58,23 @@ export function getConsoleSink(formatter = defaultConsoleFormatter, console = gl
57
58
  throw new TypeError(`Invalid log level: ${record.level}.`);
58
59
  };
59
60
  }
61
+ /**
62
+ * Get a platform-independent file sink.
63
+ *
64
+ * @typeParam TFile The type of the file descriptor.
65
+ * @param path A path to the file to write to.
66
+ * @param options The options for the sink and the file driver.
67
+ * @returns A sink that writes to the file. The sink is also a disposable
68
+ * object that closes the file when disposed.
69
+ */
70
+ export function getFileSink(path, options) {
71
+ const formatter = options.formatter ?? defaultTextFormatter;
72
+ const encoder = options.encoder ?? new TextEncoder();
73
+ const fd = options.openSync(path);
74
+ const sink = (record) => {
75
+ options.writeSync(fd, encoder.encode(formatter(record)));
76
+ options.flushSync(fd);
77
+ };
78
+ sink[Symbol.dispose] = () => options.closeSync(fd);
79
+ return sink;
80
+ }
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@logtape/logtape",
3
- "version": "0.1.0-dev.9+dfa649b3",
3
+ "version": "0.1.0",
4
4
  "description": "Simple logging library for Deno/Node.js/Bun/browsers",
5
+ "keywords": [
6
+ "logging",
7
+ "log",
8
+ "logger"
9
+ ],
5
10
  "author": {
6
11
  "name": "Hong Minhee",
7
12
  "email": "hong@minhee.org",
package/script/config.js CHANGED
@@ -1,13 +1,47 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConfigError = exports.reset = exports.configure = void 0;
26
+ exports.ConfigError = exports.dispose = exports.reset = exports.configure = void 0;
27
+ const dntShim = __importStar(require("./_dnt.shims.js"));
4
28
  const filter_js_1 = require("./filter.js");
5
29
  const logger_js_1 = require("./logger.js");
6
30
  const sink_js_1 = require("./sink.js");
31
+ /**
32
+ * Whether the loggers are configured.
33
+ */
7
34
  let configured = false;
35
+ /**
36
+ * Disposables to dispose when resetting the configuration.
37
+ */
38
+ const disposables = new Set();
8
39
  /**
9
40
  * Configure the loggers with the specified configuration.
10
41
  *
42
+ * Note that if the given sinks or filters are disposable, they will be
43
+ * disposed when the configuration is reset, or when the process exits.
44
+ *
11
45
  * @example
12
46
  * ```typescript
13
47
  * configure({
@@ -45,8 +79,8 @@ function configure(config) {
45
79
  if (configured && !config.reset) {
46
80
  throw new ConfigError("Already configured; if you want to reset, turn on the reset flag.");
47
81
  }
82
+ reset();
48
83
  configured = true;
49
- logger_js_1.LoggerImpl.getLogger([]).resetDescendants();
50
84
  let metaConfigured = false;
51
85
  for (const cfg of config.loggers) {
52
86
  if (cfg.category.length === 0 ||
@@ -76,6 +110,20 @@ function configure(config) {
76
110
  logger.filters.push((0, filter_js_1.toFilter)(filter));
77
111
  }
78
112
  }
113
+ for (const sink of Object.values(config.sinks)) {
114
+ if (Symbol.dispose in sink)
115
+ disposables.add(sink);
116
+ }
117
+ for (const filter of Object.values(config.filters)) {
118
+ if (filter != null && typeof filter !== "string" && Symbol.dispose in filter)
119
+ disposables.add(filter);
120
+ }
121
+ if ("process" in dntShim.dntGlobalThis) { // @ts-ignore: It's fine to use process in Node
122
+ process.on("exit", dispose);
123
+ }
124
+ else { // @ts-ignore: It's fine to addEventListener() on the browser/Deno
125
+ addEventListener("unload", dispose);
126
+ }
79
127
  const meta = logger_js_1.LoggerImpl.getLogger(["logtape", "meta"]);
80
128
  if (!metaConfigured) {
81
129
  meta.sinks.push((0, sink_js_1.getConsoleSink)());
@@ -94,10 +142,20 @@ exports.configure = configure;
94
142
  * Reset the configuration. Mostly for testing purposes.
95
143
  */
96
144
  function reset() {
145
+ dispose();
97
146
  logger_js_1.LoggerImpl.getLogger([]).resetDescendants();
98
147
  configured = false;
99
148
  }
100
149
  exports.reset = reset;
150
+ /**
151
+ * Dispose of the disposables.
152
+ */
153
+ function dispose() {
154
+ for (const disposable of disposables)
155
+ disposable[Symbol.dispose]();
156
+ disposables.clear();
157
+ }
158
+ exports.dispose = dispose;
101
159
  /**
102
160
  * A configuration error.
103
161
  */
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.getFileSink = exports.nodeDriver = void 0;
30
+ const dntShim = __importStar(require("./_dnt.shims.js"));
31
+ const node_fs_1 = __importDefault(require("node:fs"));
32
+ const filesink_web_js_1 = require("./filesink.web.js");
33
+ const sink_js_1 = require("./sink.js");
34
+ /**
35
+ * A Node.js-specific file sink driver.
36
+ */
37
+ exports.nodeDriver = {
38
+ openSync(path) {
39
+ return node_fs_1.default.openSync(path, "a");
40
+ },
41
+ writeSync: node_fs_1.default.writeSync,
42
+ flushSync: node_fs_1.default.fsyncSync,
43
+ closeSync: node_fs_1.default.closeSync,
44
+ };
45
+ /**
46
+ * Get a file sink.
47
+ *
48
+ * Note that this function is unavailable in the browser.
49
+ *
50
+ * @param path A path to the file to write to.
51
+ * @param options The options for the sink.
52
+ * @returns A sink that writes to the file. The sink is also a disposable
53
+ * object that closes the file when disposed.
54
+ */
55
+ function getFileSink(path, options = {}) {
56
+ if ("document" in dntShim.dntGlobalThis) {
57
+ return (0, sink_js_1.getFileSink)(path, { ...options, ...filesink_web_js_1.webDriver });
58
+ }
59
+ return (0, sink_js_1.getFileSink)(path, { ...options, ...exports.nodeDriver });
60
+ }
61
+ exports.getFileSink = getFileSink;
62
+ // cSpell: ignore filesink
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.webDriver = void 0;
4
+ function notImplemented() {
5
+ throw new Error("File sink is not available in the browser.");
6
+ }
7
+ /**
8
+ * A browser-specific file sink driver. All methods throw an error.
9
+ */
10
+ exports.webDriver = {
11
+ openSync: notImplemented,
12
+ writeSync: notImplemented,
13
+ flushSync: notImplemented,
14
+ closeSync: notImplemented,
15
+ };
@@ -32,7 +32,7 @@ const inspect = eval(`(
32
32
  * The default text formatter. This formatter formats log records as follows:
33
33
  *
34
34
  * ```
35
- * 2023-11-14 22:13:20.000 +00:00 [INF] Hello, world!
35
+ * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!
36
36
  * ```
37
37
  *
38
38
  * @param record The log record to format.
@@ -47,7 +47,8 @@ function defaultTextFormatter(record) {
47
47
  else
48
48
  msg += inspect(record.message[i]);
49
49
  }
50
- return `${ts.toISOString().replace("T", " ").replace("Z", " +00:00")} [${levelAbbreviations[record.level]}] ${msg}\n`;
50
+ const category = record.category.join("\xb7");
51
+ return `${ts.toISOString().replace("T", " ").replace("Z", " +00:00")} [${levelAbbreviations[record.level]}] ${category}: ${msg}\n`;
51
52
  }
52
53
  exports.defaultTextFormatter = defaultTextFormatter;
53
54
  /**
@@ -56,9 +57,9 @@ exports.defaultTextFormatter = defaultTextFormatter;
56
57
  const logLevelStyles = {
57
58
  "debug": "background-color: gray; color: white;",
58
59
  "info": "background-color: white; color: black;",
59
- "warning": "background-color: orange;",
60
- "error": "background-color: red;",
61
- "fatal": "background-color: maroon;",
60
+ "warning": "background-color: orange; color: black;",
61
+ "error": "background-color: red; color: white;",
62
+ "fatal": "background-color: maroon; color: white;",
62
63
  };
63
64
  /**
64
65
  * The default console formatter.
@@ -78,8 +79,11 @@ function defaultConsoleFormatter(record) {
78
79
  values.push(record.message[i]);
79
80
  }
80
81
  }
82
+ const date = new Date(record.timestamp);
83
+ const time = `${date.getUTCHours().toString().padStart(2, "0")}:${date.getUTCMinutes().toString().padStart(2, "0")}:${date.getUTCSeconds().toString().padStart(2, "0")}.${date.getUTCMilliseconds().toString().padStart(3, "0")}`;
81
84
  return [
82
- `%c${record.level.toUpperCase()}%c %c${record.category.join("\xb7")} %c${msg}`,
85
+ `%c${time} %c${levelAbbreviations[record.level]}%c %c${record.category.join("\xb7")} %c${msg}`,
86
+ "color: gray;",
83
87
  logLevelStyles[record.level],
84
88
  "background-color: default;",
85
89
  "color: gray;",
package/script/mod.js CHANGED
@@ -1,15 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getConsoleSink = exports.getLogger = exports.defaultConsoleFormatter = exports.toFilter = exports.getLevelFilter = exports.configure = exports.ConfigError = void 0;
3
+ exports.getStreamSink = exports.getConsoleSink = exports.getLogger = exports.defaultTextFormatter = exports.defaultConsoleFormatter = exports.toFilter = exports.getLevelFilter = exports.getFileSink = exports.reset = exports.configure = exports.ConfigError = void 0;
4
4
  var config_js_1 = require("./config.js");
5
5
  Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return config_js_1.ConfigError; } });
6
6
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return config_js_1.configure; } });
7
+ Object.defineProperty(exports, "reset", { enumerable: true, get: function () { return config_js_1.reset; } });
8
+ var filesink_node_js_1 = require("./filesink.node.js");
9
+ Object.defineProperty(exports, "getFileSink", { enumerable: true, get: function () { return filesink_node_js_1.getFileSink; } });
7
10
  var filter_js_1 = require("./filter.js");
8
11
  Object.defineProperty(exports, "getLevelFilter", { enumerable: true, get: function () { return filter_js_1.getLevelFilter; } });
9
12
  Object.defineProperty(exports, "toFilter", { enumerable: true, get: function () { return filter_js_1.toFilter; } });
10
13
  var formatter_js_1 = require("./formatter.js");
11
14
  Object.defineProperty(exports, "defaultConsoleFormatter", { enumerable: true, get: function () { return formatter_js_1.defaultConsoleFormatter; } });
15
+ Object.defineProperty(exports, "defaultTextFormatter", { enumerable: true, get: function () { return formatter_js_1.defaultTextFormatter; } });
12
16
  var logger_js_1 = require("./logger.js");
13
17
  Object.defineProperty(exports, "getLogger", { enumerable: true, get: function () { return logger_js_1.getLogger; } });
14
18
  var sink_js_1 = require("./sink.js");
15
19
  Object.defineProperty(exports, "getConsoleSink", { enumerable: true, get: function () { return sink_js_1.getConsoleSink; } });
20
+ Object.defineProperty(exports, "getStreamSink", { enumerable: true, get: function () { return sink_js_1.getStreamSink; } });
21
+ // cSpell: ignore filesink
package/script/sink.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getConsoleSink = exports.getStreamSink = void 0;
3
+ exports.getFileSink = exports.getConsoleSink = exports.getStreamSink = void 0;
4
4
  const formatter_js_1 = require("./formatter.js");
5
5
  /**
6
6
  * A factory that returns a sink that writes to a {@link WritableStream}.
@@ -23,29 +23,30 @@ const formatter_js_1 = require("./formatter.js");
23
23
  * ```
24
24
  *
25
25
  * @param stream The stream to write to.
26
- * @param formatter The text formatter to use. Defaults to
27
- * {@link defaultTextFormatter}.
28
- * @param encoder The text encoder to use. Defaults to an instance of
29
- * {@link TextEncoder}.
26
+ * @param options The options for the sink.
30
27
  * @returns A sink that writes to the stream.
31
28
  */
32
- function getStreamSink(stream, formatter = formatter_js_1.defaultTextFormatter, encoder = new TextEncoder()) {
29
+ function getStreamSink(stream, options = {}) {
30
+ const formatter = options.formatter ?? formatter_js_1.defaultTextFormatter;
31
+ const encoder = options.encoder ?? new TextEncoder();
33
32
  const writer = stream.getWriter();
34
- return (record) => {
33
+ const sink = (record) => {
35
34
  const bytes = encoder.encode(formatter(record));
36
35
  writer.ready.then(() => writer.write(bytes));
37
36
  };
37
+ sink[Symbol.dispose] = () => writer.close();
38
+ return sink;
38
39
  }
39
40
  exports.getStreamSink = getStreamSink;
40
41
  /**
41
42
  * A console sink factory that returns a sink that logs to the console.
42
43
  *
43
- * @param formatter A console formatter. Defaults to
44
- * {@link defaultConsoleFormatter}.
45
- * @param console The console to log to. Defaults to {@link console}.
44
+ * @param options The options for the sink.
46
45
  * @returns A sink that logs to the console.
47
46
  */
48
- function getConsoleSink(formatter = formatter_js_1.defaultConsoleFormatter, console = globalThis.console) {
47
+ function getConsoleSink(options = {}) {
48
+ const formatter = options.formatter ?? formatter_js_1.defaultConsoleFormatter;
49
+ const console = options.console ?? globalThis.console;
49
50
  return (record) => {
50
51
  const args = formatter(record);
51
52
  if (record.level === "debug")
@@ -62,3 +63,24 @@ function getConsoleSink(formatter = formatter_js_1.defaultConsoleFormatter, cons
62
63
  };
63
64
  }
64
65
  exports.getConsoleSink = getConsoleSink;
66
+ /**
67
+ * Get a platform-independent file sink.
68
+ *
69
+ * @typeParam TFile The type of the file descriptor.
70
+ * @param path A path to the file to write to.
71
+ * @param options The options for the sink and the file driver.
72
+ * @returns A sink that writes to the file. The sink is also a disposable
73
+ * object that closes the file when disposed.
74
+ */
75
+ function getFileSink(path, options) {
76
+ const formatter = options.formatter ?? formatter_js_1.defaultTextFormatter;
77
+ const encoder = options.encoder ?? new TextEncoder();
78
+ const fd = options.openSync(path);
79
+ const sink = (record) => {
80
+ options.writeSync(fd, encoder.encode(formatter(record)));
81
+ options.flushSync(fd);
82
+ };
83
+ sink[Symbol.dispose] = () => options.closeSync(fd);
84
+ return sink;
85
+ }
86
+ exports.getFileSink = getFileSink;
package/types/config.d.ts CHANGED
@@ -50,6 +50,9 @@ export interface LoggerConfig<TSinkId extends string, TFilterId extends string>
50
50
  /**
51
51
  * Configure the loggers with the specified configuration.
52
52
  *
53
+ * Note that if the given sinks or filters are disposable, they will be
54
+ * disposed when the configuration is reset, or when the process exits.
55
+ *
53
56
  * @example
54
57
  * ```typescript
55
58
  * configure({
@@ -88,6 +91,10 @@ export declare function configure<TSinkId extends string, TFilterId extends stri
88
91
  * Reset the configuration. Mostly for testing purposes.
89
92
  */
90
93
  export declare function reset(): void;
94
+ /**
95
+ * Dispose of the disposables.
96
+ */
97
+ export declare function dispose(): void;
91
98
  /**
92
99
  * A configuration error.
93
100
  */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAkB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,MAAM,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM;IACtE;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAEvC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IAE5C;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAC3B,OAAO,SAAS,MAAM,EACtB,SAAS,SAAS,MAAM;IAExB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACzB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EACxE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,QA0DnC;AAED;;GAEG;AACH,wBAAgB,KAAK,SAGpB;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC;;;OAGG;gBACS,OAAO,EAAE,MAAM;CAI5B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAkB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,MAAM,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM;IACtE;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAEvC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IAE5C;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAC3B,OAAO,SAAS,MAAM,EACtB,SAAS,SAAS,MAAM;IAExB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE5B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACzB;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,EACxE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,QA0EnC;AAED;;GAEG;AACH,wBAAgB,KAAK,SAIpB;AAED;;GAEG;AACH,wBAAgB,OAAO,SAGtB;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC;;;OAGG;gBACS,OAAO,EAAE,MAAM;CAI5B"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../../../src/deps/deno.land/x/which_runtime@0.2.0/mod.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM,SAAgC,CAAC;AACpD,eAAO,MAAM,MAAM,SAA0B,CAAC"}
@@ -0,0 +1,18 @@
1
+ /// <reference types="node" />
2
+ import { type FileSinkDriver, type FileSinkOptions, type Sink } from "./sink.js";
3
+ /**
4
+ * A Node.js-specific file sink driver.
5
+ */
6
+ export declare const nodeDriver: FileSinkDriver<number>;
7
+ /**
8
+ * Get a file sink.
9
+ *
10
+ * Note that this function is unavailable in the browser.
11
+ *
12
+ * @param path A path to the file to write to.
13
+ * @param options The options for the sink.
14
+ * @returns A sink that writes to the file. The sink is also a disposable
15
+ * object that closes the file when disposed.
16
+ */
17
+ export declare function getFileSink(path: string, options?: FileSinkOptions): Sink & Disposable;
18
+ //# sourceMappingURL=filesink.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesink.node.d.ts","sourceRoot":"","sources":["../src/filesink.node.ts"],"names":[],"mappings":";AAGA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,IAAI,EACV,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,cAAc,CAAC,MAAM,CAO7C,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAoB,GAC5B,IAAI,GAAG,UAAU,CAKnB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesink.test.d.ts","sourceRoot":"","sources":["../src/filesink.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { FileSinkDriver } from "./sink.js";
2
+ /**
3
+ * A browser-specific file sink driver. All methods throw an error.
4
+ */
5
+ export declare const webDriver: FileSinkDriver<void>;
6
+ //# sourceMappingURL=filesink.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesink.web.d.ts","sourceRoot":"","sources":["../src/filesink.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMhD;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,cAAc,CAAC,IAAI,CAK1C,CAAC"}
@@ -11,7 +11,7 @@ export type TextFormatter = (record: LogRecord) => string;
11
11
  * The default text formatter. This formatter formats log records as follows:
12
12
  *
13
13
  * ```
14
- * 2023-11-14 22:13:20.000 +00:00 [INF] Hello, world!
14
+ * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!
15
15
  * ```
16
16
  *
17
17
  * @param record The log record to format.
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;AA+B1D;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAU9D;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,SAAS,OAAO,EAAE,CAAC;AAazE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,CAoB7E"}
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;AA+B1D;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAW9D;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,SAAS,OAAO,EAAE,CAAC;AAazE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,CA2B7E"}
package/types/mod.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export { type Config, ConfigError, configure, type LoggerConfig, } from "./config.js";
1
+ export { type Config, ConfigError, configure, type LoggerConfig, reset, } from "./config.js";
2
+ export { getFileSink } from "./filesink.node.js";
2
3
  export { type Filter, type FilterLike, getLevelFilter, toFilter, } from "./filter.js";
3
- export { type ConsoleFormatter, defaultConsoleFormatter, type TextFormatter, } from "./formatter.js";
4
+ export { type ConsoleFormatter, defaultConsoleFormatter, defaultTextFormatter, type TextFormatter, } from "./formatter.js";
4
5
  export { getLogger, type Logger } from "./logger.js";
5
6
  export type { LogLevel, LogRecord } from "./record.js";
6
- export { getConsoleSink, type Sink } from "./sink.js";
7
+ export { type ConsoleSinkOptions, type FileSinkDriver, type FileSinkOptions, getConsoleSink, getStreamSink, type Sink, type StreamSinkOptions, } from "./sink.js";
7
8
  //# sourceMappingURL=mod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,WAAW,EACX,SAAS,EACT,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,gBAAgB,EACrB,uBAAuB,EACvB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,WAAW,EACX,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,gBAAgB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,cAAc,EACd,aAAa,EACb,KAAK,IAAI,EACT,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC"}
package/types/sink.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
+ /// <reference types="node" />
3
4
  import * as dntShim from "./_dnt.shims.js";
4
5
  import { type ConsoleFormatter, type TextFormatter } from "./formatter.js";
5
6
  import type { LogRecord } from "./record.js";
@@ -13,6 +14,21 @@ import type { LogRecord } from "./record.js";
13
14
  * @param record The log record to sink.
14
15
  */
15
16
  export type Sink = (record: LogRecord) => void;
17
+ /**
18
+ * Options for the {@link getStreamSink} function.
19
+ */
20
+ export interface StreamSinkOptions {
21
+ /**
22
+ * The text formatter to use. Defaults to {@link defaultTextFormatter}.
23
+ */
24
+ formatter?: TextFormatter;
25
+ /**
26
+ * The text encoder to use. Defaults to an instance of {@link TextEncoder}.
27
+ */
28
+ encoder?: {
29
+ encode(text: string): Uint8Array;
30
+ };
31
+ }
16
32
  /**
17
33
  * A factory that returns a sink that writes to a {@link WritableStream}.
18
34
  *
@@ -34,22 +50,69 @@ export type Sink = (record: LogRecord) => void;
34
50
  * ```
35
51
  *
36
52
  * @param stream The stream to write to.
37
- * @param formatter The text formatter to use. Defaults to
38
- * {@link defaultTextFormatter}.
39
- * @param encoder The text encoder to use. Defaults to an instance of
40
- * {@link TextEncoder}.
53
+ * @param options The options for the sink.
41
54
  * @returns A sink that writes to the stream.
42
55
  */
43
- export declare function getStreamSink(stream: dntShim.WritableStream, formatter?: TextFormatter, encoder?: {
44
- encode(text: string): Uint8Array;
45
- }): Sink;
56
+ export declare function getStreamSink(stream: dntShim.WritableStream, options?: StreamSinkOptions): Sink & Disposable;
57
+ /**
58
+ * Options for the {@link getConsoleSink} function.
59
+ */
60
+ export interface ConsoleSinkOptions {
61
+ /**
62
+ * The console formatter to use. Defaults to {@link defaultConsoleFormatter}.
63
+ */
64
+ formatter?: ConsoleFormatter;
65
+ /**
66
+ * The console to log to. Defaults to {@link console}.
67
+ */
68
+ console?: Console;
69
+ }
46
70
  /**
47
71
  * A console sink factory that returns a sink that logs to the console.
48
72
  *
49
- * @param formatter A console formatter. Defaults to
50
- * {@link defaultConsoleFormatter}.
51
- * @param console The console to log to. Defaults to {@link console}.
73
+ * @param options The options for the sink.
52
74
  * @returns A sink that logs to the console.
53
75
  */
54
- export declare function getConsoleSink(formatter?: ConsoleFormatter, console?: Console): Sink;
76
+ export declare function getConsoleSink(options?: ConsoleSinkOptions): Sink;
77
+ /**
78
+ * Options for the {@link getFileSink} function.
79
+ */
80
+ export type FileSinkOptions = StreamSinkOptions;
81
+ /**
82
+ * A platform-specific file sink driver.
83
+ * @typeParam TFile The type of the file descriptor.
84
+ */
85
+ export interface FileSinkDriver<TFile> {
86
+ /**
87
+ * Open a file for appending and return a file descriptor.
88
+ * @param path A path to the file to open.
89
+ */
90
+ openSync(path: string): TFile;
91
+ /**
92
+ * Write a chunk of data to the file.
93
+ * @param fd The file descriptor.
94
+ * @param chunk The data to write.
95
+ */
96
+ writeSync(fd: TFile, chunk: Uint8Array): void;
97
+ /**
98
+ * Flush the file to ensure that all data is written to the disk.
99
+ * @param fd The file descriptor.
100
+ */
101
+ flushSync(fd: TFile): void;
102
+ /**
103
+ * Close the file.
104
+ * @param fd The file descriptor.
105
+ */
106
+ closeSync(fd: TFile): void;
107
+ }
108
+ /**
109
+ * Get a platform-independent file sink.
110
+ *
111
+ * @typeParam TFile The type of the file descriptor.
112
+ * @param path A path to the file to write to.
113
+ * @param options The options for the sink and the file driver.
114
+ * @returns A sink that writes to the file. The sink is also a disposable
115
+ * object that closes the file when disposed.
116
+ */
117
+ export declare function getFileSink<TFile>(path: string, options: FileSinkOptions & FileSinkDriver<TFile>): Sink & Disposable;
55
118
  //# sourceMappingURL=sink.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,KAAK,gBAAgB,EAGrB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,CAAC,cAAc,EAC9B,SAAS,GAAE,aAAoC,EAC/C,OAAO,GAAE;IAAE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;CAAsB,GAChE,IAAI,CAMN;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,SAAS,GAAE,gBAA0C,EACrD,OAAO,GAAE,OAA4B,GACpC,IAAI,CAUN"}
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,KAAK,gBAAgB,EAGrB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,CAAC,cAAc,EAC9B,OAAO,GAAE,iBAAsB,GAC9B,IAAI,GAAG,UAAU,CAUnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAE7B;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,IAAI,CAYrE;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAE9C;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,GAC/C,IAAI,GAAG,UAAU,CAUnB"}