Skip to content

Commit

Permalink
Support using custom context key
Browse files Browse the repository at this point in the history
  • Loading branch information
maou-shonen committed Sep 23, 2024
1 parent 63e6e36 commit ed9c781
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-terms-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hono-pino": minor
---

Support using custom context key
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default config(
},
core.configs.recommended,
...typescriptConfigs.strictTypeChecked,
...typescriptConfigs.stylisticTypeChecked,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access -- I don't know why typing is broken for unicorn...
unicornConfigs["flat/recommended"],
prettierConfig,
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { logger } from "./middleware";
export { PinoLogger } from "./logger";
export type { Options } from "./types";
export { getLogger } from "./utils";
export * from "./types";
export type { Options, Env } from "./types";
65 changes: 65 additions & 0 deletions src/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Hono } from "hono";
import { logger } from "./middleware";
import type { Options } from "./types";
import { pino } from "pino";
import { PinoLogger } from "./logger";

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

Expand Down Expand Up @@ -220,3 +221,67 @@ describe("response time", () => {
expect(logs[0].responseTime).gte(1000);
});
});

describe("contextKey option", () => {
it("default", async () => {
const app = new Hono()
.use(logger())
.get("/", async (c) =>
c.text(c.get("logger") instanceof PinoLogger ? "ok" : "fail", 200),
);

const res = await app.request("/");
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});

it("custom key", async () => {
const app = new Hono()
.use(logger({ contextKey: "myLogger" as const }))
.get("/", async (c) =>
c.text(
c.get("myLogger") instanceof PinoLogger &&
// @ts-ignore
c.get("logger") === undefined
? "ok"
: "fail",
200,
),
);

const res = await app.request("/");
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});

it("multiple logger", async () => {
const app = new Hono()
.use(
logger({
contextKey: "myLogger1" as const,
pino: { name: "pinoLogger1" },
}),
)
.use(
logger({
contextKey: "myLogger2" as const,
pino: { name: "pinoLogger2" },
}),
)
.get("/", async (c) =>
c.text(
c.get("myLogger1").logger.bindings().name === "pinoLogger1" &&
c.get("myLogger2").logger.bindings().name === "pinoLogger2" &&
// @ts-ignore
c.get("logger") === undefined
? "ok"
: "fail",
200,
),
);

const res = await app.request("/");
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});
});
16 changes: 13 additions & 3 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ import { defu } from "defu";
import { isPinoLogger } from "./utils";
import type { Options } from "./types";
import { PinoLogger } from "./logger";
import type { LiteralString } from "./utils";

/**
* Pino logger middleware
*/
export const logger = (opts?: Options) => {
export const logger = <ContextKey extends string = "logger">(
opts?: Options<LiteralString<ContextKey>>,
) => {
const rootLogger = isPinoLogger(opts?.pino) ? opts.pino : pino(opts?.pino);
const contextKey = opts?.contextKey ?? ("logger" as ContextKey);

return createMiddleware(async (c, next) => {
type Env = {
Variables: {
[key in ContextKey]: PinoLogger;
};
};

return createMiddleware<Env>(async (c, next) => {
const logger = new PinoLogger(rootLogger);
c.set("logger", logger);
c.set(contextKey, logger);

// disable http logger
if (opts?.http === false) {
Expand Down
90 changes: 83 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
import type { PinoLogger } from "./logger";
import type { Context } from "hono";
import { pino } from "pino";
import type { PinoLogger } from "./logger";

declare module "hono" {
export interface ContextVariableMap {
logger: PinoLogger;
}
}
export interface Options<ContextKey extends string = "logger"> {
/**
* custom context key
* @description context key for hono, Must be set to literal string.
* @default "logger"
* @example
*
* // default
* new Hono()
* .use(logger())
* .get('/', (c) => {
* const logger = c.get("logger");
* // or use c.var
* const logger2 = c.var.logger;
* });
*
* // custom logger
* new Hono()
* .use(logger({ contextKey: "myLogger" as const }))
* .get('/', (c) => {
* const logger = c.get("myLogger");
* });
*
* // multiple logger
* new Hono()
* .use(logger({ contextKey: "myLogger1" as const }))
* .use(logger({ contextKey: "myLogger2" as const }))
* .get('/', (c) => {
* const logger1 = c.get("myLogger1");
* const logger2 = c.get("myLogger2");
* })
*
*/
contextKey?: ContextKey;

export interface Options {
/**
* a pino instance or pino options
*/
Expand Down Expand Up @@ -96,3 +124,51 @@ export interface Options {
responseTime?: boolean;
};
}

/**
* hono-pino default env for hono
* @example
* // your middleware
* import { createMiddleware } from 'hono/factory'
* import type { Env } from "hono-pino"
*
* const middleware = createMiddleware<Env>(async (c, next) => {
* const logger = c.get("logger")
* await next()
* })
*
* // multi-step app
* import { Hono } from "hono"
* import { logger, type Env } from "hono-pino"
*
* const app = new Hono<Env>()
* app.use(logger)
* app.get('/', (c) => {
* const logger = c.get("logger")
* })
*
* // custom context key
* import { Hono } from "hono"
* import { logger, type Env } from "hono-pino"
*
* const app = new Hono<Env<"myLogger">>()
* app.use(logger({ contextKey: "myLogger" as const }))
* app.get('/', (c) => {
* const logger = c.get("myLogger")
* })
*
* // merge with your env.
* import { Hono } from "hono"
* import { type Env as HonoPinoEnv } from "hono-pino"
*
* type Env = {
* foo: "bar"
* } & HonoPinoEnv
*
* const app = new Hono<Env>()
*/
export type Env<LoggerKey extends string = "logger"> = {
Variables: {
[key in LoggerKey]: PinoLogger;
};
};
14 changes: 14 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import type { Context } from "hono";
import { PinoLogger } from "./logger";
import { pino } from "pino";

/**
* get logger from context
* @deprecated Please change to use `c.get("logger")`.
*/
export function getLogger(c: Context): PinoLogger {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return c.get("logger");
}

Expand All @@ -14,3 +19,12 @@ export function isPinoLogger(value: unknown): value is pino.Logger {
typeof value.child === "function"
);
}

/**
* T must be literal string
*/
export type LiteralString<T> = T extends string
? string extends T
? never
: T
: never;

0 comments on commit ed9c781

Please sign in to comment.