@beignet/provider-logger-pino 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @beignet/provider-logger-pino
2
+
3
+ ## 0.0.1
4
+
5
+ - Initial Beignet release under the `@beignet` npm scope.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @beignet/provider-logger-pino
2
+
3
+ Pino-backed `LoggerPort` provider for Beignet.
4
+
5
+ Use this package when you want `ctx.ports.logger` backed by Pino while keeping
6
+ application code dependent on the stable `LoggerPort` interface from
7
+ `@beignet/core/ports`.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ bun add @beignet/core @beignet/provider-logger-pino pino
13
+ ```
14
+
15
+ Pretty development logs are optional:
16
+
17
+ ```bash
18
+ bun add -d pino-pretty
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ ```ts
24
+ import { createNextServer } from "@beignet/next";
25
+ import { loggerPinoProvider } from "@beignet/provider-logger-pino";
26
+ import { appPorts } from "@/infra/app-ports";
27
+
28
+ export const server = await createNextServer({
29
+ ports: appPorts,
30
+ providers: [loggerPinoProvider],
31
+ createContext: ({ ports }) => ({
32
+ requestId: crypto.randomUUID(),
33
+ ports,
34
+ }),
35
+ });
36
+ ```
37
+
38
+ The provider installs `ctx.ports.logger`.
39
+
40
+ ## Environment
41
+
42
+ The provider reads `LOG_`-prefixed environment variables:
43
+
44
+ | Variable | Default | Description |
45
+ | --- | --- | --- |
46
+ | `LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, `error`, or `fatal` |
47
+ | `LOG_FORMAT` | `json` | `json` or `pretty` |
48
+ | `LOG_SERVICE` | none | Optional service name added to log bindings |
49
+ | `LOG_TIMESTAMP` | `true` | Include ISO timestamps |
50
+
51
+ `LOG_FORMAT=pretty` requires `pino-pretty`. If it is unavailable, the provider
52
+ falls back to JSON logging and emits a warning.
53
+
54
+ ## Usage
55
+
56
+ ```ts
57
+ ctx.ports.logger.info("Post published", {
58
+ postId: post.id,
59
+ actorId: ctx.actor.type === "user" ? ctx.actor.id : undefined,
60
+ });
61
+
62
+ const log = ctx.ports.logger.child({ requestId: ctx.requestId });
63
+ log.error("Failed to publish post", { error });
64
+ ```
65
+
66
+ ## Exports
67
+
68
+ | Export | Purpose |
69
+ | --- | --- |
70
+ | `loggerPinoProvider` | Ready-to-install Beignet provider |
71
+ | `LoggerPinoConfig` | Validated provider config type |
72
+ | `LoggerPort` | Re-export from `@beignet/core/ports` |
73
+ | `LogLevel` | Re-export from `@beignet/core/ports` |
74
+
75
+ ## Read next
76
+
77
+ - Docs site logging guide: https://beignet.dev/logging
78
+ - Providers: https://beignet.dev/providers
79
+ - Pino documentation: https://getpino.io/
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @beignet/provider-logger-pino
3
+ *
4
+ * Pino logger provider that extends ports with structured logging capabilities using Pino.
5
+ *
6
+ * Configuration:
7
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
8
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
9
+ * - LOG_SERVICE: Optional service name for log enrichment
10
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { createNextServer } from "@beignet/next";
15
+ * import { loggerPinoProvider } from "@beignet/provider-logger-pino";
16
+ *
17
+ * const server = await createNextServer({
18
+ * ports: basePorts,
19
+ * providers: [loggerPinoProvider],
20
+ * // ...
21
+ * });
22
+ *
23
+ * // In your use cases:
24
+ * ctx.ports.logger.info("User logged in", { userId: user.id });
25
+ *
26
+ * // Create child logger with request context:
27
+ * const log = ctx.ports.logger.child({ requestId: ctx.requestId });
28
+ * log.info("Processing request");
29
+ * ```
30
+ */
31
+ import type { LoggerPort } from "@beignet/core/ports";
32
+ import { z } from "zod";
33
+ export type { LoggerPort, LogLevel } from "@beignet/core/ports";
34
+ /**
35
+ * Configuration schema for the Pino logger provider.
36
+ * Validates environment variables with LOG_ prefix.
37
+ */
38
+ declare const LoggerPinoConfigSchema: z.ZodObject<{
39
+ LEVEL: z.ZodDefault<z.ZodEnum<{
40
+ trace: "trace";
41
+ debug: "debug";
42
+ info: "info";
43
+ warn: "warn";
44
+ error: "error";
45
+ fatal: "fatal";
46
+ }>>;
47
+ FORMAT: z.ZodDefault<z.ZodEnum<{
48
+ json: "json";
49
+ pretty: "pretty";
50
+ }>>;
51
+ SERVICE: z.ZodOptional<z.ZodString>;
52
+ TIMESTAMP: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
53
+ }, z.core.$strip>;
54
+ /**
55
+ * Inferred configuration type for Pino logger provider.
56
+ */
57
+ export type LoggerPinoConfig = z.infer<typeof LoggerPinoConfigSchema>;
58
+ /**
59
+ * Pino logger provider that extends ports with structured logging capabilities.
60
+ *
61
+ * Configuration via environment variables:
62
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
63
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
64
+ * - LOG_SERVICE: Optional service name for log enrichment
65
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const server = await createNextServer({
70
+ * ports: basePorts,
71
+ * providers: [loggerPinoProvider],
72
+ * // ...
73
+ * });
74
+ * ```
75
+ */
76
+ export declare const loggerPinoProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
77
+ LEVEL: z.ZodDefault<z.ZodEnum<{
78
+ trace: "trace";
79
+ debug: "debug";
80
+ info: "info";
81
+ warn: "warn";
82
+ error: "error";
83
+ fatal: "fatal";
84
+ }>>;
85
+ FORMAT: z.ZodDefault<z.ZodEnum<{
86
+ json: "json";
87
+ pretty: "pretty";
88
+ }>>;
89
+ SERVICE: z.ZodOptional<z.ZodString>;
90
+ TIMESTAMP: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
91
+ }, z.core.$strip>, {
92
+ logger: LoggerPort;
93
+ }>;
94
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;GAGG;AACH,QAAA,MAAM,sBAAsB;;;;;;;;;;;;;;;iBA6B1B,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AA4EtE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;EA8B7B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @beignet/provider-logger-pino
3
+ *
4
+ * Pino logger provider that extends ports with structured logging capabilities using Pino.
5
+ *
6
+ * Configuration:
7
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
8
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
9
+ * - LOG_SERVICE: Optional service name for log enrichment
10
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { createNextServer } from "@beignet/next";
15
+ * import { loggerPinoProvider } from "@beignet/provider-logger-pino";
16
+ *
17
+ * const server = await createNextServer({
18
+ * ports: basePorts,
19
+ * providers: [loggerPinoProvider],
20
+ * // ...
21
+ * });
22
+ *
23
+ * // In your use cases:
24
+ * ctx.ports.logger.info("User logged in", { userId: user.id });
25
+ *
26
+ * // Create child logger with request context:
27
+ * const log = ctx.ports.logger.child({ requestId: ctx.requestId });
28
+ * log.info("Processing request");
29
+ * ```
30
+ */
31
+ import { createProvider } from "@beignet/core/providers";
32
+ import pino from "pino";
33
+ import { z } from "zod";
34
+ /**
35
+ * Configuration schema for the Pino logger provider.
36
+ * Validates environment variables with LOG_ prefix.
37
+ */
38
+ const LoggerPinoConfigSchema = z.object({
39
+ /**
40
+ * Minimum log level.
41
+ * Messages below this level will be ignored.
42
+ * Default: "info"
43
+ */
44
+ LEVEL: z
45
+ .enum(["trace", "debug", "info", "warn", "error", "fatal"])
46
+ .default("info"),
47
+ /**
48
+ * Log format.
49
+ * - "json": Newline-delimited JSON (production-friendly)
50
+ * - "pretty": Human-readable format (requires pino-pretty)
51
+ * Default: "json"
52
+ */
53
+ FORMAT: z.enum(["json", "pretty"]).default("json"),
54
+ /**
55
+ * Optional service name for log enrichment.
56
+ * Will be added to all log entries as { service: "your-service" }
57
+ */
58
+ SERVICE: z.string().optional(),
59
+ /**
60
+ * Whether to include timestamps in log entries.
61
+ * Default: true
62
+ */
63
+ TIMESTAMP: z.coerce.boolean().default(true),
64
+ });
65
+ /**
66
+ * Create a Pino logger instance with the given configuration.
67
+ *
68
+ * @param config - Validated logger configuration
69
+ * @param bindings - Optional base bindings to add to all log entries
70
+ * @returns Configured Pino logger instance
71
+ */
72
+ function createPinoLogger(config, bindings = {}) {
73
+ const pinoOptions = {
74
+ level: config.LEVEL,
75
+ timestamp: config.TIMESTAMP ? pino.stdTimeFunctions.isoTime : false,
76
+ base: bindings,
77
+ };
78
+ // Handle pretty format
79
+ if (config.FORMAT === "pretty") {
80
+ try {
81
+ // Attempt to use pino-pretty transport
82
+ return pino({
83
+ ...pinoOptions,
84
+ transport: {
85
+ target: "pino-pretty",
86
+ options: {
87
+ colorize: true,
88
+ translateTime: "HH:MM:ss Z",
89
+ ignore: "pid,hostname",
90
+ },
91
+ },
92
+ });
93
+ }
94
+ catch (_error) {
95
+ // pino-pretty not available, fall back to JSON with warning
96
+ const jsonLogger = pino(pinoOptions);
97
+ jsonLogger.warn("LOG_FORMAT=pretty requested but pino-pretty is not installed. " +
98
+ "Falling back to JSON format. Install pino-pretty to enable pretty printing.");
99
+ return jsonLogger;
100
+ }
101
+ }
102
+ // Default to JSON format
103
+ return pino(pinoOptions);
104
+ }
105
+ /**
106
+ * Wrap a Pino logger instance to conform to the LoggerPort interface.
107
+ *
108
+ * Pino's API uses the signature: logger.level(obj, msg) or logger.level(msg)
109
+ * We adapt this to: logger.level(msg, meta?)
110
+ *
111
+ * @param logger - Pino logger instance to wrap
112
+ * @returns LoggerPort implementation
113
+ */
114
+ function wrapPino(logger) {
115
+ return {
116
+ trace: (message, meta) => meta ? logger.trace(meta, message) : logger.trace(message),
117
+ debug: (message, meta) => meta ? logger.debug(meta, message) : logger.debug(message),
118
+ info: (message, meta) => meta ? logger.info(meta, message) : logger.info(message),
119
+ warn: (message, meta) => meta ? logger.warn(meta, message) : logger.warn(message),
120
+ error: (message, meta) => meta ? logger.error(meta, message) : logger.error(message),
121
+ fatal: (message, meta) => meta ? logger.fatal(meta, message) : logger.fatal(message),
122
+ child: (bindings) => wrapPino(logger.child(bindings)),
123
+ };
124
+ }
125
+ /**
126
+ * Pino logger provider that extends ports with structured logging capabilities.
127
+ *
128
+ * Configuration via environment variables:
129
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
130
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
131
+ * - LOG_SERVICE: Optional service name for log enrichment
132
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const server = await createNextServer({
137
+ * ports: basePorts,
138
+ * providers: [loggerPinoProvider],
139
+ * // ...
140
+ * });
141
+ * ```
142
+ */
143
+ export const loggerPinoProvider = createProvider({
144
+ name: "logger-pino",
145
+ config: {
146
+ schema: LoggerPinoConfigSchema,
147
+ envPrefix: "LOG_",
148
+ },
149
+ async setup({ config }) {
150
+ if (!config) {
151
+ throw new Error("[loggerPinoProvider] Missing logger config. " +
152
+ "Please set LOG_LEVEL, LOG_FORMAT, etc. environment variables.");
153
+ }
154
+ // Prepare base bindings
155
+ const baseBindings = {};
156
+ if (config.SERVICE) {
157
+ baseBindings.service = config.SERVICE;
158
+ }
159
+ // Create and configure Pino logger
160
+ const logger = createPinoLogger(config, baseBindings);
161
+ // Wrap logger to match LoggerPort interface
162
+ const port = wrapPino(logger);
163
+ return { ports: { logger: port } };
164
+ },
165
+ });
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,IAAmC,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC;;;;OAIG;IACH,KAAK,EAAE,CAAC;SACL,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;SAC1D,OAAO,CAAC,MAAM,CAAC;IAElB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAElD;;;OAGG;IACH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAE9B;;;OAGG;IACH,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CAC5C,CAAC,CAAC;AAOH;;;;;;GAMG;AACH,SAAS,gBAAgB,CACvB,MAAwB,EACxB,WAAoC,EAAE;IAEtC,MAAM,WAAW,GAAuB;QACtC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;QACnE,IAAI,EAAE,QAAQ;KACf,CAAC;IAEF,uBAAuB;IACvB,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,uCAAuC;YACvC,OAAO,IAAI,CAAC;gBACV,GAAG,WAAW;gBACd,SAAS,EAAE;oBACT,MAAM,EAAE,aAAa;oBACrB,OAAO,EAAE;wBACP,QAAQ,EAAE,IAAI;wBACd,aAAa,EAAE,YAAY;wBAC3B,MAAM,EAAE,cAAc;qBACvB;iBACF;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,4DAA4D;YAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;YACrC,UAAU,CAAC,IAAI,CACb,gEAAgE;gBAC9D,6EAA6E,CAChF,CAAC;YACF,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,QAAQ,CAAC,MAAkB;IAClC,OAAO;QACL,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACvB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5D,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACvB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5D,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACtB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAC1D,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACtB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAC1D,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACvB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5D,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACvB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5D,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAC;IAC/C,IAAI,EAAE,aAAa;IAEnB,MAAM,EAAE;QACN,MAAM,EAAE,sBAAsB;QAC9B,SAAS,EAAE,MAAM;KAClB;IAED,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE;QACpB,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,8CAA8C;gBAC5C,+DAA+D,CAClE,CAAC;QACJ,CAAC;QAED,wBAAwB;QACxB,MAAM,YAAY,GAA4B,EAAE,CAAC;QACjD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACxC,CAAC;QAED,mCAAmC;QACnC,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAEtD,4CAA4C;QAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE9B,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;IACrC,CAAC;CACF,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@beignet/provider-logger-pino",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Pino logger provider for Beignet - adds logger port using pino",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "!src/**/*.test.ts",
18
+ "!src/**/*.test.tsx",
19
+ "!src/**/*.test-d.ts",
20
+ "README.md",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsc --watch",
26
+ "clean": "rm -rf dist coverage .turbo",
27
+ "test": "bun test",
28
+ "test:coverage": "bun test --coverage",
29
+ "lint": "biome check ."
30
+ },
31
+ "keywords": [
32
+ "beignet",
33
+ "logger",
34
+ "logging",
35
+ "provider",
36
+ "pino",
37
+ "structured-logging",
38
+ "ports"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/taylorbryant/beignet.git",
44
+ "directory": "packages/provider-logger-pino"
45
+ },
46
+ "author": "Taylor Bryant",
47
+ "homepage": "https://github.com/taylorbryant/beignet#readme",
48
+ "bugs": "https://github.com/taylorbryant/beignet/issues",
49
+ "sideEffects": false,
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "peerDependencies": {
57
+ "pino": "^9.0.0 || ^10.0.0"
58
+ },
59
+ "dependencies": {
60
+ "zod": "^4.0.0",
61
+ "@beignet/core": "*"
62
+ },
63
+ "devDependencies": {
64
+ "@types/bun": "^1.3.13",
65
+ "@types/node": "^20.10.0",
66
+ "pino": "^10.0.0",
67
+ "typescript": "^5.3.0"
68
+ }
69
+ }
package/src/index.ts ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * @beignet/provider-logger-pino
3
+ *
4
+ * Pino logger provider that extends ports with structured logging capabilities using Pino.
5
+ *
6
+ * Configuration:
7
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
8
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
9
+ * - LOG_SERVICE: Optional service name for log enrichment
10
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { createNextServer } from "@beignet/next";
15
+ * import { loggerPinoProvider } from "@beignet/provider-logger-pino";
16
+ *
17
+ * const server = await createNextServer({
18
+ * ports: basePorts,
19
+ * providers: [loggerPinoProvider],
20
+ * // ...
21
+ * });
22
+ *
23
+ * // In your use cases:
24
+ * ctx.ports.logger.info("User logged in", { userId: user.id });
25
+ *
26
+ * // Create child logger with request context:
27
+ * const log = ctx.ports.logger.child({ requestId: ctx.requestId });
28
+ * log.info("Processing request");
29
+ * ```
30
+ */
31
+
32
+ import type { LoggerPort } from "@beignet/core/ports";
33
+ import { createProvider } from "@beignet/core/providers";
34
+ import pino, { type Logger as PinoLogger } from "pino";
35
+ import { z } from "zod";
36
+
37
+ export type { LoggerPort, LogLevel } from "@beignet/core/ports";
38
+
39
+ /**
40
+ * Configuration schema for the Pino logger provider.
41
+ * Validates environment variables with LOG_ prefix.
42
+ */
43
+ const LoggerPinoConfigSchema = z.object({
44
+ /**
45
+ * Minimum log level.
46
+ * Messages below this level will be ignored.
47
+ * Default: "info"
48
+ */
49
+ LEVEL: z
50
+ .enum(["trace", "debug", "info", "warn", "error", "fatal"])
51
+ .default("info"),
52
+
53
+ /**
54
+ * Log format.
55
+ * - "json": Newline-delimited JSON (production-friendly)
56
+ * - "pretty": Human-readable format (requires pino-pretty)
57
+ * Default: "json"
58
+ */
59
+ FORMAT: z.enum(["json", "pretty"]).default("json"),
60
+
61
+ /**
62
+ * Optional service name for log enrichment.
63
+ * Will be added to all log entries as { service: "your-service" }
64
+ */
65
+ SERVICE: z.string().optional(),
66
+
67
+ /**
68
+ * Whether to include timestamps in log entries.
69
+ * Default: true
70
+ */
71
+ TIMESTAMP: z.coerce.boolean().default(true),
72
+ });
73
+
74
+ /**
75
+ * Inferred configuration type for Pino logger provider.
76
+ */
77
+ export type LoggerPinoConfig = z.infer<typeof LoggerPinoConfigSchema>;
78
+
79
+ /**
80
+ * Create a Pino logger instance with the given configuration.
81
+ *
82
+ * @param config - Validated logger configuration
83
+ * @param bindings - Optional base bindings to add to all log entries
84
+ * @returns Configured Pino logger instance
85
+ */
86
+ function createPinoLogger(
87
+ config: LoggerPinoConfig,
88
+ bindings: Record<string, unknown> = {},
89
+ ): PinoLogger {
90
+ const pinoOptions: pino.LoggerOptions = {
91
+ level: config.LEVEL,
92
+ timestamp: config.TIMESTAMP ? pino.stdTimeFunctions.isoTime : false,
93
+ base: bindings,
94
+ };
95
+
96
+ // Handle pretty format
97
+ if (config.FORMAT === "pretty") {
98
+ try {
99
+ // Attempt to use pino-pretty transport
100
+ return pino({
101
+ ...pinoOptions,
102
+ transport: {
103
+ target: "pino-pretty",
104
+ options: {
105
+ colorize: true,
106
+ translateTime: "HH:MM:ss Z",
107
+ ignore: "pid,hostname",
108
+ },
109
+ },
110
+ });
111
+ } catch (_error) {
112
+ // pino-pretty not available, fall back to JSON with warning
113
+ const jsonLogger = pino(pinoOptions);
114
+ jsonLogger.warn(
115
+ "LOG_FORMAT=pretty requested but pino-pretty is not installed. " +
116
+ "Falling back to JSON format. Install pino-pretty to enable pretty printing.",
117
+ );
118
+ return jsonLogger;
119
+ }
120
+ }
121
+
122
+ // Default to JSON format
123
+ return pino(pinoOptions);
124
+ }
125
+
126
+ /**
127
+ * Wrap a Pino logger instance to conform to the LoggerPort interface.
128
+ *
129
+ * Pino's API uses the signature: logger.level(obj, msg) or logger.level(msg)
130
+ * We adapt this to: logger.level(msg, meta?)
131
+ *
132
+ * @param logger - Pino logger instance to wrap
133
+ * @returns LoggerPort implementation
134
+ */
135
+ function wrapPino(logger: PinoLogger): LoggerPort {
136
+ return {
137
+ trace: (message, meta) =>
138
+ meta ? logger.trace(meta, message) : logger.trace(message),
139
+ debug: (message, meta) =>
140
+ meta ? logger.debug(meta, message) : logger.debug(message),
141
+ info: (message, meta) =>
142
+ meta ? logger.info(meta, message) : logger.info(message),
143
+ warn: (message, meta) =>
144
+ meta ? logger.warn(meta, message) : logger.warn(message),
145
+ error: (message, meta) =>
146
+ meta ? logger.error(meta, message) : logger.error(message),
147
+ fatal: (message, meta) =>
148
+ meta ? logger.fatal(meta, message) : logger.fatal(message),
149
+ child: (bindings) => wrapPino(logger.child(bindings)),
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Pino logger provider that extends ports with structured logging capabilities.
155
+ *
156
+ * Configuration via environment variables:
157
+ * - LOG_LEVEL: Minimum log level (trace/debug/info/warn/error/fatal, default: info)
158
+ * - LOG_FORMAT: Log format (json/pretty, default: json)
159
+ * - LOG_SERVICE: Optional service name for log enrichment
160
+ * - LOG_TIMESTAMP: Whether to include timestamps (true/false, default: true)
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const server = await createNextServer({
165
+ * ports: basePorts,
166
+ * providers: [loggerPinoProvider],
167
+ * // ...
168
+ * });
169
+ * ```
170
+ */
171
+ export const loggerPinoProvider = createProvider({
172
+ name: "logger-pino",
173
+
174
+ config: {
175
+ schema: LoggerPinoConfigSchema,
176
+ envPrefix: "LOG_",
177
+ },
178
+
179
+ async setup({ config }) {
180
+ if (!config) {
181
+ throw new Error(
182
+ "[loggerPinoProvider] Missing logger config. " +
183
+ "Please set LOG_LEVEL, LOG_FORMAT, etc. environment variables.",
184
+ );
185
+ }
186
+
187
+ // Prepare base bindings
188
+ const baseBindings: Record<string, unknown> = {};
189
+ if (config.SERVICE) {
190
+ baseBindings.service = config.SERVICE;
191
+ }
192
+
193
+ // Create and configure Pino logger
194
+ const logger = createPinoLogger(config, baseBindings);
195
+
196
+ // Wrap logger to match LoggerPort interface
197
+ const port = wrapPino(logger);
198
+
199
+ return { ports: { logger: port } };
200
+ },
201
+ });