Skip to content

Commit

Permalink
Renewed API to configure lowest level of loggers
Browse files Browse the repository at this point in the history
Close #26
  • Loading branch information
dahlia committed Nov 20, 2024
1 parent 5c38a54 commit 24252bf
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 55 deletions.
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ Version 0.8.0

To be released.

- Renewed the API to configure the lowest severity level of loggers. [[#26]]

- Added `LoggerConfig.lowestLevel` property.
- Deprecated `LoggerConfig.level` property in favor of
`LoggerConfig.lowestLevel`.

- Added `compareLogLevel()` function.

[#26]: https://github.com/dahlia/logtape/issues/26


Version 0.7.1
-------------
Expand Down
Binary file modified docs/bun.lockb
Binary file not shown.
9 changes: 5 additions & 4 deletions docs/manual/categories.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ with the category `["my-app", "my-module", "my-submodule"]`, it is dispatched
to loggers whose categories are `["my-app", "my-module"]` and `["my-app"]`.

This behavior allows you to control the verbosity of log messages by setting
the log level of loggers at different levels of the category hierarchy.
the `~LoggerConfig.lowestLevel` of loggers at different levels of the category
hierarchy.

Here's an example of setting log levels for different categories:

Expand All @@ -20,11 +21,11 @@ import { configure, getConsoleSink, getFileSink } from "@logtape/logtape";
await configure({
sinks: {
console: getConsoleSink(),
file: getFileSink("app.log"),
file: getFileSink("app.log"),
},
loggers: [
{ category: ["my-app"], level: "info", sinks: ["file"] },
{ category: ["my-app", "my-module"], level: "debug", sinks: ["console"] },
{ category: ["my-app"], lowestLevel: "info", sinks: ["file"] },
{ category: ["my-app", "my-module"], lowestLevel: "debug", sinks: ["console"] },
],
})
~~~~
Expand Down
8 changes: 4 additions & 4 deletions docs/manual/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ await configure({
loggers: [
{
category: "my-app",
level: "info",
lowestLevel: "info",
sinks: ["console"],
},
],
Expand Down Expand Up @@ -109,18 +109,18 @@ await configure({
loggers: [
{
category: "my-app",
level: "info",
lowestLevel: "info",
sinks: ["console", "file"],
},
{
category: ["my-app", "database"],
level: "debug",
lowestLevel: "debug",
sinks: ["file"],
filters: ["noDebug"],
},
{
category: ["my-app", "user-service"],
level: "info",
lowestLevel: "info",
sinks: ["console", "file"],
filters: ["containsUserData"],
},
Expand Down
70 changes: 68 additions & 2 deletions docs/manual/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ await configure({
loggers: [
{
category: ["my-app", "database"],
level: "debug",
sinks: ["console"],
filters: ["tooSlow"], // [!code highlight]
}
Expand All @@ -44,6 +43,55 @@ await configure({
~~~~


Inheritance
-----------

Child loggers inherit filters from their parent loggers. Even if a child logger
has its own filters, the child logger filters out log messages that are filtered
out by its parent logger filters plus its own filters.

For example, the following example sets two filters, `hasUserInfo` and
`tooSlow`, and assigns the `hasUserInfo` filter to the parent logger and
the `tooSlow` filter to the child logger:

~~~~ typescript twoslash
// @noErrors: 2345
import { configure, type LogRecord } from "@logtape/logtape";
// ---cut-before---
await configure({
// Omitted for brevity
filters: {
hasUserInfo(record: LogRecord) {
return "userInfo" in record.properties;
},
tooSlow(record: LogRecord) {
return "elapsed" in record.properties
&& typeof record.properties.elapsed === "number"
&& record.properties.elapsed >= 100;
},
},
loggers: [
{
category: ["my-app"],
sinks: ["console"],
filters: ["hasUserInfo"], // [!code highlight]
},
{
category: ["my-app", "database"],
sinks: [],
filters: ["tooSlow"], // [!code highlight]
}
]
});
~~~~

In this example, any log messages under the `["my-app"]` category including
the `["my-app", "database"]` category are passed to the console sink only if
they have the `userInfo` property. In addition, the log messages under the
`["my-app", "database"]` category are passed to the console sink only if they
have the `elapsed` with a value greater than or equal to 100 milliseconds.


Level filter
------------

Expand All @@ -58,7 +106,25 @@ import { configure, getLevelFilter } from "@logtape/logtape";

await configure({
filters: {
infoOrHigher: getLevelFilter("info"), // [!code highlight]
infoAndAbove: getLevelFilter("info"), // [!code highlight]
},
// Omitted for brevity
});
~~~~

The `~Config.filters` takes a map of filter names to `FilterLike`, instead of
just `Filter`, where `FilterLike` is either a `Filter` function or a severity
level string. The severity level string will be resolved to a `Filter` that
filters log records with the specified severity level and above. Hence, you
can simplify the above example as follows:

~~~~ typescript twoslash
// @noErrors: 2345
import { configure } from "@logtape/logtape";
// ---cut-before---
await configure({
filters: {
infoAndAbove: "info", // [!code highlight]
},
// Omitted for brevity
});
Expand Down
101 changes: 97 additions & 4 deletions docs/manual/levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,33 @@ When deciding which level to use, consider:
Configuring severity levels
---------------------------

*This API is available since LogTape 0.8.0.*

You can control which severity levels are logged in different parts of your
application. For example:

~~~~ typescript twoslash
// @noErrors: 2345
~~~~ typescript{6,11} twoslash
// @noErrors: 2345 2353
import { configure } from "@logtape/logtape";
// ---cut-before---
await configure({
// ---cut-start---
sinks: {
console(record) { },
file(record) { },
},
// ---cut-end---
// ... other configuration ...
loggers: [
{
category: ["app"],
level: "info", // This will log info and above
lowestLevel: "info", // This will log info and above
sinks: ["console"],
},
{
category: ["app", "database"],
level: "debug", // This will log everything for database operations
lowestLevel: "debug", // This will log everything for database operations
sinks: ["file"],
}
]
});
Expand All @@ -137,6 +147,89 @@ await configure({
This configuration will log all levels from `"info"` up for most of the app,
but will include `"debug"` logs for database operations.

> [!NOTE]
> The `~LoggerConfig.lowestLevel` is applied to the logger itself, not to its
> sinks. In other words, the `~LoggerConfig.lowestLevel` property determines
> which log records are emitted by the logger. For example, if the parent
> logger has a `~LoggerConfig.lowestLevel` of `"debug"` with a sink `"console"`,
> and the child logger has a `~LoggerConfig.lowestLevel` of `"info"`,
> the child logger still won't emit `"debug"` records to the `"console"` sink.
The `~LoggerConfig.lowestLevel` property does not inherit from parent loggers,
but it is `"debug"` by default for all loggers. If you want to make child
loggers inherit the severity level from their parent logger, you can use the
`~LoggerConfig.filters` option instead.

If you want make child loggers inherit the severity level from their parent
logger, you can use the `~LoggerConfig.filters` option instead:

~~~~ typescript{4,9,13} twoslash
// @noErrors: 2345 2353
import { configure } from "@logtape/logtape";
// ---cut-before---
await configure({
// ... other configuration ...
filters: {
infoAndAbove: "info",
},
loggers: [
{
category: ["app"],
filters: ["infoAndAbove"], // This will log info and above
},
{
category: ["app", "database"],
// This also logs info and above, because it inherits from the parent logger
}
]
});
~~~~

In this example, the database logger will inherit the `aboveAndInfo` filter from
the parent logger, so it will log all levels from `"info"` up.

> [!TIP]
> The `~LoggerConfig.filters` option takes a map of filter names to
> `FilterLike`, where `FilterLike` is either a `Filter` function or a severity
> level string. The severity level string will be resolved to a `Filter` that
> filters log records with the specified severity level and above.
>
> See also the [*Level filter* section](./filters.md#level-filter).

Comparing two severity levels
-----------------------------

*This API is available since LogTape 0.8.0.*

You can compare two severity levels to see which one is more severe by using
the `compareLogLevel()` function. Since this function returns a number where
negative means the first argument is less severe, zero means they are equal,
and positive means the first argument is more severe, you can use it with
[`Array.sort()`] or [`Array.toSorted()`] to sort severity levels:

~~~~ typescript twoslash
// @noErrors: 2724
import { type LogLevel, compareLogLevel } from "@logtape/logtape";

const levels: LogLevel[] = ["info", "debug", "error", "warning", "fatal"];
levels.sort(compareLogLevel);
for (const level of levels) console.log(level);
~~~~

The above code will output:

~~~~
debug
info
warning
error
fatal
~~~~

[`Array.sort()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
[`Array.toSorted()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted


Best practices
--------------
Expand Down
4 changes: 2 additions & 2 deletions docs/manual/library.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ await configure({
loggers: [
{
category: ["my-awesome-lib"],
level: "info",
lowestLevel: "info",
sinks: ["console", "file"]
},
{
category: ["my-awesome-lib", "database"],
level: "debug",
lowestLevel: "debug",
sinks: ["file"],
filters: ["excludeDebug"]
}
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/sinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ await configure({
},
filters: {},
loggers: [
{ category: [], sinks: ["otel"], level: "debug" },
{ category: [], sinks: ["otel"], lowestLevel: "debug" },
],
});
~~~~
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: { console: getConsoleSink() },
loggers: [
{ category: "my-app", level: "debug", sinks: ["console"] }
{ category: "my-app", lowestLevel: "debug", sinks: ["console"] }
]
});
~~~~
Expand Down
6 changes: 5 additions & 1 deletion logtape/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ Deno.test("configure()", async (t) => {
category: ["my-app", "bar"],
sinks: ["c"],
filters: ["debug"],
level: "info",
level: "info", // deprecated
lowestLevel: "info",
},
],
};
Expand All @@ -63,11 +64,14 @@ Deno.test("configure()", async (t) => {
const logger = LoggerImpl.getLogger("my-app");
assertEquals(logger.sinks, [a]);
assertEquals(logger.filters, [x]);
assertEquals(logger.lowestLevel, "debug");
const foo = LoggerImpl.getLogger(["my-app", "foo"]);
assertEquals(foo.sinks, [b]);
assertEquals(foo.filters, [y]);
assertEquals(foo.lowestLevel, "debug");
const bar = LoggerImpl.getLogger(["my-app", "bar"]);
assertEquals(bar.sinks, [c]);
assertEquals(bar.lowestLevel, "info");
bar.debug("ignored");
assertEquals(aLogs, []);
assertEquals(bLogs, []);
Expand Down
Loading

0 comments on commit 24252bf

Please sign in to comment.