From dff01d79dea316210639379ab9af92dff2afe7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Sun, 1 Feb 2026 13:56:47 -0400 Subject: [PATCH] refactor: improve logger initialization and configuration --- apps/api/src/instrument.ts | 3 - apps/api/src/routes/index.ts | 4 +- apps/api/src/shared/logger.ts | 18 +++ apps/api/src/shared/types.ts | 4 +- apps/app/src/client.tsx | 3 - .../src/routes/_unauthenticated/sign-in.tsx | 8 -- apps/app/src/server.ts | 3 - apps/app/src/shared/logger.ts | 10 +- apps/desktop/src/main.tsx | 3 - apps/desktop/src/shared/logger.ts | 9 +- apps/extension/src/entrypoints/background.ts | 3 - apps/extension/src/shared/logger.ts | 9 +- apps/mobile/src/instrument.ts | 3 - apps/mobile/src/shared/logger.ts | 5 +- packages/email/src/client.ts | 4 +- packages/observability/src/logger/index.ts | 113 ++++++++++-------- packages/workflows/src/client.ts | 4 +- 17 files changed, 114 insertions(+), 92 deletions(-) create mode 100644 apps/api/src/shared/logger.ts diff --git a/apps/api/src/instrument.ts b/apps/api/src/instrument.ts index 8fa757c0..3ccf1f13 100644 --- a/apps/api/src/instrument.ts +++ b/apps/api/src/instrument.ts @@ -1,6 +1,3 @@ -import { configureLoggerAsync } from "@init/observability/logger" import { initializeErrorMonitoring } from "@init/observability/monitoring/server" -void configureLoggerAsync() - initializeErrorMonitoring() diff --git a/apps/api/src/routes/index.ts b/apps/api/src/routes/index.ts index c76c1b86..0c206b04 100644 --- a/apps/api/src/routes/index.ts +++ b/apps/api/src/routes/index.ts @@ -1,6 +1,5 @@ import { database } from "@init/db/client" import { kv } from "@init/kv/client" -import { getLogger, LoggerCategory } from "@init/observability/logger" import { honoLogger } from "@init/observability/logger/integrations" import { captureException } from "@init/observability/monitoring" import { Scalar } from "@scalar/hono-api-reference" @@ -15,10 +14,9 @@ import v1Routes from "#routes/v1/index.ts" import workflowRoutes from "#routes/workflows.ts" import { auth } from "#shared/auth.ts" import env from "#shared/env.ts" +import { LoggerCategory, logger } from "#shared/logger.ts" import { factory } from "#shared/utils.ts" -const logger = getLogger() - const app = factory.createApp() app.use(honoLogger({ category: LoggerCategory.HONO })) diff --git a/apps/api/src/shared/logger.ts b/apps/api/src/shared/logger.ts new file mode 100644 index 00000000..cc946fe2 --- /dev/null +++ b/apps/api/src/shared/logger.ts @@ -0,0 +1,18 @@ +import { buildLogger, LoggerCategory } from "@init/observability/logger" +import { singleton } from "@init/utils/singleton" + +export const logger = singleton("logger:api", () => + buildLogger( + [ + LoggerCategory.DEFAULT, + LoggerCategory.EMAIL, + LoggerCategory.LOGTAPE, + LoggerCategory.HONO, + LoggerCategory.DRIZZLE_ORM, + LoggerCategory.INNGEST, + ], + { async: true } + ) +) + +export { LoggerCategory } diff --git a/apps/api/src/shared/types.ts b/apps/api/src/shared/types.ts index bded611c..39b947a6 100644 --- a/apps/api/src/shared/types.ts +++ b/apps/api/src/shared/types.ts @@ -1,9 +1,9 @@ import type { Database } from "@init/db/client" import type { KeyValue } from "@init/kv/client" -import type { getLogger } from "@init/observability/logger" import type { Auth, Session } from "#shared/auth.ts" +import type { logger } from "#shared/logger.ts" -type AppLogger = ReturnType +type AppLogger = typeof logger export type AppContext = { Variables: { diff --git a/apps/app/src/client.tsx b/apps/app/src/client.tsx index 2dd96644..bd37d441 100644 --- a/apps/app/src/client.tsx +++ b/apps/app/src/client.tsx @@ -1,10 +1,7 @@ -import { configureLogger } from "@init/observability/logger" import { StartClient } from "@tanstack/react-start/client" import { StrictMode } from "react" import { hydrateRoot } from "react-dom/client" -configureLogger({ isDevelopment: import.meta.env.DEV }) - hydrateRoot( document, diff --git a/apps/app/src/routes/_unauthenticated/sign-in.tsx b/apps/app/src/routes/_unauthenticated/sign-in.tsx index 26768c1f..7bb24553 100644 --- a/apps/app/src/routes/_unauthenticated/sign-in.tsx +++ b/apps/app/src/routes/_unauthenticated/sign-in.tsx @@ -6,17 +6,9 @@ import { SignInWithGitHubButton, SignInWithGoogleButton, } from "#features/auth/components/sign-in-with-social-buttons.tsx" -import { authClient } from "#shared/auth.ts" export const Route = createFileRoute("/_unauthenticated/sign-in")({ component: RouteComponent, - loader: async () => { - const session = await authClient.getSession() - - return { - session, - } - }, }) function RouteComponent() { diff --git a/apps/app/src/server.ts b/apps/app/src/server.ts index e6c5419f..5a265d1f 100644 --- a/apps/app/src/server.ts +++ b/apps/app/src/server.ts @@ -1,8 +1,5 @@ -import { configureLoggerAsync } from "@init/observability/logger" import { createStartHandler, defaultStreamHandler } from "@tanstack/react-start/server" -void configureLoggerAsync() - export default { fetch: createStartHandler(defaultStreamHandler), } diff --git a/apps/app/src/shared/logger.ts b/apps/app/src/shared/logger.ts index 27d8ac5f..d15b5e31 100644 --- a/apps/app/src/shared/logger.ts +++ b/apps/app/src/shared/logger.ts @@ -1,3 +1,9 @@ -import { getLogger } from "@init/observability/logger" +import { buildLogger, LoggerCategory } from "@init/observability/logger" +import { singleton } from "@init/utils/singleton" -export const logger = getLogger() +export const logger = singleton("logger:app", () => + buildLogger([LoggerCategory.DEFAULT], { + async: globalThis.window === undefined, + isDevelopment: import.meta.env.DEV, + }) +) diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx index d8263943..14742fc8 100644 --- a/apps/desktop/src/main.tsx +++ b/apps/desktop/src/main.tsx @@ -1,5 +1,4 @@ import type { QueryClient } from "@tanstack/react-query" -import { configureLogger } from "@init/observability/logger" import { createHashHistory, createRouter, RouterProvider } from "@tanstack/react-router" import { StrictMode } from "react" import ReactDOM from "react-dom/client" @@ -10,8 +9,6 @@ import { queryClient } from "#shared/query-client.ts" import "@init/ui/globals.css" -configureLogger({ isDevelopment: import.meta.env.DEV }) - export type RouterContext = { queryClient: QueryClient logger: typeof logger diff --git a/apps/desktop/src/shared/logger.ts b/apps/desktop/src/shared/logger.ts index 27d8ac5f..7d071140 100644 --- a/apps/desktop/src/shared/logger.ts +++ b/apps/desktop/src/shared/logger.ts @@ -1,3 +1,8 @@ -import { getLogger } from "@init/observability/logger" +import { buildLogger, LoggerCategory } from "@init/observability/logger" +import { singleton } from "@init/utils/singleton" -export const logger = getLogger() +export const logger = singleton("logger:desktop", () => + buildLogger([LoggerCategory.DEFAULT], { + isDevelopment: import.meta.env.DEV, + }) +) diff --git a/apps/extension/src/entrypoints/background.ts b/apps/extension/src/entrypoints/background.ts index 5b3e4479..80587614 100644 --- a/apps/extension/src/entrypoints/background.ts +++ b/apps/extension/src/entrypoints/background.ts @@ -1,10 +1,7 @@ -import { configureLogger } from "@init/observability/logger" import { browser } from "wxt/browser" import { defineBackground } from "wxt/utils/define-background" import { logger } from "#shared/logger.ts" -configureLogger({ isDevelopment: import.meta.env.DEV }) - export default defineBackground({ main: () => { logger.with({ id: browser.runtime.id }).info`Hello from the background script!` diff --git a/apps/extension/src/shared/logger.ts b/apps/extension/src/shared/logger.ts index 27d8ac5f..9039af81 100644 --- a/apps/extension/src/shared/logger.ts +++ b/apps/extension/src/shared/logger.ts @@ -1,3 +1,8 @@ -import { getLogger } from "@init/observability/logger" +import { buildLogger, LoggerCategory } from "@init/observability/logger" +import { singleton } from "@init/utils/singleton" -export const logger = getLogger() +export const logger = singleton("logger:extension", () => + buildLogger([LoggerCategory.DEFAULT], { + isDevelopment: import.meta.env.DEV, + }) +) diff --git a/apps/mobile/src/instrument.ts b/apps/mobile/src/instrument.ts index 595e4d60..0cd08082 100644 --- a/apps/mobile/src/instrument.ts +++ b/apps/mobile/src/instrument.ts @@ -1,5 +1,2 @@ -import { configureLogger } from "@init/observability/logger" import { initializeErrorMonitoring } from "@init/observability/monitoring/expo" - -configureLogger() initializeErrorMonitoring() diff --git a/apps/mobile/src/shared/logger.ts b/apps/mobile/src/shared/logger.ts index 27d8ac5f..b0a7ca2f 100644 --- a/apps/mobile/src/shared/logger.ts +++ b/apps/mobile/src/shared/logger.ts @@ -1,3 +1,4 @@ -import { getLogger } from "@init/observability/logger" +import { buildLogger, LoggerCategory } from "@init/observability/logger" +import { singleton } from "@init/utils/singleton" -export const logger = getLogger() +export const logger = singleton("logger:mobile", () => buildLogger([LoggerCategory.DEFAULT])) diff --git a/packages/email/src/client.ts b/packages/email/src/client.ts index ef57f812..771410c4 100644 --- a/packages/email/src/client.ts +++ b/packages/email/src/client.ts @@ -1,7 +1,7 @@ import type { ReactNode } from "react" import { resend } from "@init/env/presets" import { SendEmailError, BatchSendEmailError } from "@init/error" -import { logger } from "@init/observability/logger" +import { getLogger, LoggerCategory } from "@init/observability/logger" import { type DurationInput, milliseconds } from "@init/utils/duration" import { singleton } from "@init/utils/singleton" import { render } from "@react-email/render" @@ -17,6 +17,8 @@ type EmailSendParams = { export const email = singleton("email", () => new Resend(resend().RESEND_API_KEY)) +const logger = getLogger(LoggerCategory.EMAIL) + export async function sendEmail(body: ReactNode, params: EmailSendParams) { const env = resend() const { emails, subject, sendAt, from = env.EMAIL_FROM } = params diff --git a/packages/observability/src/logger/index.ts b/packages/observability/src/logger/index.ts index c42e2594..e61e7620 100644 --- a/packages/observability/src/logger/index.ts +++ b/packages/observability/src/logger/index.ts @@ -15,19 +15,65 @@ export const LoggerCategory = { CONVEX: ["convex"], DEFAULT: ["default"], DRIZZLE_ORM: ["drizzle-orm"], + EMAIL: ["email"], HONO: ["hono"], INNGEST: ["inngest"], LOGTAPE: ["logtape", "meta"], - SECURITY: ["security"], } as const satisfies Record type LoggerCategoryType = (typeof LoggerCategory)[keyof typeof LoggerCategory] -type LoggerConfigOptions = { +type BuildLoggerOptions = { + async?: boolean isDevelopment?: boolean } -function buildConfig(nonBlocking: boolean, options?: LoggerConfigOptions): Config { +const LOGGER_CONFIGS = [ + { + category: LoggerCategory.LOGTAPE, + lowestLevel: "warning", + sinks: ["meta"], + }, + { + category: LoggerCategory.INNGEST, + lowestLevel: "info", + sinks: ["console"], + }, + { + category: LoggerCategory.CONVEX, + lowestLevel: "info", + sinks: ["console"], + }, + { + category: LoggerCategory.HONO, + lowestLevel: "info", + sinks: ["console"], + }, + { + category: LoggerCategory.DRIZZLE_ORM, + lowestLevel: "debug", + sinks: ["console"], + }, + { + category: LoggerCategory.EMAIL, + lowestLevel: "info", + sinks: ["console"], + }, + { + category: LoggerCategory.DEFAULT, + lowestLevel: "trace", + sinks: ["console"], + }, +] as const satisfies Config["loggers"] + +export function buildLogger( + categories: readonly LoggerCategoryType[], + options?: BuildLoggerOptions +) { + if (categories.length === 0) { + throw new Error("At least one logger category is required") + } + const isDev = options?.isDevelopment ?? isDevelopment const consoleSink = getConsoleSink({ formatter: isDev @@ -40,66 +86,33 @@ function buildConfig(nonBlocking: boolean, options?: LoggerConfigOptions): Confi timestamp: "time", }) : jsonLinesFormatter, - nonBlocking, + nonBlocking: options?.async === true, }) - return { - loggers: [ - { - category: LoggerCategory.LOGTAPE, - lowestLevel: "warning", - sinks: ["meta"], - }, - { - category: LoggerCategory.SECURITY, - lowestLevel: "info", - sinks: ["console"], - }, - { - category: LoggerCategory.INNGEST, - lowestLevel: "info", - sinks: ["console"], - }, - { - category: LoggerCategory.CONVEX, - lowestLevel: "info", - sinks: ["console"], - }, - { - category: LoggerCategory.HONO, - lowestLevel: "info", - sinks: ["console"], - }, - { - category: LoggerCategory.DRIZZLE_ORM, - lowestLevel: "debug", - sinks: ["console"], - }, - { - category: LoggerCategory.DEFAULT, - lowestLevel: "trace", - sinks: ["console"], - }, - ], + const configuredCategories = new Set(categories.map((category) => category.join("/"))) + + const config: Config = { + loggers: LOGGER_CONFIGS.filter((logger) => configuredCategories.has(logger.category.join("/"))), sinks: { console: redactSink(consoleSink), meta: consoleSink, }, } -} -export function configureLogger(options?: LoggerConfigOptions) { - configureSync(buildConfig(false, options)) -} + if (options?.async) { + void configure(config) + } else { + configureSync(config) + } -export async function configureLoggerAsync(options?: LoggerConfigOptions) { - await configure(buildConfig(true, options)) + const defaultCategoryKey = LoggerCategory.DEFAULT.join("/") + const defaultCategory = categories.find((category) => category.join("/") === defaultCategoryKey) + + return getLogger(defaultCategory ?? categories[0]) } export function getLogger(category: LoggerCategoryType = LoggerCategory.DEFAULT) { return getLogtapeLogger(category) } -export const logger = getLogger() - export type Logger = LogtapeLogger diff --git a/packages/workflows/src/client.ts b/packages/workflows/src/client.ts index 426e6dd4..f107633e 100644 --- a/packages/workflows/src/client.ts +++ b/packages/workflows/src/client.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@init/observability/logger" +import { getLogger, LoggerCategory } from "@init/observability/logger" import { singleton } from "@init/utils/singleton" import { dependencyInjectionMiddleware, Inngest } from "inngest" import { extendedTracesMiddleware } from "inngest/experimental" @@ -14,7 +14,7 @@ export const inngest = singleton( () => new Inngest({ id: "init", - logger: getLogger(["inngest"]), + logger: getLogger(LoggerCategory.INNGEST), middleware: [ dependencyInjectionMiddleware({ // Add any dependencies here