@hardlydifficult/logger 1.0.4 → 1.0.6
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 +112 -35
- package/dist/Logger.d.ts +3 -1
- package/dist/Logger.d.ts.map +1 -1
- package/dist/Logger.js +7 -4
- package/dist/Logger.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/logger
|
|
2
2
|
|
|
3
|
-
Plugin-based structured logger with Console,
|
|
3
|
+
Plugin-based structured logger with configurable log levels and extensible output handlers for Console, File, and Discord.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,97 +8,160 @@ Plugin-based structured logger with Console, Discord, and File output plugins.
|
|
|
8
8
|
npm install @hardlydifficult/logger
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Logger, ConsolePlugin, FilePlugin, DiscordPlugin } from "@hardlydifficult/logger";
|
|
15
15
|
|
|
16
|
-
const discord = new DiscordPlugin();
|
|
17
|
-
|
|
18
16
|
const logger = new Logger("info")
|
|
19
17
|
.use(new ConsolePlugin())
|
|
20
|
-
.use(new FilePlugin("
|
|
21
|
-
.use(discord);
|
|
22
|
-
|
|
23
|
-
// Wire up Discord sender once the bot is ready
|
|
24
|
-
discord.setSender((msg) => channel.send(msg));
|
|
18
|
+
.use(new FilePlugin("./app.log"));
|
|
25
19
|
|
|
26
20
|
logger.info("Server started", { port: 3000 });
|
|
27
21
|
logger.warn("High memory usage", { usage: "85%" });
|
|
28
22
|
logger.error("Request failed", { url: "/api/data", status: 500 });
|
|
29
|
-
|
|
30
|
-
// Out-of-band notification (goes to plugins that support notify)
|
|
31
|
-
logger.notify("Deployment complete");
|
|
32
23
|
```
|
|
33
24
|
|
|
34
|
-
##
|
|
25
|
+
## Core Logger
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
The `Logger` class dispatches log entries to registered plugins based on a minimum log level.
|
|
37
28
|
|
|
38
29
|
```typescript
|
|
39
|
-
|
|
30
|
+
import { Logger } from "@hardlydifficult/logger";
|
|
31
|
+
|
|
32
|
+
const logger = new Logger("info"); // default: "info"
|
|
40
33
|
```
|
|
41
34
|
|
|
35
|
+
### Log Levels
|
|
36
|
+
|
|
37
|
+
Log levels in order of severity: `debug` < `info` < `warn` < `error`. Entries below the logger's `minLevel` are filtered out before reaching plugins.
|
|
38
|
+
|
|
39
|
+
### Methods
|
|
40
|
+
|
|
42
41
|
| Method | Description |
|
|
43
42
|
|--------|-------------|
|
|
44
|
-
| `use(plugin)` | Register a plugin
|
|
43
|
+
| `use(plugin, options?)` | Register a plugin; returns `this` for chaining. Optional `minLevel` filters entries per-plugin. |
|
|
45
44
|
| `debug(message, context?)` | Log at debug level |
|
|
46
45
|
| `info(message, context?)` | Log at info level |
|
|
47
46
|
| `warn(message, context?)` | Log at warn level |
|
|
48
47
|
| `error(message, context?)` | Log at error level |
|
|
49
48
|
| `notify(message)` | Send out-of-band notification to plugins that support it |
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
### Plugin-Level Filtering
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
Each plugin can have its own minimum log level, independent of the logger's global level:
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
```typescript
|
|
55
|
+
const logger = new Logger("debug")
|
|
56
|
+
.use(new ConsolePlugin()) // receives all levels
|
|
57
|
+
.use(new FilePlugin("./errors.log"), { minLevel: "error" }); // only errors
|
|
58
|
+
|
|
59
|
+
logger.debug("This goes to console only");
|
|
60
|
+
logger.error("This goes to both console and file");
|
|
61
|
+
```
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
## Console Plugin
|
|
64
|
+
|
|
65
|
+
Outputs formatted log entries to the console, routing to `console.log`, `console.warn`, or `console.error` based on log level.
|
|
58
66
|
|
|
59
67
|
```typescript
|
|
60
|
-
|
|
68
|
+
import { Logger, ConsolePlugin } from "@hardlydifficult/logger";
|
|
69
|
+
|
|
70
|
+
const logger = new Logger("info").use(new ConsolePlugin());
|
|
71
|
+
|
|
72
|
+
logger.info("Server started", { port: 3000 });
|
|
73
|
+
// Output: [2025-01-15T10:30:00.000Z] INFO: Server started {"port":3000}
|
|
61
74
|
```
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
### Format
|
|
64
77
|
|
|
65
|
-
|
|
78
|
+
The `formatEntry` function is also exported for custom formatting:
|
|
66
79
|
|
|
67
80
|
```typescript
|
|
68
|
-
|
|
81
|
+
import { formatEntry } from "@hardlydifficult/logger";
|
|
82
|
+
import type { LogEntry } from "@hardlydifficult/logger";
|
|
83
|
+
|
|
84
|
+
const entry: LogEntry = {
|
|
85
|
+
level: "warn",
|
|
86
|
+
message: "High memory",
|
|
87
|
+
timestamp: "2025-01-15T10:30:00.000Z",
|
|
88
|
+
context: { usage: "85%" },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
console.log(formatEntry(entry));
|
|
92
|
+
// Output: [2025-01-15T10:30:00.000Z] WARN: High memory {"usage":"85%"}
|
|
69
93
|
```
|
|
70
94
|
|
|
71
|
-
|
|
95
|
+
## File Plugin
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
Appends JSON-serialized log entries to a file (one entry per line, JSONL format). Creates parent directories automatically.
|
|
74
98
|
|
|
75
99
|
```typescript
|
|
100
|
+
import { Logger, FilePlugin } from "@hardlydifficult/logger";
|
|
101
|
+
|
|
102
|
+
const logger = new Logger("info").use(new FilePlugin("./logs/app.log"));
|
|
103
|
+
|
|
104
|
+
logger.info("Request processed", { method: "GET", path: "/api/users" });
|
|
105
|
+
// Appends: {"level":"info","message":"Request processed","timestamp":"2025-01-15T10:30:00.000Z","context":{"method":"GET","path":"/api/users"}}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Discord Plugin
|
|
109
|
+
|
|
110
|
+
Forwards warn/error log entries and notifications to Discord via a configurable sender function. Useful for alerting on critical issues.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { Logger, DiscordPlugin } from "@hardlydifficult/logger";
|
|
114
|
+
|
|
76
115
|
const discord = new DiscordPlugin();
|
|
116
|
+
const logger = new Logger("info").use(discord);
|
|
117
|
+
|
|
118
|
+
// Set the sender once your Discord bot is ready
|
|
77
119
|
discord.setSender((msg) => channel.send(msg));
|
|
120
|
+
|
|
121
|
+
logger.warn("High memory usage", { usage: "85%" });
|
|
122
|
+
// Sends to Discord: ⚠️ **WARN**: High memory usage
|
|
123
|
+
// ```json
|
|
124
|
+
// {
|
|
125
|
+
// "usage": "85%"
|
|
126
|
+
// }
|
|
127
|
+
// ```
|
|
128
|
+
|
|
129
|
+
logger.notify("Deployment complete");
|
|
130
|
+
// Sends to Discord: Deployment complete
|
|
78
131
|
```
|
|
79
132
|
|
|
80
|
-
###
|
|
133
|
+
### Behavior
|
|
81
134
|
|
|
82
|
-
|
|
135
|
+
- Only `warn` and `error` log entries are sent (debug/info are filtered)
|
|
136
|
+
- Warn entries use ⚠️ emoji; error entries use 🚨 emoji
|
|
137
|
+
- Context is formatted as a JSON code block when present
|
|
138
|
+
- `notify()` sends messages directly without level filtering
|
|
139
|
+
- If `setSender` is not called, entries are silently dropped
|
|
140
|
+
|
|
141
|
+
## Custom Plugins
|
|
142
|
+
|
|
143
|
+
Implement the `LoggerPlugin` interface to create custom output handlers:
|
|
83
144
|
|
|
84
145
|
```typescript
|
|
85
146
|
import type { LoggerPlugin, LogEntry } from "@hardlydifficult/logger";
|
|
147
|
+
import { Logger } from "@hardlydifficult/logger";
|
|
86
148
|
|
|
87
|
-
class
|
|
149
|
+
class SlackPlugin implements LoggerPlugin {
|
|
88
150
|
log(entry: LogEntry): void {
|
|
89
|
-
|
|
151
|
+
if (entry.level === "error") {
|
|
152
|
+
// Send to Slack
|
|
153
|
+
}
|
|
90
154
|
}
|
|
91
155
|
|
|
92
|
-
// Optional: handle notify() calls
|
|
93
156
|
notify?(message: string): void {
|
|
94
|
-
//
|
|
157
|
+
// Send notification to Slack
|
|
95
158
|
}
|
|
96
159
|
}
|
|
97
160
|
|
|
98
|
-
logger.use(new
|
|
161
|
+
const logger = new Logger("info").use(new SlackPlugin());
|
|
99
162
|
```
|
|
100
163
|
|
|
101
|
-
|
|
164
|
+
## Types
|
|
102
165
|
|
|
103
166
|
```typescript
|
|
104
167
|
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
@@ -106,7 +169,7 @@ type LogLevel = "debug" | "info" | "warn" | "error";
|
|
|
106
169
|
interface LogEntry {
|
|
107
170
|
readonly level: LogLevel;
|
|
108
171
|
readonly message: string;
|
|
109
|
-
readonly timestamp: string;
|
|
172
|
+
readonly timestamp: string; // ISO 8601 format
|
|
110
173
|
readonly context?: Readonly<Record<string, unknown>>;
|
|
111
174
|
}
|
|
112
175
|
|
|
@@ -114,4 +177,18 @@ interface LoggerPlugin {
|
|
|
114
177
|
log(entry: LogEntry): void;
|
|
115
178
|
notify?(message: string): void;
|
|
116
179
|
}
|
|
180
|
+
|
|
181
|
+
type DiscordSender = (message: string) => void;
|
|
117
182
|
```
|
|
183
|
+
|
|
184
|
+
## Error Handling
|
|
185
|
+
|
|
186
|
+
All plugins are isolated—if one plugin throws an error, it does not affect other plugins or the logger itself. Errors are silently swallowed to ensure logging never crashes your application.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const logger = new Logger("info")
|
|
190
|
+
.use(new ConsolePlugin()) // works fine
|
|
191
|
+
.use(brokenPlugin); // throws, but doesn't break the logger
|
|
192
|
+
|
|
193
|
+
logger.info("This still works"); // ConsolePlugin receives it
|
|
194
|
+
```
|
package/dist/Logger.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ export declare class Logger {
|
|
|
4
4
|
private readonly minLevel;
|
|
5
5
|
private readonly plugins;
|
|
6
6
|
constructor(minLevel?: LogLevel);
|
|
7
|
-
use(plugin: LoggerPlugin
|
|
7
|
+
use(plugin: LoggerPlugin, options?: {
|
|
8
|
+
minLevel?: LogLevel;
|
|
9
|
+
}): this;
|
|
8
10
|
debug(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
9
11
|
info(message: string, context?: Readonly<Record<string, unknown>>): void;
|
|
10
12
|
warn(message: string, context?: Readonly<Record<string, unknown>>): void;
|
package/dist/Logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAcnE,qHAAqH;AACrH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,QAAQ,GAAE,QAAiB;IAIvC,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI;IAKlE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAIzE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAIxE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAIxE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAIzE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAY7B,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,GAAG;CAyBZ"}
|
package/dist/Logger.js
CHANGED
|
@@ -14,8 +14,8 @@ class Logger {
|
|
|
14
14
|
constructor(minLevel = "info") {
|
|
15
15
|
this.minLevel = minLevel;
|
|
16
16
|
}
|
|
17
|
-
use(plugin) {
|
|
18
|
-
this.plugins.push(plugin);
|
|
17
|
+
use(plugin, options) {
|
|
18
|
+
this.plugins.push({ plugin, minLevel: options?.minLevel });
|
|
19
19
|
return this;
|
|
20
20
|
}
|
|
21
21
|
debug(message, context) {
|
|
@@ -31,7 +31,7 @@ class Logger {
|
|
|
31
31
|
this.log("error", message, context);
|
|
32
32
|
}
|
|
33
33
|
notify(message) {
|
|
34
|
-
for (const plugin of this.plugins) {
|
|
34
|
+
for (const { plugin } of this.plugins) {
|
|
35
35
|
if (plugin.notify) {
|
|
36
36
|
try {
|
|
37
37
|
plugin.notify(message);
|
|
@@ -52,7 +52,10 @@ class Logger {
|
|
|
52
52
|
const entry = context !== undefined
|
|
53
53
|
? { level, message, timestamp: new Date().toISOString(), context }
|
|
54
54
|
: { level, message, timestamp: new Date().toISOString() };
|
|
55
|
-
for (const plugin of this.plugins) {
|
|
55
|
+
for (const { plugin, minLevel } of this.plugins) {
|
|
56
|
+
if (minLevel !== undefined && LOG_LEVELS[level] < LOG_LEVELS[minLevel]) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
56
59
|
try {
|
|
57
60
|
plugin.log(entry);
|
|
58
61
|
}
|
package/dist/Logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":";;;AAEA,MAAM,UAAU,GAAuC;IACrD,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;
|
|
1
|
+
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":";;;AAEA,MAAM,UAAU,GAAuC;IACrD,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAOF,qHAAqH;AACrH,MAAa,MAAM;IACA,QAAQ,CAAW;IACnB,OAAO,GAAkB,EAAE,CAAC;IAE7C,YAAY,WAAqB,MAAM;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,GAAG,CAAC,MAAoB,EAAE,OAAiC;QACzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,OAA2C;QAChE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,OAA2C;QAC/D,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,OAA2C;QAC/D,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,OAA2C;QAChE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,CAAC,OAAe;QACpB,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,IAAI,CAAC;oBACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,aAAa;gBACf,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,KAAe;QAC/B,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;IAEO,GAAG,CACT,KAAe,EACf,OAAe,EACf,OAA2C;QAE3C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GACT,OAAO,KAAK,SAAS;YACnB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE;YAClE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QAE9D,KAAK,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChD,IAAI,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvE,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,aAAa;YACf,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAtED,wBAsEC"}
|