@fimbul-works/logger 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FimbulWorks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @fimbul-works/logger
2
+
3
+ A lightweight, TypeScript-friendly wrapper around [Pino](https://getpino.io/) that provides flexible logging patterns with automatic argument handling.
4
+
5
+ [![npm version](https://badge.fury.io/js/%40logger-works%2Flogger.svg)](https://www.npmjs.com/package/@logger-works/logger)
6
+ [![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://github.com/microsoft/TypeScript)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@logger-works/logger)](https://bundlephobia.com/package/@logger-works/logger)
8
+
9
+ **Note:** This is a wrapper around [Pino](https://getpino.io/). For advanced configuration and features, refer to the [Pino documentation](https://getpino.io/#/docs/api).
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import Logger from 'your-package-name';
15
+
16
+ const logger = new Logger();
17
+
18
+ logger.info('Hello, world!');
19
+ logger.error('I AM ERROR');
20
+ ```
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @logger-works/logger
26
+ ```
27
+
28
+ ## Features
29
+
30
+ - ๐Ÿ”„ Multiple argument patterns supported (console-style, Pino-style, printf-style)
31
+ - ๐ŸŽฏ TypeScript support with full type definitions
32
+ - ๐Ÿงช Automatic test environment detection (disables output in `NODE_ENV=test`)
33
+ - ๐Ÿš€ Zero-config defaults with STDOUT/STDERR separation
34
+ - ๐Ÿ“ฆ Access to underlying Pino instance for advanced usage
35
+
36
+ ## Usage Patterns
37
+
38
+ The logger intelligently handles different argument patterns to accommodate various logging styles:
39
+
40
+ ### 1. Simple String Message
41
+
42
+ ```typescript
43
+ logger.info('Server started on port 3000');
44
+ // Output: {"level":30,"time":1234567890,"msg":"Server started on port 3000"}
45
+ ```
46
+
47
+ ### 2. Pino-Style (Object First)
48
+
49
+ Standard Pino pattern with structured data first, message second:
50
+
51
+ ```typescript
52
+ logger.info({ userId: 123, action: 'login' }, 'User logged in');
53
+ // Output: {"level":30,"time":1234567890,"userId":123,"action":"login","msg":"User logged in"}
54
+ ```
55
+
56
+ ### 3. Console-Style (Message First, Object Second)
57
+
58
+ Automatically swaps arguments to Pino format:
59
+
60
+ ```typescript
61
+ logger.info('User logged in', { userId: 123, action: 'login' });
62
+ // Output: {"level":30,"time":1234567890,"userId":123,"action":"login","msg":"User logged in"}
63
+ ```
64
+
65
+ ### 4. Printf-Style Formatting
66
+
67
+ Uses Pino's built-in printf formatting when `%` is detected:
68
+
69
+ ```typescript
70
+ logger.info('User %s logged in from %s', 'john', '192.168.1.1');
71
+ // Output: {"level":30,"time":1234567890,"msg":"User john logged in from 192.168.1.1"}
72
+ ```
73
+
74
+ ### 5. Message with Multiple Arguments
75
+
76
+ Wraps additional arguments in a structured `args` field:
77
+
78
+ ```typescript
79
+ logger.info('Processing items', 'item1', 'item2', 'item3');
80
+ // Output: {"level":30,"time":1234567890,"args":["item1","item2","item3"],"msg":"Processing items"}
81
+ ```
82
+
83
+ ### 6. Primitive Values
84
+
85
+ Non-string primitives are wrapped in an `args` array:
86
+
87
+ ```typescript
88
+ logger.info(42, true, null);
89
+ // Output: {"level":30,"time":1234567890,"args":[42,true,null],"msg":"Log event"}
90
+ ```
91
+
92
+ ## API
93
+
94
+ All methods support the argument patterns described above:
95
+
96
+ - `logger.info(...args)` - Info level logs
97
+ - `logger.error(...args)` - Error level logs
98
+ - `logger.warn(...args)` - Warning level logs
99
+ - `logger.debug(...args)` - Debug level logs
100
+ - `logger.log(level, ...args)` - Log at specified level
101
+
102
+ ### Access Raw Pino Instance
103
+
104
+ For advanced Pino features:
105
+
106
+ ```typescript
107
+ const pinoInstance = logger.getRawLogger();
108
+ pinoInstance.child({ requestId: '123' });
109
+ ```
110
+
111
+ ## Default Behavior
112
+
113
+ ### Output Destinations
114
+
115
+ By default, the logger sends:
116
+ - **Info, warn, debug** logs โ†’ STDOUT (file descriptor 1)
117
+ - **Error** logs โ†’ STDERR (file descriptor 2)
118
+
119
+ ### Test Environment
120
+
121
+ When `NODE_ENV=test`, all output is automatically disabled to keep test output clean.
122
+
123
+ ### Custom Targets
124
+
125
+ Override default targets with your own configuration:
126
+
127
+ ```typescript
128
+ const logger = new Logger({
129
+ transport: {
130
+ targets: [
131
+ {
132
+ target: 'pino-pretty',
133
+ options: { colorize: true }
134
+ }
135
+ ]
136
+ }
137
+ });
138
+ ```
139
+
140
+ ## License
141
+
142
+ MIT License - See [LICENSE](LICENSE) file for details.
143
+
144
+ ---
145
+
146
+ Built with โšก by [loggerWorks](https://github.com/logger-works)
package/biome.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "formatter": {
3
+ "indentStyle": "space",
4
+ "indentWidth": 2,
5
+ "lineWidth": 120,
6
+ "lineEnding": "lf"
7
+ },
8
+ "linter": {
9
+ "rules": {
10
+ "suspicious": {
11
+ "noExplicitAny": "off"
12
+ }
13
+ }
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@fimbul-works/logger",
3
+ "description": "Simple wrapper for Pino",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "author": "FimbulWorks <contact@fimbul.works>",
9
+ "homepage": "https://github.com/fimbul-works/logger#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/fimbul-works/logger.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/fimbul-works/logger/issues"
16
+ },
17
+ "keywords": [
18
+ "pino",
19
+ "logger",
20
+ "typescript"
21
+ ],
22
+ "scripts": {
23
+ "build": "rm -rf dist && pnpm build:esm && pnpm build:cjs",
24
+ "build:esm": "tsc -p tsconfig.json",
25
+ "build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
26
+ "format": "biome format --write",
27
+ "test": "NODE_ENV=test vitest run",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "dependencies": {
31
+ "pino": "^10.3.0"
32
+ },
33
+ "devDependencies": {
34
+ "@biomejs/biome": "^2.3.14",
35
+ "@types/node": "^25.2.1",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^4.0.18"
38
+ }
39
+ }
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Logger } from "./index";
3
+
4
+ // Mock Pino
5
+ vi.mock("pino", () => {
6
+ const info = vi.fn();
7
+ const error = vi.fn();
8
+ const warn = vi.fn();
9
+ const debug = vi.fn();
10
+ const logger = { info, error, warn, debug };
11
+ return {
12
+ default: vi.fn(() => logger),
13
+ };
14
+ });
15
+
16
+ describe("Logger", () => {
17
+ it("should log info messages", () => {
18
+ const logger = new Logger();
19
+ logger.info("Hello world");
20
+
21
+ const rawLogger = logger.getRawLogger();
22
+ expect(rawLogger.info).toHaveBeenCalledWith("Hello world");
23
+ });
24
+
25
+ it("should log error messages", () => {
26
+ const logger = new Logger();
27
+ const err = new Error("Test error");
28
+
29
+ logger.error("Something went wrong", err);
30
+
31
+ const rawLogger = logger.getRawLogger();
32
+ expect(rawLogger.error).toHaveBeenCalledWith(err, "Something went wrong");
33
+ });
34
+
35
+ describe("Smart Argument Handling", () => {
36
+ it("should swap args when calling (msg, obj)", () => {
37
+ const logger = new Logger();
38
+ const rawLogger = logger.getRawLogger();
39
+ const obj = { id: 123 };
40
+
41
+ logger.info("User login", obj);
42
+
43
+ // Expect swap: (obj, msg)
44
+ expect(rawLogger.info).toHaveBeenCalledWith(obj, "User login");
45
+ });
46
+
47
+ it("should wrap multiple args in payload object", () => {
48
+ const logger = new Logger();
49
+ const rawLogger = logger.getRawLogger();
50
+
51
+ logger.info("Values", 1, true, "extra");
52
+
53
+ // Expect wrap: ({ args: [1, true, "extra"] }, msg)
54
+ expect(rawLogger.info).toHaveBeenCalledWith({ args: [1, true, "extra"] }, "Values");
55
+ });
56
+
57
+ it("should respect printf style formatting", () => {
58
+ const logger = new Logger();
59
+ const rawLogger = logger.getRawLogger();
60
+
61
+ logger.info("User %s logged in", "Alice");
62
+
63
+ // Expect pass-through: (msg, ...args)
64
+ expect(rawLogger.info).toHaveBeenCalledWith("User %s logged in", "Alice");
65
+ });
66
+
67
+ it("should handle mixed printf and extra args (best effort)", () => {
68
+ const logger = new Logger();
69
+ const rawLogger = logger.getRawLogger();
70
+
71
+ // If user mixes printf with too many args, we just pass through and let Pino handle/ignore
72
+ logger.info("User %s", "Alice", "ExtraIgnored");
73
+
74
+ expect(rawLogger.info).toHaveBeenCalledWith("User %s", "Alice", "ExtraIgnored");
75
+ });
76
+
77
+ it("should pass through (obj, msg) as is", () => {
78
+ const logger = new Logger();
79
+ const rawLogger = logger.getRawLogger();
80
+ const obj = { id: 1 };
81
+
82
+ logger.info(obj, "Message");
83
+
84
+ expect(rawLogger.info).toHaveBeenCalledWith(obj, "Message");
85
+ });
86
+ });
87
+ });
package/src/index.ts ADDED
@@ -0,0 +1,170 @@
1
+ import pino, { type Logger as Pino } from "pino";
2
+
3
+ /**
4
+ * Log level.
5
+ */
6
+ export type LogLevel = "info" | "error" | "warn" | "debug";
7
+
8
+ /**
9
+ * Configuration options for the Logger.
10
+ */
11
+ export interface LoggerOptions extends pino.LoggerOptions {
12
+ /**
13
+ * Optional name label (e.g., "http")
14
+ */
15
+ name?: string;
16
+
17
+ /**
18
+ * Optional log level (e.g., "info"), default: "info"
19
+ */
20
+ level?: string;
21
+
22
+ /**
23
+ * Optional targets, default: STDOUT & STDERR, disabled when NODE_ENV="test"
24
+ */
25
+ targets?: pino.TransportTargetOptions[];
26
+ }
27
+
28
+ /**
29
+ * A wrapper around Pino to provide a consistent logging interface.
30
+ */
31
+ export class Logger {
32
+ /**
33
+ * pino instance.
34
+ *
35
+ * @private
36
+ * @type {Pino}
37
+ */
38
+ private logger: Pino;
39
+
40
+ /**
41
+ * Creates a new Logger instance.
42
+ *
43
+ * @param {LoggerOptions} options - Optional configuration options for the logger
44
+ */
45
+ constructor(options: pino.LoggerOptions = {}) {
46
+ this.logger = pino({
47
+ level: "info",
48
+ ...options,
49
+ transport: {
50
+ targets:
51
+ process.env.NODE_ENV !== "test"
52
+ ? [
53
+ {
54
+ target: "pino/file",
55
+ options: { destination: 1 }, // STDOUT
56
+ level: options.level || "info",
57
+ },
58
+ {
59
+ target: "pino/file",
60
+ options: { destination: 2 }, // STDERR
61
+ level: "error",
62
+ },
63
+ ]
64
+ : [],
65
+ ...options.transport,
66
+ },
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Logs a message at the specified level.
72
+ *
73
+ * @param {LogLevel} level - The log level.
74
+ */
75
+ public log(level: LogLevel, ...args: any[]): void {
76
+ this.handleLog(level, args);
77
+ }
78
+
79
+ /**
80
+ * Logs a message at the `info` level.
81
+ */
82
+ public info(...args: any[]): void {
83
+ this.handleLog("info", args);
84
+ }
85
+
86
+ /**
87
+ * Logs a message at the `error` level.
88
+ */
89
+ public error(...args: any[]): void {
90
+ this.handleLog("error", args);
91
+ }
92
+
93
+ /**
94
+ * Logs a message at the `warn` level.
95
+ */
96
+ public warn(...args: any[]): void {
97
+ this.handleLog("warn", args);
98
+ }
99
+
100
+ /**
101
+ * Logs a message at the `debug` level.
102
+ */
103
+ public debug(...args: any[]): void {
104
+ this.handleLog("debug", args);
105
+ }
106
+
107
+ /**
108
+ * Returns the underlying Pino logger instance.
109
+ *
110
+ * @returns {Pino} The Pino logger instance.
111
+ */
112
+ public getRawLogger(): Pino {
113
+ return this.logger;
114
+ }
115
+
116
+ /**
117
+ * Handles logging by parsing arguments and calling the appropriate Pino method.
118
+ *
119
+ * @private
120
+ * @param {LogLevel} level
121
+ * @param {any[]} args
122
+ */
123
+ private handleLog(level: LogLevel, args: any[]): void {
124
+ if (args.length === 0) {
125
+ this.logger[level]("");
126
+ return;
127
+ }
128
+
129
+ const [first, ...rest] = args;
130
+
131
+ // Case 1: Object first -> Pass through (Standard Pino)
132
+ // logger.info({ id: 1 }, "msg")
133
+ if (typeof first === "object" && first !== null) {
134
+ this.logger[level](first, ...rest);
135
+ return;
136
+ }
137
+
138
+ // Case 2: String first
139
+ if (typeof first === "string") {
140
+ // Check for printf-style formatting
141
+ // Simple heuristic: if string contains %, treat as printf and pass through
142
+ if (first.includes("%") && rest.length > 0) {
143
+ this.logger[level](first, ...rest);
144
+ return;
145
+ }
146
+
147
+ // Check for (msg, obj) pattern -> Swap to (obj, msg) (Console style)
148
+ if (rest.length === 1 && typeof rest[0] === "object" && rest[0] !== null) {
149
+ this.logger[level](rest[0], first);
150
+ return;
151
+ }
152
+
153
+ // Check for (msg, ...vars) pattern -> Wrap in payload
154
+ if (rest.length > 0) {
155
+ this.logger[level]({ args: rest }, first);
156
+ return;
157
+ }
158
+
159
+ // Just a single string message
160
+ this.logger[level](first);
161
+ return;
162
+ }
163
+
164
+ // Case 3: Primitive first (non-string output) -> Wrap in payload
165
+ // logger.info(1, true)
166
+ this.logger[level]({ args }, "Log event");
167
+ }
168
+ }
169
+
170
+ export default Logger;
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "CommonJS",
5
+ "moduleResolution": "node",
6
+ "outDir": "dist/cjs",
7
+ "verbatimModuleSyntax": false
8
+ }
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "Bundler",
7
+ "declaration": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "outDir": "./dist/esm/"
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["src/**/*.test.ts", "node_modules"]
17
+ }