Skip to content

feat: introduce @llmgateway/logger package to replace logs - #684

Merged
steebchen merged 14 commits into
mainfrom
fix/remove-debug-logs
Sep 1, 2025
Merged

steebchen merged 14 commits into
mainfrom
fix/remove-debug-logs

Conversation

@steebchen

@steebchen steebchen commented Aug 30, 2025

Copy link
Copy Markdown
Member

Summary

This PR introduces a new @llmgateway/logger package that provides a comprehensive structured logging solution using the pino logger. It replaces debug console.log, console.error, console.warn, and console.info statements throughout the codebase with this proper logging infrastructure.

Key Changes Made:

  • Created new @llmgateway/logger package with pino-based structured logging
  • Replaced console statements (log, error, warn, info) in critical files and packages:
    • packages/models/src/provider-api.ts (including line 915 validation log)
    • packages/auth/src/auth.ts (email verification and Brevo contact creation)
    • packages/db/src/migrate.ts and db.ts (database operations)
    • apps/gateway/src/serve.ts and worker.ts (server lifecycle, shutdown, worker logs)
    • apps/gateway/src/index.ts (HTTP exceptions and health checks)
    • apps/api/src/index.ts, lib/beacon.ts, routes/beacon.ts, routes/chat.ts, routes/subscriptions.ts, scripts/generate-openapi.ts, serve.ts, stripe.ts (various API and webhook logs)
    • apps/gateway/src/chat/chat.ts, lib/cache.ts, lib/costs.ts, lib/redis.ts, models/models.ts, scripts/generate-openapi.ts (gateway logs and cache)
    • apps/ui/src/lib/server-api.ts (server API error logging)
  • Updated package.json files in multiple packages/apps to add @llmgateway/logger dependency
  • Updated ESLint config to enforce no console usage except in test and UI files

Logging Features:

  • Environment-aware configuration: Debug/info in development, warn/error in production
  • Pretty printing for development with colorized output
  • JSON format for production logging
  • Specialized logging methods for:
    • HTTP request/response tracking
    • Model request/response logging
    • Database operations
    • Cache operations
    • Worker operations
    • Payment/billing events
  • Error handling improvements with proper Error type checking
  • Comprehensive test coverage with unit tests

Technical Implementation:

  • Uses Pino logger for high-performance structured logging
  • Configurable log levels via environment variables
  • Supports child loggers with additional context
  • Backward compatible - existing functionality unchanged
  • TypeScript-first with full type definitions

Performance Impact:

  • Improved production performance by removing console.log statements
  • Better observability with structured logs for monitoring/debugging
  • Professional logging practices following industry standards

Test Plan

  • Unit tests for logger functionality pass
  • Logger integration smoke test successful
  • Code formatting and basic validation completed
  • Full e2e test suite (requires database setup)
  • Production deployment validation

🤖 Generated with Claude Code

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/d9e82a8a-9f9e-4f59-b7bd-75723f3ee45b

Summary by CodeRabbit

  • New Features

    • Added a shared, structured logger package with env-aware pretty/JSON output and a default logger.
  • Refactor

    • Replaced console.* across services (API, Gateway, Auth, DB, Models, workers, scripts) with the centralized logger.
    • UI tweaks: playground now re-syncs URL when selected model changes; upgrade dialog no longer exposes a post-success callback.
  • Chores

    • Added logger deps and build/config files; tightened ESLint no-console with test overrides.
  • Tests

    • Added unit tests for logger API and child loggers.

- Create new @llmgateway/logger package with pino-based structured logging
- Add configurable log levels for development vs production environments
- Replace console.log/warn/error statements in critical files:
  - packages/models/src/provider-api.ts (including line 915 validation log)
  - packages/auth/src/auth.ts (email verification and Brevo contact creation)
  - packages/db/src/migrate.ts and db.ts (database operations)
  - apps/gateway/src/serve.ts (server lifecycle and shutdown)
  - apps/gateway/src/index.ts (HTTP exceptions and health checks)
- Add specialized logging methods for common use cases
- Configure pretty printing for development and JSON format for production
- Add comprehensive test coverage for logging functionality
- Improve error handling with proper Error type checking

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 30, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new @llmgateway/logger package (pino-based) and replaces ad-hoc console.* logging with the centralized logger across many apps/packages, updates workspace dependencies, adds build/test configs for the logger, and tightens ESLint no-console rules with test overrides.

Changes

Cohort / File(s) Summary
New logger package
packages/logger/package.json, packages/logger/src/index.ts, packages/logger/src/logger.spec.ts, packages/logger/tsconfig.json, packages/logger/tsup.config.ts
Introduces @llmgateway/logger: a pino wrapper (LLMGatewayLogger), default logger, createLogger, types, build/test config and unit tests.
Workspace manifests updated
apps/api/package.json, apps/gateway/package.json, packages/auth/package.json, packages/db/package.json, packages/models/package.json
Adds @llmgateway/logger as a workspace dependency to multiple packages/apps.
Gateway logging adoption
apps/gateway/src/index.ts, apps/gateway/src/serve.ts, apps/gateway/src/chat/chat.ts, apps/gateway/src/lib/cache.ts, apps/gateway/src/lib/costs.ts, apps/gateway/src/lib/redis.ts, apps/gateway/src/models/models.ts, apps/gateway/src/scripts/generate-openapi.ts, apps/gateway/src/worker.ts, apps/gateway/package.json
Replaces console.* with structured logger.* across request handling, health checks, startup/shutdown, chat flow, cache/redis/costs, worker lifecycle, model endpoints, and openapi script; preserves control flow and error handling.
API logging adoption
apps/api/src/index.ts, apps/api/src/lib/beacon.ts, apps/api/src/routes/beacon.ts, apps/api/src/routes/chat.ts, apps/api/src/routes/subscriptions.ts, apps/api/src/scripts/generate-openapi.ts, apps/api/src/serve.ts, apps/api/src/stripe.ts, apps/api/package.json
Replaces console.* with logger.* across API error handling, beacon, chat, subscriptions, Stripe webhook, OpenAPI generation, serve/migrations/startup/shutdown; errors normalized before logging.
Auth logging adoption
packages/auth/src/auth.ts, packages/auth/package.json
Replaces console logs with logger.* in auth flows, Brevo/SMTP handling, and email verification flows.
DB logging and migrate tweak
packages/db/src/db.ts, packages/db/src/migrate.ts, packages/db/package.json
Replaces console.* with logger.* in DB close/migration flows; migration now initializes drizzle with explicit connection: databaseUrl.
Models logging adoption
packages/models/src/provider-api.ts, packages/models/package.json
Replaces console warnings/errors with structured logger.* across image processing, provider validation and transforms.
UI small changes
apps/ui/src/lib/server-api.ts, apps/ui/src/app/playground/playground-client.tsx, apps/ui/src/components/landing/pricing-plans.tsx, apps/ui/src/components/settings/caching-settings.tsx, apps/ui/src/components/settings/project-mode-settings.tsx, apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx, apps/ui/src/hooks/useUser.ts
Minor UI changes: comment added, effect deps updated (selectedModel, pathname), removal of onSuccess from UpgradeToProDialog and related callers, and mutation onSuccess callbacks changed to no-arg handlers; behavior preserved.
ESLint rule change
eslint.config.mjs
Changes global no-console from "off" to "error" and adds overrides to disable no-console for tests and specific directories.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Component as App Component
  participant Logger as @llmgateway/logger
  participant Pino as pino
  participant Out as stdout/transport

  Component->>Logger: logger.info/warn/error(message, context|Error)
  activate Logger
  Logger->>Pino: pino[level]({...context, err?}, message)
  activate Pino
  Pino-->>Out: formatted log (JSON or pretty)
  deactivate Pino
  deactivate Logger

  rect rgb(245,250,255)
  note over Component,Logger: Error normalization for non-Error values
  Component->>Logger: logger.error("Unhandled error", error instanceof Error ? error : new Error(String(error)))
  end
Loading
sequenceDiagram
  autonumber
  participant CLI as migrate.ts
  participant Logger as @llmgateway/logger
  participant DB as Drizzle Migration

  CLI->>Logger: info("Starting database migrations")
  CLI->>DB: migrate({ connection: databaseUrl })
  alt success
    DB-->>CLI: done
    CLI->>Logger: info("Database migrations completed")
  else failure
    DB-->>CLI: throw Error
    CLI->>Logger: error("Database migration failed", Error)
    CLI-->>CLI: rethrow
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • smakosh
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/remove-debug-logs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Removed the unused consoleSpy variable from the logger.spec.ts test file to clean up the test setup and avoid unnecessary variable declaration.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@steebchen steebchen changed the title feat: replace console logging with structured pino logger feat: introduce @llmgateway/logger package and replace console logs with structured pino logger Aug 30, 2025
- Remove unnecessary specific logging methods (httpRequest, modelRequest, etc.)
- Keep only core logging methods (trace, debug, info, warn, error, fatal)
- Update provider-api.ts to use generic debug method instead of validation method
- Simplify test cases to match new logger interface
- Maintain same functionality with cleaner, more flexible API

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/package.json (1)

20-33: Replace all console. calls with the centralized @llmgateway/logger*
console.log/info/warn/error calls remain throughout apps/api/src (e.g. stripe.ts, serve.ts, routes, lib), and there are no imports of @llmgateway/logger. Import and use the shared logger in place of console.* to complete the structured-logging migration.

packages/models/src/provider-api.ts (1)

31-33: btoa usage will break in Node environments

btoa isn’t available in Node. Use Buffer when present; fall back to btoa in browsers.

-		const isBase64 = url.includes(";base64,");
-		const base64Data = isBase64 ? data : btoa(data);
+		const isBase64 = url.includes(";base64,");
+		const base64Data = isBase64
+			? data
+			: (typeof btoa === "function"
+				? btoa(data)
+				: Buffer.from(data, "utf-8").toString("base64"));
-		const uint8Array = new Uint8Array(arrayBuffer);
-		const binaryString = Array.from(uint8Array, (byte) =>
-			String.fromCharCode(byte),
-		).join("");
-		const base64 = btoa(binaryString);
+		const uint8Array = new Uint8Array(arrayBuffer);
+		// Prefer Buffer in Node; fall back to btoa in the browser
+		const base64 =
+			typeof Buffer !== "undefined"
+				? Buffer.from(uint8Array).toString("base64")
+				: btoa(Array.from(uint8Array, (b) => String.fromCharCode(b)).join(""));

Also applies to: 93-99

🧹 Nitpick comments (19)
packages/logger/tsconfig.json (1)

3-6: Consider enabling declaration-only builds and project references for faster, cleaner outputs.

If you run tsc alongside tsup, emitting only d.ts and marking the project composite improves incremental builds and avoids duplicate JS outputs.

   "compilerOptions": {
     "rootDir": "./src",
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "declaration": true,
+    "emitDeclarationOnly": true,
+    "composite": true
   },
packages/logger/tsup.config.ts (1)

3-10: Add platform/target to tsup.config.ts

The external array is correct—both pino (^9.4.0) and pino-pretty (^11.3.0) are listed as dependencies. For predictable, Node-only bundles, add:

 export default defineConfig({
   entry: ["src/index.ts"],
   format: ["esm", "cjs"],
   dts: true,
   sourcemap: true,
   clean: true,
   external: ["pino", "pino-pretty"],
+  platform: "node",
+  target: "node18",
 });
apps/gateway/src/serve.ts (1)

81-84: Avoid logging Promise objects in unhandledRejection.
Serializing a Promise can be noisy or circular; log the reason only.

Apply:

-process.on("unhandledRejection", (reason, promise) => {
-	logger.fatal("Unhandled rejection", { promise, reason });
+process.on("unhandledRejection", (reason) => {
+	logger.fatal(
+		"Unhandled rejection",
+		reason instanceof Error ? reason : { reason: String(reason) },
+	);
 	process.exit(1);
-});
+});
packages/db/src/db.ts (1)

12-12: Optionally handle connect errors explicitly.
Top-level connect can fail silently; log and surface it early.

-void client.connect();
+await client.connect().catch((err) => {
+	logger.error(
+		"Error connecting to database",
+		err instanceof Error ? err : new Error(String(err)),
+	);
+	throw err;
+});
packages/logger/package.json (1)

19-22: Consider making pino-pretty dev-only with lazy load.
If pretty printing is used only in development, move pino-pretty to devDependencies and lazy-import in dev to reduce prod footprint. Optional.

packages/db/src/migrate.ts (1)

1-1: Prefer a child logger for scoped context

Using a child logger adds stable context (component) to every line and simplifies downstream filtering.

 import { logger } from "@llmgateway/logger";
+const log = logger.child({ component: "db:migrate" });

-	logger.info("Starting database migrations");
+	log.info("Starting database migrations");

-		logger.info("Database migrations completed successfully");
+		log.info("Database migrations completed successfully");

-		logger.error(
+		log.error(
 			"Database migration failed",
 			error instanceof Error ? error : new Error(String(error)),
 		);

Also applies to: 13-14, 25-30

packages/logger/src/logger.spec.ts (1)

1-1: Silence logger output in tests (console mocks won’t affect pino)

Pino writes to stdout/stderr, so mocking console.* won’t stop noise. Force a silent level for the test run.

-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { describe, it, expect, vi, beforeEach, beforeAll, afterAll } from "vitest";

 describe("LLMGateway Logger", () => {
+	beforeAll(() => {
+		// Ensure no logger output during tests
+		process.env.LOG_LEVEL = "silent";
+	});
+	afterAll(() => {
+		delete process.env.LOG_LEVEL;
+	});
 	beforeEach(() => {
 		// Mock console methods to avoid actual output during tests
 		vi.spyOn(console, "log").mockImplementation(() => {});
 		vi.spyOn(console, "warn").mockImplementation(() => {});
 		vi.spyOn(console, "error").mockImplementation(() => {});
 	});

Also applies to: 6-11

packages/auth/src/auth.ts (2)

57-58: Avoid logging PII at info level

Email addresses are PII. Consider logging domain only or moving this to debug.

-		logger.info("Successfully created Brevo contact", { email });
+		// Avoid PII leakage in logs
+		const emailDomain = email.includes("@") ? email.split("@")[1] : "unknown";
+		logger.debug("Successfully created Brevo contact", { emailDomain });

31-50: Add request timeout to Brevo API call

Prevent hangs on network issues.

-		const response = await fetch("https://api.brevo.com/v3/contacts", {
+		const response = await fetch("https://api.brevo.com/v3/contacts", {
 			method: "POST",
 			headers: {
 				"Content-Type": "application/json",
 				"api-key": brevoApiKey,
 			},
+			signal: AbortSignal.timeout(10_000),
 			body: JSON.stringify({
apps/gateway/src/index.ts (3)

65-68: Include request context in HTTP exception logs

Method and path make incidents traceable without relying on upstream logs.

-			logger.error("HTTP 500 exception", error);
+			logger.error("HTTP 500 exception", {
+				err: error,
+				status,
+				method: c.req.method,
+				path: c.req.path,
+			});
 		} else {
-			logger.warn("HTTP client error", { status, message: error.message });
+			logger.warn("HTTP client error", {
+				status,
+				message: error.message,
+				method: c.req.method,
+				path: c.req.path,
+			});

82-85: Add request context to unhandled error logs

Keeps logs consistent and actionable.

-	logger.error(
-		"Unhandled error",
-		error instanceof Error ? error : new Error(String(error)),
-	);
+	logger.error("Unhandled error", {
+		err: error instanceof Error ? error : new Error(String(error)),
+		method: c.req.method,
+		path: c.req.path,
+	});

158-161: Add request context to healthcheck failure logs

Helps correlate failures with callers.

-			logger.error(
-				"Redis healthcheck failed",
-				error instanceof Error ? error : new Error(String(error)),
-			);
+			logger.error("Redis healthcheck failed", {
+				err: error instanceof Error ? error : new Error(String(error)),
+				method: c.req.method,
+				path: c.req.path,
+			});
-			logger.error(
-				"Database healthcheck failed",
-				error instanceof Error ? error : new Error(String(error)),
-			);
+			logger.error("Database healthcheck failed", {
+				err: error instanceof Error ? error : new Error(String(error)),
+				method: c.req.method,
+				path: c.req.path,
+			});

Also applies to: 174-177

packages/models/src/provider-api.ts (1)

954-958: Add timeout to validation fetch

Prevents hung key validations on provider slowness.

-		const response = await fetch(endpoint, {
+		const response = await fetch(endpoint, {
 			method: "POST",
 			headers,
+			signal: AbortSignal.timeout(10_000),
 			body: JSON.stringify(payload),
 		});
packages/logger/src/index.ts (6)

1-1: Honor LOG_LEVEL and include 'silent' for tests; tighten LogLevel typing.

Read LOG_LEVEL when set and prefer pino’s LevelWithSilent (adds "silent"). This makes test runs noise-free and gives ops an override.

-import pino, { type Logger } from "pino";
+import pino, { type Logger, type Bindings, type LevelWithSilent } from "pino";

-export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
+export type LogLevel = LevelWithSilent;

-  private getDefaultLevel(): LogLevel {
-    const nodeEnv = process.env.NODE_ENV;
-    if (nodeEnv === "test") {
-      return "warn";
-    }
-    if (nodeEnv === "production") {
-      return "info";
-    }
-    return "debug";
-  }
+  private getDefaultLevel(): LogLevel {
+    const env = process.env.LOG_LEVEL as LogLevel | undefined;
+    if (env) return env;
+    const nodeEnv = process.env.NODE_ENV;
+    if (nodeEnv === "test") return "silent";
+    if (nodeEnv === "production") return "info";
+    return "debug";
+  }

Also applies to: 3-3, 37-46


48-52: Normalize LOG_PRETTY parsing.

Handle case-insensitive values to avoid surprises from "True"/"FALSE".

-    const nodeEnv = process.env.NODE_ENV;
-    const forcePretty = process.env.LOG_PRETTY === "true";
-    const forceJson = process.env.LOG_PRETTY === "false";
+    const nodeEnv = process.env.NODE_ENV;
+    const prettyEnv = process.env.LOG_PRETTY?.toLowerCase();
+    const forcePretty = prettyEnv === "true";
+    const forceJson = prettyEnv === "false";

64-79: Don’t pass undefined as the first arg; tighten extra typing.

Avoids odd frames and improves TS safety.

-  trace(message: string, extra?: object): void {
-    this.logger.trace(extra, message);
-  }
+  trace(message: string, extra?: Record<string, unknown>): void {
+    extra ? this.logger.trace(extra, message) : this.logger.trace(message);
+  }

-  debug(message: string, extra?: object): void {
-    this.logger.debug(extra, message);
-  }
+  debug(message: string, extra?: Record<string, unknown>): void {
+    extra ? this.logger.debug(extra, message) : this.logger.debug(message);
+  }

-  info(message: string, extra?: object): void {
-    this.logger.info(extra, message);
-  }
+  info(message: string, extra?: Record<string, unknown>): void {
+    extra ? this.logger.info(extra, message) : this.logger.info(message);
+  }

-  warn(message: string, extra?: object): void {
-    this.logger.warn(extra, message);
-  }
+  warn(message: string, extra?: Record<string, unknown>): void {
+    extra ? this.logger.warn(extra, message) : this.logger.warn(message);
+  }

81-95: Error/fatal: accept unknown and handle undefined cleanly.

Prevents logging “undefined” frames and keeps stacks intact.

-  error(message: string, error?: Error | object): void {
+  error(message: string, error?: unknown): void {
     if (error instanceof Error) {
       this.logger.error({ err: error }, message);
-    } else {
-      this.logger.error(error, message);
+    } else if (error && typeof error === "object") {
+      this.logger.error(error as Record<string, unknown>, message);
+    } else {
+      this.logger.error(message);
     }
   }

-  fatal(message: string, error?: Error | object): void {
+  fatal(message: string, error?: unknown): void {
     if (error instanceof Error) {
       this.logger.fatal({ err: error }, message);
-    } else {
-      this.logger.fatal(error, message);
+    } else if (error && typeof error === "object") {
+      this.logger.fatal(error as Record<string, unknown>, message);
+    } else {
+      this.logger.fatal(message);
     }
   }

97-103: Type child bindings with pino.Bindings.

Improves discoverability and correctness when adding fields.

-  child(bindings: object): LLMGatewayLogger {
+  child(bindings: Bindings): LLMGatewayLogger {
     const childPino = this.logger.child(bindings);
     const childLogger = Object.create(LLMGatewayLogger.prototype);
     childLogger.logger = childPino;
     return childLogger;
   }

110-112: Make createLogger options optional.

Saves call sites from passing empty objects.

-export function createLogger(options: LoggerOptions): LLMGatewayLogger {
+export function createLogger(options: LoggerOptions = {}): LLMGatewayLogger {
   return new LLMGatewayLogger(options);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between abf8439 and 51e7857.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/api/package.json (1 hunks)
  • apps/gateway/package.json (1 hunks)
  • apps/gateway/src/index.ts (5 hunks)
  • apps/gateway/src/serve.ts (3 hunks)
  • packages/auth/package.json (1 hunks)
  • packages/auth/src/auth.ts (5 hunks)
  • packages/db/package.json (1 hunks)
  • packages/db/src/db.ts (2 hunks)
  • packages/db/src/migrate.ts (3 hunks)
  • packages/logger/package.json (1 hunks)
  • packages/logger/src/index.ts (1 hunks)
  • packages/logger/src/logger.spec.ts (1 hunks)
  • packages/logger/tsconfig.json (1 hunks)
  • packages/logger/tsup.config.ts (1 hunks)
  • packages/models/package.json (1 hunks)
  • packages/models/src/provider-api.ts (7 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • packages/logger/tsup.config.ts
  • packages/logger/src/logger.spec.ts
  • apps/gateway/src/index.ts
  • packages/db/src/db.ts
  • packages/models/src/provider-api.ts
  • packages/db/src/migrate.ts
  • packages/auth/src/auth.ts
  • apps/gateway/src/serve.ts
  • packages/logger/src/index.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • packages/logger/tsup.config.ts
  • packages/logger/src/logger.spec.ts
  • apps/gateway/src/index.ts
  • packages/db/src/db.ts
  • packages/models/src/provider-api.ts
  • packages/db/src/migrate.ts
  • packages/auth/src/auth.ts
  • apps/gateway/src/serve.ts
  • packages/logger/src/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • packages/logger/tsup.config.ts
  • packages/logger/src/logger.spec.ts
  • apps/gateway/src/index.ts
  • packages/db/src/db.ts
  • packages/models/src/provider-api.ts
  • packages/db/src/migrate.ts
  • packages/auth/src/auth.ts
  • apps/gateway/src/serve.ts
  • packages/logger/src/index.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Name unit test files with the .spec.ts suffix

Files:

  • packages/logger/src/logger.spec.ts
apps/{api,gateway}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{api,gateway}/**/*.{ts,tsx}: Use Zod schemas for validation in backend services
Use Hono as the web framework for backend routes and middleware

apps/{api,gateway}/**/*.{ts,tsx}: Use Drizzle with the latest object syntax for database operations
For read queries, use db().query.

.findMany() or db().query.
.findFirst()

Files:

  • apps/gateway/src/index.ts
  • apps/gateway/src/serve.ts
apps/gateway/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In apps/gateway (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Files:

  • apps/gateway/src/index.ts
  • apps/gateway/src/serve.ts
🧠 Learnings (4)
📚 Learning: 2025-08-29T15:31:07.044Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.044Z
Learning: Applies to apps/gateway/**/*.{ts,tsx} : In apps/gateway (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Applied to files:

  • apps/gateway/package.json
  • apps/api/package.json
📚 Learning: 2025-08-29T02:12:34.132Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T02:12:34.132Z
Learning: Applies to apps/{api,gateway}/**/*.{ts,tsx} : Use Zod schemas for validation in backend services

Applied to files:

  • apps/api/package.json
📚 Learning: 2025-08-29T15:31:07.044Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.044Z
Learning: Applies to apps/{api,gateway}/**/*.{ts,tsx} : Use Drizzle with the latest object syntax for database operations

Applied to files:

  • packages/db/src/db.ts
  • packages/db/src/migrate.ts
  • packages/auth/src/auth.ts
📚 Learning: 2025-07-23T19:33:40.639Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-23T19:33:40.639Z
Learning: Applies to **/*.{js,ts} : Use drizzle with the latest object syntax for database operations

Applied to files:

  • packages/db/src/migrate.ts
🧬 Code graph analysis (7)
packages/logger/src/logger.spec.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • createLogger (110-112)
apps/gateway/src/index.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
packages/db/src/db.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
packages/models/src/provider-api.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
packages/db/src/migrate.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
packages/auth/src/auth.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/serve.ts (3)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/worker.ts (1)
  • stopWorker (465-490)
packages/db/src/db.ts (1)
  • closeDatabase (20-31)
🪛 GitHub Actions: ci
packages/logger/package.json

[error] 1-1: Lockfile specifiers don't match package.json: 1 dependency added (vitest@^3.2.3). Run 'pnpm install' to synchronize the lockfile.

🔇 Additional comments (13)
packages/auth/package.json (1)

19-24: Approve code changes — logger dependency validated

No remaining console.* calls in packages/auth/src; @llmgateway/logger is imported in packages/auth/src/auth.ts (line 2).

packages/models/package.json (1)

22-24: LGTM: models now depends on the centralized logger.
This aligns with the PR goal and keeps workspace versions consistent.

apps/gateway/src/serve.ts (2)

41-63: Nice structured, stepwise shutdown logs.
Readable, ordered, and with context fields where applicable. This will help postmortems.


77-78: No action needed: logger.fatal signature mirrors logger.error and usage is correct.

packages/db/package.json (1)

28-28: LGTM: db package depends on @llmgateway/logger.
Keeps observability consistent across data layer.

packages/db/src/db.ts (1)

23-28: Good: standardized DB close logging (success and error paths).
The error branch preserves the original error and adds context; rethrowing is correct.

packages/logger/package.json (1)

7-13: Verify exports vs build outputs (ESM/CJS).
Both "import" and "require" point to index.js. Ensure tsup emits compatible formats or export separate files to avoid module interop issues.

Suggested:

 "exports": {
   ".": {
-    "types": "./dist/index.d.ts",
-    "import": "./dist/index.js",
-    "require": "./dist/index.js"
+    "types": "./dist/index.d.ts",
+    "import": "./dist/index.mjs",
+    "require": "./dist/index.cjs"
   }
 },
 "scripts": {
-  "build": "tsup",
-  "dev": "tsup --watch",
+  "build": "tsup --format esm,cjs --dts",
+  "dev": "tsup --watch --format esm,cjs --dts",
   "test": "vitest run"
 }
packages/db/src/migrate.ts (1)

13-13: Structured logs look good

Replacements with the centralized logger are consistent and keep messages actionable.

Also applies to: 25-25

packages/auth/src/auth.ts (1)

59-62: Good error normalization with structured logging

Passing a real Error ensures consistent logger formatting and stack traces.

apps/gateway/src/index.ts (1)

60-94: Overall error handling/logging upgrade looks solid

Centralized logger usage is consistent and respects severity levels.

packages/models/src/provider-api.ts (2)

106-109: Good use of structured error payload

Passing a plain object with err and url preserves both stack and context.


908-911: Validation model debug log is helpful

The context keys are clear and low-noise at debug level.

packages/logger/src/index.ts (1)

28-31: translateTime format is valid

Dateformat (used by pino-pretty) supports HH (24-hour), MM (minutes), ss (seconds) and Z (timezone), so "HH:MM:ss Z" is correct and needs no change.

Comment thread apps/gateway/package.json
Comment on lines 21 to 24
"@llmgateway/db": "workspace:*",
"@llmgateway/logger": "workspace:*",
"@llmgateway/models": "workspace:*",
"gpt-tokenizer": "3.0.1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Adopting centralized logger in gateway — LGTM.

Dependency aligns the app with the new logging stack.

Quick check that console.* calls are gone and logger is used:


🏁 Script executed:

#!/bin/bash
rg -n --type=ts --no-ignore -S $'\\bconsole\\.(log|info|warn|error|debug)\\(' apps/gateway/src || true
rg -n --type=ts --no-ignore -S 'from\\s+["\\\']@llmgateway/logger["\\\']' apps/gateway/src

Length of output: 7802


Remove all remaining console. calls and use @llmgateway/logger*
Search revealed console.log/warn/error calls still present across apps/gateway/src; import { logger } from @llmgateway/logger and replace all console.* usages with logger.[level](…).

🤖 Prompt for AI Agents
In apps/gateway/package.json around lines 21 to 24, the review asks to remove
console.* calls across apps/gateway/src and use @llmgateway/logger instead;
update each file under apps/gateway/src that currently uses console.log /
console.warn / console.error / console.info to import { logger } from
'@llmgateway/logger' at the top, then replace console.log(...) ->
logger.info(...), console.warn(...) -> logger.warn(...), console.error(...) ->
logger.error(...), and console.info(...) -> logger.info(...); ensure there are
no remaining console.* usages, adjust any message formatting to accept
structured args if required, and run tests/lint to confirm imports and usage are
correct.

Comment thread apps/gateway/src/serve.ts
Comment on lines +11 to 12
logger.info("Server starting", { port });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Quick follow-up: ensure all console. are gone repo-wide.*
worker.ts still shows console usage in the snippet. Run this to find stragglers and I can auto-fix:


🏁 Script executed:

#!/bin/bash
# Find remaining console calls
rg -nP '\bconsole\.(log|warn|error|info|debug)\s*\(' -g '!**/node_modules/**'

Length of output: 17156


Eliminate all remaining console. calls repository-wide.*
Run:

rg -nP '\bconsole\.(log|warn|error|info|debug)\s*\(' -g '!**/node_modules/**'

and replace every console.log, console.warn, console.error, and console.info with the centralized logger methods (or remove if purely for local debugging). This cleanup spans dozens of files (e.g. apps/gateway/src/worker.ts, apps/gateway/src/chat, apps/ui/src/**, etc.) and is required before merging.

🤖 Prompt for AI Agents
In apps/gateway/src/serve.ts around lines 11 to 12 there is a plain console.*
usage remaining (e.g., console.log/info) that must be removed; replace any
console calls with the centralized logger methods
(logger.info/logger.warn/logger.error/logger.debug) or remove them if they were
only for local debugging, ensuring you pass the same message and structured
metadata (like { port }) to logger and run the repository-wide search/replace
command rg -nP '\bconsole\.(log|warn|error|info|debug)\s*\(' -g
'!**/node_modules/**' to find and update all other occurrences across the
codebase before merging.

Comment thread apps/gateway/src/serve.ts
Comment on lines +77 to 84
logger.fatal("Uncaught exception", error);
process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
logger.fatal("Unhandled rejection", { promise, reason });
process.exit(1);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure logs flush before process.exit on fatal paths.
Without draining, the last logs can be dropped. Consider exposing a flush/final handler in @llmgateway/logger (using pino.final) and calling it here before exit.

🤖 Prompt for AI Agents
In apps/gateway/src/serve.ts around lines 77 to 84, the fatal-path handlers call
process.exit immediately which can drop the last logs; update these handlers to
invoke the logger's flush/final routine (exposed from @llmgateway/logger, e.g. a
pino.final-based function) and wait for it to complete before calling
process.exit. Specifically, import and call the exposed final/flush handler with
the error/reason and a callback (or await a returned promise) to ensure the log
is written, then call process.exit with the appropriate code inside that
completion callback or after awaiting the flush.

Comment thread packages/auth/src/auth.ts
Comment on lines 111 to 116
if (!smtpHost || !smtpUser || !smtpPass) {
console.log(`email verification link: ${url}`);
console.error(
logger.info("Email verification link generated", { url });
logger.error(
"SMTP configuration is not set. Email verification will not work.",
);
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not log verification tokens; downgrade severity for config issue

This logs a full verification URL (includes token). That’s sensitive. Also, missing SMTP is a configuration warning, not an error.

-			if (!smtpHost || !smtpUser || !smtpPass) {
-				logger.info("Email verification link generated", { url });
-				logger.error(
-					"SMTP configuration is not set. Email verification will not work.",
-				);
+			if (!smtpHost || !smtpUser || !smtpPass) {
+				const safeUrl = url.replace(/([?&]token=)[^&]+/, "$1[redacted]");
+				logger.debug("Email verification link generated", { url: safeUrl });
+				logger.warn(
+					"SMTP configuration is not set. Email verification will not work.",
+				);
 				return;
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!smtpHost || !smtpUser || !smtpPass) {
console.log(`email verification link: ${url}`);
console.error(
logger.info("Email verification link generated", { url });
logger.error(
"SMTP configuration is not set. Email verification will not work.",
);
return;
if (!smtpHost || !smtpUser || !smtpPass) {
const safeUrl = url.replace(/([?&]token=)[^&]+/, "$1[redacted]");
logger.debug("Email verification link generated", { url: safeUrl });
logger.warn(
"SMTP configuration is not set. Email verification will not work.",
);
return;
}
🤖 Prompt for AI Agents
In packages/auth/src/auth.ts around lines 111 to 116, the code currently logs
the full verification URL (which contains a sensitive token) and emits an error
when SMTP config is missing; remove any logging of the verification URL or token
and replace it with a non-sensitive, generic message (e.g., "Email verification
link generated" without URL) or omit entirely, and change the logger.error call
to logger.warn (or equivalent warning level) to reflect a configuration warning
rather than an error; ensure no sensitive token is output and that the log
message gives clear non-sensitive context.

Comment on lines +1 to +29
{
"name": "@llmgateway/logger",
"version": "0.1.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run"
},
"dependencies": {
"pino": "^9.4.0",
"pino-pretty": "^11.3.0"
},
"devDependencies": {
"@types/node": "^22.9.0",
"tsup": "8.4.0",
"typescript": "5.8.3",
"vitest": "^3.2.3"
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Pipeline is red: lockfile out of sync.
GitHub Actions error: “Lockfile specifiers don't match package.json: 1 dependency added (vitest@^3.2.3).” Run pnpm install at the workspace root and commit the updated lockfile.

#!/bin/bash
# From repo root
pnpm install -w
git add pnpm-lock.yaml
git commit -m "chore: sync lockfile after logger package changes"
🧰 Tools
🪛 GitHub Actions: ci

[error] 1-1: Lockfile specifiers don't match package.json: 1 dependency added (vitest@^3.2.3). Run 'pnpm install' to synchronize the lockfile.

🤖 Prompt for AI Agents
In packages/logger/package.json lines 1-29 the package.json was changed (vitest
added) but the workspace lockfile is out of sync; run pnpm install -w from the
repository root to update pnpm-lock.yaml, stage and commit the updated
pnpm-lock.yaml (e.g., git add pnpm-lock.yaml && git commit -m "chore: sync
lockfile after logger package changes"), then push the commit so CI picks up the
updated lockfile.

Comment thread packages/logger/package.json Outdated
Comment on lines +15 to +17
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm .d.ts are emitted.
"types" points to dist/index.d.ts; ensure tsup (or tsup.config.ts) generates declarations.


🏁 Script executed:

#!/bin/bash
# After build, verify types exist
pnpm -w --filter @llmgateway/logger build
test -f packages/logger/dist/index.d.ts && echo "types OK" || (echo "missing index.d.ts"; exit 1)

Length of output: 542


🏁 Script executed:

#!/bin/bash
pnpm install
pnpm -w --filter @llmgateway/logger run build
if test -f packages/logger/dist/index.d.ts; then
  echo "types OK"
else
  echo "missing index.d.ts"; exit 1
fi

Length of output: 1068


Install tsup and configure declaration output Add tsup to devDependencies in packages/logger/package.json and enable declaration file generation (e.g. CLI flag --dts or tsup.config.ts dts: true), update the lockfile (pnpm install --no-frozen-lockfile), then verify pnpm -w --filter @llmgateway/logger build produces dist/index.d.ts.

🤖 Prompt for AI Agents
In packages/logger/package.json around lines 15 to 17, the package scripts
reference tsup but tsup is not added to devDependencies and declaration (.d.ts)
files are not enabled; add "tsup" to devDependencies, enable declaration output
by adding the CLI flag --dts to the build script (or set dts: true in
tsup.config.ts), run pnpm install --no-frozen-lockfile to update the lockfile,
and verify by running pnpm -w --filter @llmgateway/logger build that
dist/index.d.ts is generated.

Comment on lines +21 to +34
this.logger = pino({
name,
level,
...(prettyPrint && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss Z",
ignore: "pid,hostname",
},
},
}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add default redaction to avoid leaking secrets.

Without redaction, headers/credentials can hit logs (especially in JSON mode). Add safe defaults and allow extension via LOG_REDACT.

     this.logger = pino({
       name,
       level,
+      // Redact common secret fields; extend via LOG_REDACT="a,b,c"
+      redact: {
+        paths:
+          process.env.LOG_REDACT?.split(",").map((s) => s.trim()).filter(Boolean) ??
+          [
+            "req.headers.authorization",
+            "req.headers.cookie",
+            "headers.authorization",
+            "headers.cookie",
+            "password",
+            "*.password",
+            "token",
+            "*.token",
+            "apiKey",
+            "*.apiKey",
+            "secret",
+            "*.secret",
+          ],
+        censor: "[REDACTED]",
+      },
       ...(prettyPrint && {
         transport: {
           target: "pino-pretty",
           options:
           {
             colorize: true,
-            translateTime: "HH:MM:ss Z",
+            translateTime: "SYS:standard",
             ignore: "pid,hostname",
           },
         },
       }),
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.logger = pino({
name,
level,
...(prettyPrint && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss Z",
ignore: "pid,hostname",
},
},
}),
});
this.logger = pino({
name,
level,
// Redact common secret fields; extend via LOG_REDACT="a,b,c"
redact: {
paths:
process.env.LOG_REDACT?.split(",").map((s) => s.trim()).filter(Boolean) ??
[
"req.headers.authorization",
"req.headers.cookie",
"headers.authorization",
"headers.cookie",
"password",
"*.password",
"token",
"*.token",
"apiKey",
"*.apiKey",
"secret",
"*.secret",
],
censor: "[REDACTED]",
},
...(prettyPrint && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "SYS:standard",
ignore: "pid,hostname",
},
},
}),
});

Comment on lines +210 to +212
logger.error(`Failed to fetch image ${part.image_url.url}`, {
err: error instanceof Error ? error : new Error(String(error)),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize image URLs in error logs

This logs full URLs, which can include tokens or private paths. Align with earlier truncation/sanitization.

-							logger.error(`Failed to fetch image ${part.image_url.url}`, {
-								err: error instanceof Error ? error : new Error(String(error)),
-							});
+							logger.error("Failed to fetch image", {
+								err: error instanceof Error ? error : new Error(String(error)),
+								url: part.image_url.url.startsWith("data:")
+									? "data:[truncated]"
+									: part.image_url.url.substring(0, 50) + "...",
+							});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.error(`Failed to fetch image ${part.image_url.url}`, {
err: error instanceof Error ? error : new Error(String(error)),
});
logger.error("Failed to fetch image", {
err: error instanceof Error ? error : new Error(String(error)),
url: part.image_url.url.startsWith("data:")
? "data:[truncated]"
: part.image_url.url.substring(0, 50) + "...",
});
🤖 Prompt for AI Agents
In packages/models/src/provider-api.ts around lines 210 to 212, the error log
includes the full image URL (which may contain tokens/private paths); replace
the direct usage of part.image_url.url with the same sanitization/truncation
used elsewhere (e.g., call the existing sanitize/truncate helper or strip
query/auth parts and limit length) and log only the sanitized/truncated URL in
the logger.error call while keeping the error object as-is.

@steebchen steebchen changed the title feat: introduce @llmgateway/logger package and replace console logs with structured pino logger feat: introduce @llmgateway/logger package Aug 31, 2025
steebchen and others added 2 commits August 31, 2025 23:17
- Resolve import conflicts in provider-api.ts keeping logger import
- Keep validation logging with debug method
- Regenerate pnpm-lock.yaml to resolve dependency conflicts
- Preserve all logging infrastructure changes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…atements

- Add eslint no-console rule enforced for backend code (apps/api, apps/gateway, packages/)
- Allow console statements in test files and UI components (apps/ui, apps/docs)
- Systematically replace all console statements in backend services with structured logger calls:
  - apps/gateway/src/worker.ts: 19+ console replacements
  - apps/gateway/src/chat/chat.ts: 15+ console replacements
  - apps/api/src/stripe.ts: 40+ console replacements
  - All other backend service files updated
- Maintain console statements in test files for test output
- All packages build successfully with new logging infrastructure
- Production logging now fully structured and professional

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@steebchen steebchen changed the title feat: introduce @llmgateway/logger package feat: introduce @llmgateway/logger package and replace console logs with structured logging Aug 31, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (18)
apps/gateway/src/lib/redis.ts (1)

38-39: Use RPOP instead of LPOP for FIFO
In apps/gateway/src/lib/redis.ts (line 38), change the pop call to RPOP so LPUSH+RPOP yields FIFO semantics:

- const result = await redisClient.lpop(queue, 10);
+ const result = await redisClient.rpop(queue, 10);
apps/gateway/src/lib/costs.ts (1)

60-66: Fix falsy checks for token counts (0 is valid) — use nullish checks.

Zero tokens are legitimate; current falsy checks mis-treat 0 as “missing,” causing incorrect estimation and null costs.

- if ((!promptTokens || !completionTokens) && fullOutput) {
+ if ((promptTokens == null || completionTokens == null) && fullOutput) {

- if (!promptTokens && fullOutput) {
+ if (promptTokens == null && fullOutput) {

- if (!completionTokens && fullOutput && fullOutput.completion) {
+ if (completionTokens == null && fullOutput && fullOutput.completion) {

- if (!calculatedPromptTokens || !calculatedCompletionTokens) {
+ if (calculatedPromptTokens == null || calculatedCompletionTokens == null) {

Also applies to: 87-96, 98-111

apps/gateway/src/models/models.ts (1)

262-265: Remove as any; use a precise type extension.

Avoids violating TS guideline and keeps type safety.

- const supportedParameters = (provider as any)?.supportedParameters as
-   | string[]
-   | undefined;
+ type ProviderWithSupported = ProviderModelMapping & {
+   supportedParameters?: string[];
+ };
+ const supportedParameters =
+   (provider as ProviderWithSupported).supportedParameters;
apps/gateway/src/scripts/generate-openapi.ts (1)

14-15: Eliminate as any on securitySchemes.

Keep strict typing; merge objects instead of casting.

- spec.components.securitySchemes = config.components.securitySchemes as any;
+ spec.components.securitySchemes = {
+   ...(spec.components.securitySchemes ?? {}),
+   ...(config.components.securitySchemes ?? {}),
+ };
apps/api/src/stripe.ts (1)

559-576: Remove as any on Stripe types; use exact fields.

Use Stripe’s typed properties and extract IDs safely.

- const subscription = (invoice as any).subscription;
+ const subscription = invoice.subscription;
  // Extract subscription ID from line items if not directly available
- let subscriptionId =
-   typeof subscription === "string" ? subscription : subscription?.id;
+ let subscriptionId =
+   typeof subscription === "string" ? subscription : subscription?.id ?? null;

@@
- stripePaymentIntentId: (invoice as any).payment_intent,
+ stripePaymentIntentId:
+   typeof invoice.payment_intent === "string"
+     ? invoice.payment_intent
+     : invoice.payment_intent?.id ?? undefined,

Also applies to: 615-616

packages/models/src/provider-api.ts (5)

260-264: Avoid echoing raw URLs in fallback user-visible text.

This text can reach external providers and logs. Replace with a generic placeholder.

Apply:

-							return {
-								type: "text",
-								text: `[Image failed to load: ${part.image_url.url}]`,
-							} as TextContent;
+							return {
+								type: "text",
+								text: "[Image failed to load]",
+							} as TextContent;

55-56: btoa is not reliable in Node runtimes; use Buffer for base64.

Ensures compatibility across Node/browser.

Apply:

-		const base64Data = isBase64 ? data : btoa(data);
+		const base64Data = isBase64 ? data : Buffer.from(data, "utf8").toString("base64");
-		const base64 = btoa(binaryString);
+		const base64 = Buffer.from(binaryString, "binary").toString("base64");

Also applies to: 121-122


213-223: Remove any: type messages precisely.

Aligns with TS guideline “never use any”.

Apply:

-function transformMessagesForNoSystemRole(messages: any[]): any[] {
+function transformMessagesForNoSystemRole(messages: BaseMessage[]): BaseMessage[] {

415-419: Avoid requestBody: any.

Use a broad but typed record to keep flexibility without any.

Apply:

-	const requestBody: any = {
+	const requestBody: Record<string, unknown> = {

1062-1066: Add timeout to provider validation request.

Prevents API key validation from stalling callers under network issues.

Apply:

-		const response = await fetch(endpoint, {
+		const response = await fetch(endpoint, {
 			method: "POST",
 			headers,
 			body: JSON.stringify(payload),
-		});
+			signal: AbortSignal.timeout(10_000),
+		});
apps/api/src/routes/beacon.ts (2)

48-48: Remove any: type context explicitly.

Use Hono Context to satisfy “no any” guideline.

Apply:

+import type { Context } from "hono";
-function extractClientIP(c: any): string | null {
+function extractClientIP(c: Context): string | null {

75-75: Remove any: type context explicitly (region helper).

Apply:

-function extractRegionInfo(c: any): { country?: string; region?: string } {
+function extractRegionInfo(c: Context): { country?: string; region?: string } {
apps/api/src/routes/chat.ts (1)

75-88: Replace as any with proper StatusCode typing.

Meets “no any” rule and preserves typesafety.

Apply:

+import type { StatusCode } from "hono/utils/http-status";
-					return c.json(
-						{ error: "gateway returned: " + errorJson.message },
-						response.status as any,
-					);
+					return c.json(
+						{ error: "gateway returned: " + errorJson.message },
+						response.status as StatusCode,
+					);
-				return c.json(
-					{ error: `Failed to get chat completion: ${errorText}` },
-					response.status as any,
-				);
+				return c.json(
+					{ error: `Failed to get chat completion: ${errorText}` },
+					response.status as StatusCode,
+				);
-				return c.json(
-					{ error: `Failed to get chat completion: ${err}` },
-					response.status as any,
-				);
+				return c.json(
+					{ error: `Failed to get chat completion: ${String(err)}` },
+					response.status as StatusCode,
+				);
apps/api/src/serve.ts (1)

38-41: Replace any with precise types for server and signal

Repository guideline forbids : any. Tighten types to Node’s http.Server and NodeJS.Signals. Also type the close callback error.

+import type { Server } from "http";
@@
-const closeServer = (server: any): Promise<void> => {
+const closeServer = (server: Server): Promise<void> => {
   return new Promise((resolve, reject) => {
-    server.close((error: any) => {
+    server.close((error?: Error) => {
       if (error) {
         reject(error);
       } else {
         resolve();
       }
     });
   });
 };
@@
-const gracefulShutdown = async (signal: string, server: any) => {
+const gracefulShutdown = async (signal: NodeJS.Signals, server: Server) => {

Also applies to: 50-50

apps/gateway/src/worker.ts (4)

58-66: Remove as any and harden PG unique-violation detection in lock acquisition.

Avoids forbidden as any, improves type-safety, and keeps behavior identical.

Apply this diff:

-    if (
-      typeof (error as any)?.code === "string" &&
-      (error as any).code === "23505"
-    ) {
-      return false;
-    }
+    if (isPgUniqueViolation(error)) {
+      return false;
+    }

Add this helper once (e.g., above acquireLock):

function isPgUniqueViolation(err: unknown): err is { code: "23505" } {
  return (
    typeof err === "object" &&
    err !== null &&
    "code" in err &&
    (err as Record<string, unknown>).code === "23505"
  );
}

153-154: Validate and clamp topUpAmount to a sane positive number.

Prevents NaN/zero/negative amounts reaching Stripe and DB.

-        const topUpAmount = Number(org.autoTopUpAmount || "10");
+        const rawAmount = Number(org.autoTopUpAmount ?? 10);
+        const topUpAmount = Number.isFinite(rawAmount) && rawAmount > 0 ? rawAmount : 10;

399-406: Eliminate as any on log inserts by normalizing payload shape.

Keep types consistent by nulling large fields when retention is “none”.

-        if (organization?.retentionLevel === "none") {
-          const {
-            messages: _messages,
-            content: _content,
-            ...metadataOnly
-          } = data;
-          return metadataOnly;
-        }
-        return data;
+        if (organization?.retentionLevel === "none") {
+          return { ...data, messages: null, content: null };
+        }
+        return data;
-    await db.insert(log).values(processedLogData as any);
+    await db.insert(log).values(processedLogData);

Also applies to: 413-413, 415-418


18-19: Avoid cross-app imports for payments/fees; move to shared package.

Importing from ../../api/src/... couples gateway worker to API internals and can break builds/deploys. Extract Stripe client init and fee calculator into shared packages (e.g., @llmgateway/payments and @llmgateway/fees) and depend on those here and in the API.

Also applies to: 195-197, 228-239, 250-255

♻️ Duplicate comments (1)
packages/models/src/provider-api.ts (1)

256-258: Do not log full image URLs (sanitize/truncate).

Leak risk: tokens/paths may be embedded. Log a sanitized/truncated variant and keep the error object structured.

Apply:

-							logger.error(`Failed to fetch image ${part.image_url.url}`, {
-								err: error instanceof Error ? error : new Error(String(error)),
-							});
+							logger.error("Failed to fetch image", {
+								err: error instanceof Error ? error : new Error(String(error)),
+								url:
+									part.image_url.url.startsWith("data:")
+										? "data:[truncated]"
+										: part.image_url.url.split("?")[0].substring(0, 50) + "...",
+							});
🧹 Nitpick comments (39)
apps/gateway/src/lib/redis.ts (3)

10-15: Use a Redis-scoped child logger for consistent context.

Attach a module-scoped child logger (e.g., { component: "redis" }) so every log line is searchable by component. Then use it in handlers here and below.

Example (outside selected lines):

const log = logger.child({ component: "redis" });
// ...
redisClient.on("error", (err) =>
  log.error("Redis Client Error", err instanceof Error ? err : new Error(String(err)))
);

26-29: Add queue context to the error log payload.

Include the queue name as structured data to aid debugging.

-    logger.error(
-      "Error publishing to queue",
-      error instanceof Error ? error : new Error(String(error)),
-    );
+    logger.error(
+      "Error publishing to queue",
+      { err: (error instanceof Error ? error : new Error(String(error))), queue },
+    );

Optional robustness (outside selected lines): avoid JSON.stringify pitfalls (BigInt, etc.).

const payload =
  typeof message === "string"
    ? message
    : JSON.stringify(message, (_k, v) => (typeof v === "bigint" ? v.toString() : v));
await redisClient.lpush(queue, payload);

46-49: Add queue context to consumer error logs.

Mirror producer logs with structured context.

-    logger.error(
-      "Error consuming from queue",
-      error instanceof Error ? error : new Error(String(error)),
-    );
+    logger.error(
+      "Error consuming from queue",
+      { err: (error instanceof Error ? error : new Error(String(error))), queue },
+    );
apps/gateway/src/lib/cache.ts (3)

2-2: Prefer a child logger for scoped context.

Attach consistent metadata (module scope) once and reuse.

-import { logger } from "@llmgateway/logger";
+import { logger } from "@llmgateway/logger";
+const log = logger.child({ module: "cache" });

29-30: Type-safe catch + structured context in logs.

  • Use unknown in catch, then narrow to Error.
  • Include operation context (keys/ids) so logs are actionable.
  • Use the child logger above for consistency.
-  } catch (error) {
-    logger.error("Error setting cache:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("cache.set failed", { ...meta, key, expirationSeconds });
   }

-  } catch (error) {
-    logger.error("Error getting cache:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("cache.get failed", { ...meta, key });
     return null;
   }

-  } catch (error) {
-    logger.error("Error checking if caching is enabled:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("cache.config failed", { ...meta, projectId, key: `project_cache_config:${projectId}` });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error fetching project:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("project fetch failed", { ...meta, projectId, key: `project:${projectId}` });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error fetching organization:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("organization fetch failed", { ...meta, organizationId, key: `organization:${organizationId}` });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error fetching provider key:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("providerKey fetch failed", { ...meta, organizationId, provider });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error fetching custom provider key:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("customProviderKey fetch failed", { ...meta, organizationId, customName });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error checking if custom provider exists:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("customProvider.exists check failed", { ...meta, organizationId, providerCandidate });
     throw error;
   }

-  } catch (error) {
-    logger.error("Error setting streaming cache:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("streamingCache.set failed", { ...meta, key, expirationSeconds });
   }

-  } catch (error) {
-    logger.error("Error getting streaming cache:", error);
+  } catch (error: unknown) {
+    const meta = error instanceof Error ? { err: error } : { error };
+    log.error("streamingCache.get failed", { ...meta, key });
     return null;
   }

Also applies to: 41-43, 78-80, 106-108, 134-136, 171-173, 211-213, 250-252, 294-296, 308-311


16-21: Remove remaining any by making cache helpers generic.

Aligns with “no : any” TS guideline and improves type safety.

-export async function setCache(
-  key: string,
-  value: any,
-  expirationSeconds: number,
-): Promise<void> {
+export async function setCache<T>(
+  key: string,
+  value: T,
+  expirationSeconds: number,
+): Promise<void> {

-export async function getCache(key: string): Promise<any | null> {
+export async function getCache<T>(key: string): Promise<T | null> {
   try {
     const cachedValue = await redisClient.get(key);
     if (!cachedValue) {
       return null;
     }
-    return JSON.parse(cachedValue);
+    return JSON.parse(cachedValue) as T;

Optionally specialize call sites for stronger types, e.g.:

const cachedProject = await getCache<InferSelectModel<typeof tables.project>>(projectCacheKey);

Also applies to: 33-34

apps/gateway/src/lib/costs.ts (1)

139-141: Minor: prefer nullish check for cachedTokens.

- const cachedInputCost = cachedTokens ? cachedTokens * cachedInputPrice : 0;
+ const cachedInputCost = cachedTokens != null ? cachedTokens * cachedInputPrice : 0;
packages/models/src/provider-api.ts (1)

72-75: Strip query strings in logged URLs.

Truncate and drop ?query to avoid leaking tokens.

Apply:

-		logger.warn("Non-HTTPS URL provided for image fetch in production", {
-			url: url.substring(0, 20) + "...",
+		logger.warn("Non-HTTPS URL provided for image fetch in production", {
+			url: url.split("?")[0].substring(0, 20) + "...",
		});
-			logger.warn(`Failed to fetch image from URL (${response.status})`, {
-				url: url.substring(0, 50) + "...",
+			logger.warn(`Failed to fetch image from URL (${response.status})`, {
+				url: url.split("?")[0].substring(0, 50) + "...",
			});
-			logger.warn("Invalid content type for image URL", {
-				contentType,
-				url: url.substring(0, 50) + "...",
+			logger.warn("Invalid content type for image URL", {
+				contentType,
+				url: url.split("?")[0].substring(0, 50) + "...",
			});
-		logger.error("Error processing image URL", {
+		logger.error("Error processing image URL", {
 			err: error instanceof Error ? error : new Error(String(error)),
-			url: url.substring(0, 50) + "...",
+			url: url.split("?")[0].substring(0, 50) + "...",
 		});

Also applies to: 82-85, 99-103, 129-132

apps/api/src/routes/beacon.ts (1)

130-136: Minimize PII in info logs.

Consider hashing UUID and redacting IP in logs.

Apply:

-	logger.info("Received installation beacon", {
-		uuid: beaconData.uuid,
-		type: beaconData.type,
-		clientIP,
-		country: regionInfo.country,
-		cloudProvider,
-	});
+	logger.info("Received installation beacon", {
+		uuid: beaconData.uuid.substring(0, 8) + "...",
+		type: beaconData.type,
+		clientIP: clientIP ? "[redacted]" : null,
+		country: regionInfo.country,
+		cloudProvider,
+	});
apps/api/src/routes/chat.ts (1)

52-68: Optional: add fetch timeout.

Prevents indefinite waits to the gateway in failure scenarios.

Apply:

-		const response = await fetch(
+		const response = await fetch(
 			process.env.NODE_ENV === "production"
 				? "https://api.llmgateway.io/v1/chat/completions"
 				: "http://localhost:4001/v1/chat/completions",
 			{
 				method: "POST",
 				headers: {
 					"Content-Type": "application/json",
 					Authorization: `Bearer ${authToken}`,
 				},
 				body: JSON.stringify({
 					model,
 					messages,
 					stream,
 				}),
+				signal: AbortSignal.timeout(15_000),
 			},
 		);
apps/api/src/lib/beacon.ts (2)

43-45: Fix log message punctuation/clarity.

Apply:

-	logger.info(
-		"Sending installation beacon (for anonymous tracking of self-hosted installs. To disable, set TELEMETRY_ACTIVE=false in your environment variables.",
-	);
+	logger.info(
+		"Sending installation beacon for anonymous tracking of self-hosted installs. To disable, set TELEMETRY_ACTIVE=false.",
+	);

76-81: Consistent error field name.

Consider err (not error) to align with logger.error structure and keep parity across levels.

Apply:

-		logger.warn("Failed to send installation beacon", {
-			error: error instanceof Error ? error : new Error(String(error)),
+		logger.warn("Failed to send installation beacon", {
+			err: error instanceof Error ? error : new Error(String(error)),
 		});
apps/api/src/routes/subscriptions.ts (1)

139-140: Avoid leaking error details to clients.

The error is already logged; client message should be generic.

Apply:

-		throw new HTTPException(500, {
-			message: `Failed to create checkout session: ${error}`,
-		});
+		throw new HTTPException(500, {
+			message: "Failed to create checkout session",
+		});
apps/api/src/index.ts (3)

49-49: Include request context in 5xx HTTPException logs

Add method/path to aid triage without grepping separate access logs.

-      logger.error("HTTPException", error);
+      logger.error("HTTPException", {
+        err: error,
+        status,
+        method: c.req.method,
+        path: c.req.path,
+      });

64-67: Enrich “Unhandled error” logs with request context

Keeps parity with HTTPException logging for faster debugging.

-logger.error(
-  "Unhandled error",
-  error instanceof Error ? error : new Error(String(error)),
-);
+logger.error("Unhandled error", {
+  err: error instanceof Error ? error : new Error(String(error)),
+  method: c.req.method,
+  path: c.req.path,
+});

119-122: Refactor logger.error to use object payload
Use { err } to attach the error (with stack) and allow adding fields later.

-logger.error(
-  "Database healthcheck failed",
-  error instanceof Error ? error : new Error(String(error)),
-);
+logger.error("Database healthcheck failed", {
+  err: error instanceof Error ? error : new Error(String(error)),
+});
apps/api/src/serve.ts (1)

28-33: Nit: log “listening” after server starts

Minor ordering tweak to avoid a misleading “listening” log if serve() throws.

-  logger.info("Server listening", { port });
-
-  return serve({
+  const server = serve({
     port,
     fetch: app.fetch,
   });
+  logger.info("Server listening", { port });
+  return server;
apps/gateway/src/worker.ts (5)

228-236: Use warn for requires_action Stripe status.

Action required likely indicates a degraded path for off-session payments.

-            logger.info(
+            logger.warn(
               `Auto top-up requires action for organization ${org.id}: ${paymentIntent.status}`,
             );

361-361: Mask API key identifiers in logs.

Prevents leaking full internal identifiers in logs while keeping them traceable.

-          logger.info(`Added ${totalCost} usage to API key ${apiKeyId}`);
+          logger.info(`Added ${totalCost} usage to API key ${maskId(apiKeyId)}`);

Add helper (once near the top):

const maskId = (id: string) => (id?.length > 8 ? `${id.slice(0, 4)}${id.slice(-4)}` : id);

24-26: Lock TTL may expire mid-work; consider a heartbeat.

With a fixed 5-minute TTL and no updatedAt refresh, long-running batches can have their locks stolen by another worker. Mitigate by periodically bumping updatedAt while processing or extend TTL to worst-case duration.

Also applies to: 278-283


341-348: Optional: guard against negative credits.

If business rules disallow negative balances, clamp at zero or add a DB CHECK constraint; otherwise, ignore.


431-435: Optional: use a child logger with module context.

Gives every message consistent context without repeating identifiers.

Example:

const logw = logger.child({ module: "gateway-worker" });
// then use logw.info/error/...
apps/gateway/src/chat/chat.ts (17)

2077-2088: Use warn (not error) for expected client validation failures

This path throws a 400 for an invalid parameter. Log as warn to avoid polluting error budgets with client mistakes.

-			logger.error(
+			logger.warn(
 				`Reasoning effort specified for non-reasoning model: ${requestedModel}`,
 				{
 					requestedModel,
 					requestedProvider,
 					reasoning_effort,
 					modelProviders: modelInfo.providers.map((p) => ({
 						providerId: p.providerId,
 						reasoning: (p as any).reasoning,
 					})),
 				},
 			);

2713-2715: Ensure stack capture in warn logs

Pass the Error under the conventional “err” key so the logger (and pino tooling) can serialize stacks consistently.

-						logger.warn("Failed to parse cached chunk", {
-							error: e instanceof Error ? e : new Error(String(e)),
-						});
+						logger.warn("Failed to parse cached chunk", {
+							err: e instanceof Error ? e : new Error(String(e)),
+						});

3087-3091: Add correlation context to provider error logs

Include requestId/provider/model to make incidents traceable without scanning DB logs.

-				logger.error("Provider error", {
-					status: res.status,
-					errorText: errorResponseText,
-				});
+				logger.error("Provider error", {
+					status: res.status,
+					errorText: errorResponseText,
+					requestId,
+					provider: usedProvider,
+					model: usedModel,
+				});

3247-3249: Attach requestId to buffer overflow warning

Helps correlate with the same request’s DB log entry and upstream events.

-						logger.warn(
-							"Buffer size exceeded 10MB, clearing buffer to prevent memory exhaustion",
-						);
+						logger.warn(
+							"Buffer size exceeded 10MB, clearing buffer to prevent memory exhaustion",
+							{ requestId }
+						);

3389-3397: Prefer “err” + include requestId in streaming JSON-parse warning

This preserves the stack and improves correlation.

-							logger.warn("Event data contains SSE field", {
+							logger.warn("Event data contains SSE field", {
 								eventData:
 									eventData.substring(0, 200) +
 									(eventData.length > 200 ? "..." : ""),
 								dataIndex,
 								eventEnd,
 								bufferLength: bufferCopy.length,
 								provider: usedProvider,
+								requestId,
 							});

3492-3501: Promote error details to “err” field and add requestId

Ensures stack capture and correlation.

-								logger.warn("Failed to parse streaming JSON", {
-									error: e instanceof Error ? e.message : String(e),
+								logger.warn("Failed to parse streaming JSON", {
+									err: e instanceof Error ? e : new Error(String(e)),
 									eventData:
 										eventData.substring(0, 200) +
 										(eventData.length > 200 ? "..." : ""),
 									provider: usedProvider,
 									eventLength: eventData.length,
 									bufferEnd: eventEnd,
 									bufferLength: bufferCopy.length,
+									requestId,
 								});

3749-3751: Attach requestId to stream read error

Minor, but helpful for cross-referencing.

-					logger.error(
-						"Error reading stream",
-						error instanceof Error ? error : new Error(String(error)),
-					);
+					logger.error(
+						"Error reading stream",
+						error instanceof Error ? error : new Error(String(error)),
+					);
+					logger.error("Error reading stream context", { requestId, provider: usedProvider, model: usedModel });

3776-3780: Attach requestId to SSE send error

Same traceability rationale.

-						logger.error(
+						logger.error(
 							"Failed to send error SSE",
 							sseError instanceof Error
 								? sseError
 								: new Error(String(sseError)),
-						);
+						);
+						logger.error("Failed to send error SSE context", { requestId });

3827-3829: Good: streamed prompt token encoding error is normalized

Consider adding requestId for correlation.

-							logger.error(
+							logger.error(
 								"Failed to encode chat messages in streaming",
 								error instanceof Error ? error : new Error(String(error)),
 							);
+							logger.debug("Encoding failure context", { requestId });

3842-3844: Add requestId to completion encoding error

Consistent with other error logs.

-							logger.error(
+							logger.error(
 								"Failed to encode completion text in streaming",
 								error instanceof Error ? error : new Error(String(error)),
 							);
+							logger.debug("Encoding failure context", { requestId });

3923-3925: Add requestId to final usage chunk send error

Minor correlation aid.

-						logger.error(
+						logger.error(
 							"Error sending final usage chunk",
 							error instanceof Error ? error : new Error(String(error)),
 						);
+						logger.debug("Final usage chunk context", { requestId });

4033-4035: Add requestId to streaming cache save error

Aids cache troubleshooting.

-						logger.error(
+						logger.error(
 							"Error saving streaming cache",
 							error instanceof Error ? error : new Error(String(error)),
 						);
+						logger.debug("Streaming cache context", { requestId, streamingCacheKey });

4148-4151: Add correlation context to non-streaming provider errors

Mirror the streaming branch improvement.

-		logger.error("Provider error", {
-			status: res.status,
-			errorText: errorResponseText,
-		});
+		logger.error("Provider error", {
+			status: res.status,
+			errorText: errorResponseText,
+			requestId,
+			provider: usedProvider,
+			model: usedModel,
+		});

4263-4265: Gate heavy debug logging behind x-debug and summarize

Avoid dumping full provider responses into logs; log a compact summary when x-debug=true.

-	if (process.env.NODE_ENV !== "production") {
-		logger.debug("API response", { response: json });
-	}
+	if (process.env.NODE_ENV !== "production" && debugMode) {
+		logger.debug("API response (summary)", {
+			id: (json as any)?.id,
+			model: (json as any)?.model,
+			choicesLength: Array.isArray((json as any)?.choices) ? (json as any).choices.length : undefined,
+			status: (json as any)?.status,
+			requestId,
+		});
+	}

4283-4286: Don’t log base64 image payloads; log metadata instead

These can be huge and sensitive. Log count/types only.

-	logger.debug("Gateway - parseProviderResponse extracted images", { images });
-	logger.debug("Gateway - Used provider", { usedProvider });
-	logger.debug("Gateway - Used model", { usedModel });
+	logger.debug("Gateway response metadata", {
+		usedProvider,
+		usedModel,
+		imageCount: images?.length ?? 0,
+		imageTypes: (images ?? []).slice(0, 5).map((i) => i.image_url?.url?.split(';')?.[0]?.replace('data:', '') || 'unknown'),
+		requestId,
+	});

558-576: DRY up repeated “tokenizer encode failed” logging

The same pattern appears in multiple catch blocks. Consider a tiny helper to reduce duplication and ensure consistent context.

Example helper (placed near other utils in this file):

function logEncodeFailure(context: string, err: unknown, extra?: object) {
	const error = err instanceof Error ? err : new Error(String(err));
	logger.error(context, error);
	if (extra) logger.debug(`${context} context`, extra);
}

Usage:

logEncodeFailure("Failed to encode completion text", error, { requestId });

Also applies to: 3825-3846


1860-1866: Consider a per-request child logger

Creating a child logger early (e.g., after computing requestId) avoids repeating correlation fields and keeps logs consistent.

Example:

const reqLog = logger.child({ requestId, route: "chat.completions" });
// Then use reqLog.*() throughout this handler
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 51e7857 and 88ec008.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • apps/api/package.json (1 hunks)
  • apps/api/src/index.ts (4 hunks)
  • apps/api/src/lib/beacon.ts (4 hunks)
  • apps/api/src/routes/beacon.ts (2 hunks)
  • apps/api/src/routes/chat.ts (3 hunks)
  • apps/api/src/routes/subscriptions.ts (6 hunks)
  • apps/api/src/scripts/generate-openapi.ts (2 hunks)
  • apps/api/src/serve.ts (5 hunks)
  • apps/api/src/stripe.ts (35 hunks)
  • apps/gateway/package.json (1 hunks)
  • apps/gateway/src/chat/chat.ts (18 hunks)
  • apps/gateway/src/lib/cache.ts (11 hunks)
  • apps/gateway/src/lib/costs.ts (3 hunks)
  • apps/gateway/src/lib/redis.ts (4 hunks)
  • apps/gateway/src/models/models.ts (2 hunks)
  • apps/gateway/src/scripts/generate-openapi.ts (2 hunks)
  • apps/gateway/src/worker.ts (16 hunks)
  • apps/ui/src/lib/server-api.ts (1 hunks)
  • eslint.config.mjs (2 hunks)
  • packages/models/src/provider-api.ts (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/ui/src/lib/server-api.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/gateway/package.json
  • apps/api/package.json
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/api/src/routes/subscriptions.ts
  • apps/gateway/src/lib/costs.ts
  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/lib/beacon.ts
  • apps/api/src/stripe.ts
  • apps/api/src/scripts/generate-openapi.ts
  • apps/api/src/index.ts
  • apps/gateway/src/lib/cache.ts
  • apps/gateway/src/worker.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/scripts/generate-openapi.ts
  • apps/api/src/routes/beacon.ts
  • apps/gateway/src/lib/redis.ts
  • apps/api/src/serve.ts
  • packages/models/src/provider-api.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/api/src/routes/subscriptions.ts
  • apps/gateway/src/lib/costs.ts
  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/lib/beacon.ts
  • apps/api/src/stripe.ts
  • apps/api/src/scripts/generate-openapi.ts
  • apps/api/src/index.ts
  • apps/gateway/src/lib/cache.ts
  • apps/gateway/src/worker.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/scripts/generate-openapi.ts
  • apps/api/src/routes/beacon.ts
  • apps/gateway/src/lib/redis.ts
  • apps/api/src/serve.ts
  • packages/models/src/provider-api.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • apps/api/src/routes/subscriptions.ts
  • apps/gateway/src/lib/costs.ts
  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/lib/beacon.ts
  • apps/api/src/stripe.ts
  • apps/api/src/scripts/generate-openapi.ts
  • apps/api/src/index.ts
  • apps/gateway/src/lib/cache.ts
  • apps/gateway/src/worker.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/scripts/generate-openapi.ts
  • apps/api/src/routes/beacon.ts
  • apps/gateway/src/lib/redis.ts
  • apps/api/src/serve.ts
  • packages/models/src/provider-api.ts
apps/{api,gateway}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{api,gateway}/**/*.{ts,tsx}: Use Zod schemas for validation in backend services
Use Hono as the web framework for backend routes and middleware

apps/{api,gateway}/**/*.{ts,tsx}: Use Drizzle with the latest object syntax for database operations
For read queries, use db().query.

.findMany() or db().query.
.findFirst()

Files:

  • apps/api/src/routes/subscriptions.ts
  • apps/gateway/src/lib/costs.ts
  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/lib/beacon.ts
  • apps/api/src/stripe.ts
  • apps/api/src/scripts/generate-openapi.ts
  • apps/api/src/index.ts
  • apps/gateway/src/lib/cache.ts
  • apps/gateway/src/worker.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/scripts/generate-openapi.ts
  • apps/api/src/routes/beacon.ts
  • apps/gateway/src/lib/redis.ts
  • apps/api/src/serve.ts
apps/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In apps/api (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Files:

  • apps/api/src/routes/subscriptions.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/lib/beacon.ts
  • apps/api/src/stripe.ts
  • apps/api/src/scripts/generate-openapi.ts
  • apps/api/src/index.ts
  • apps/api/src/routes/beacon.ts
  • apps/api/src/serve.ts
apps/gateway/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In apps/gateway (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Files:

  • apps/gateway/src/lib/costs.ts
  • apps/gateway/src/models/models.ts
  • apps/gateway/src/lib/cache.ts
  • apps/gateway/src/worker.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/scripts/generate-openapi.ts
  • apps/gateway/src/lib/redis.ts
🧠 Learnings (4)
📚 Learning: 2025-08-29T02:12:34.132Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T02:12:34.132Z
Learning: Applies to apps/{api,gateway}/**/*.{ts,tsx} : Use Hono as the web framework for backend routes and middleware

Applied to files:

  • apps/api/src/routes/subscriptions.ts
  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/stripe.ts
  • apps/api/src/index.ts
  • apps/api/src/routes/beacon.ts
📚 Learning: 2025-08-29T15:31:07.044Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.044Z
Learning: Applies to apps/gateway/**/*.{ts,tsx} : In apps/gateway (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Applied to files:

  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/routes/beacon.ts
📚 Learning: 2025-08-29T15:31:07.044Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.044Z
Learning: Applies to apps/api/**/*.{ts,tsx} : In apps/api (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Applied to files:

  • apps/gateway/src/models/models.ts
  • apps/api/src/routes/chat.ts
  • apps/api/src/routes/beacon.ts
📚 Learning: 2025-08-29T02:12:34.132Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T02:12:34.132Z
Learning: Applies to **/*.spec.ts : Name unit test files with the .spec.ts suffix

Applied to files:

  • eslint.config.mjs
🧬 Code graph analysis (16)
apps/api/src/routes/subscriptions.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/lib/costs.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/models/models.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/api/src/routes/chat.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/api/src/lib/beacon.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/api/src/stripe.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/api/src/scripts/generate-openapi.ts (1)
packages/logger/src/index.ts (1)
  • logger (107-107)
apps/api/src/index.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/lib/cache.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/worker.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/chat/chat.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/gateway/src/scripts/generate-openapi.ts (1)
packages/logger/src/index.ts (1)
  • logger (107-107)
apps/api/src/routes/beacon.ts (1)
packages/logger/src/index.ts (1)
  • logger (107-107)
apps/gateway/src/lib/redis.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
apps/api/src/serve.ts (2)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
packages/db/src/db.ts (1)
  • closeDatabase (20-31)
packages/models/src/provider-api.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
🔇 Additional comments (28)
eslint.config.mjs (2)

14-14: Enforcing no-console aligns with the new logger—LGTM.

This complements the migration to @llmgateway/logger and will prevent regressions.


28-42: Override split confirmed
Validation shows no console.* usage outside the allowed test patterns and test-utils, and the UI/docs override correctly enforces console restrictions.

apps/gateway/src/lib/redis.ts (1)

1-1: LGTM: centralized logger import.

Importing the shared logger via a named export matches the package API. No issues.

apps/api/src/scripts/generate-openapi.ts (1)

10-10: LGTM on logger adoption.

Structured logging replaces console calls correctly; error path preserves the Error instance.

Also applies to: 15-18

apps/gateway/src/models/models.ts (1)

237-240: LGTM on error logging.

Good use of logger.error(message, Error) to keep stack/context.

apps/gateway/src/scripts/generate-openapi.ts (1)

18-18: LGTM on logger usage.

Success and error paths now use structured logs.

Also applies to: 23-26

packages/models/src/provider-api.ts (2)

1017-1020: Good: scoped debug with model + provider.

Helpful for triaging validation flows without leaking secrets.


1-1: Nice migration to central logger.

Consistent structured logging across this module.

apps/api/src/routes/beacon.ts (1)

2-2: LGTM: central logger adoption.

apps/api/src/routes/chat.ts (2)

127-130: Good: unified error logging shape for streaming failures.


161-164: Good: unified error logging for handler failures.

apps/api/src/lib/beacon.ts (1)

67-67: LGTM: structured info on record creation.

apps/api/src/routes/subscriptions.ts (5)

134-137: LGTM: error logging migration.


209-213: LGTM: error logging migration (cancel).


295-299: LGTM: error logging migration (resume).


395-399: LGTM: error logging migration (upgrade).


472-475: LGTM: error logging migration (status fetch).

apps/api/src/index.ts (1)

4-4: LGTM: centralized logger import

Good replacement for console.* with the shared logger.

apps/api/src/serve.ts (5)

3-3: LGTM: centralized logger import

Consistent with the new logging standard.


16-19: LGTM: migration failure logging with safe error wrapping

Clear message and proper exit on failure.


52-69: LGTM: comprehensive, ordered shutdown logs

Good sequencing and clarity for ops.


71-74: LGTM: error logging during shutdown with safe wrapping

Covers non-Error cases correctly.


96-99: LGTM: startup failure logging with safe error wrapping

Clear message and correct termination.

apps/gateway/src/worker.ts (2)

13-13: Structured logger migration looks good.

Nice, consistent replacement of console.* with the centralized logger, proper error normalization, and clear lifecycle messages.

Also applies to: 120-131, 147-151, 195-197, 228-236, 238-248, 250-255, 266-269, 309-312, 345-347, 361-361, 373-373, 376-379, 427-427, 433-433, 461-464, 474-474, 479-479, 483-483, 493-493, 501-501


283-303: Confirm guideline exception for read query with row locks.

Coding guideline says reads should use db().query.<table>.*, but here a SELECT … FOR UPDATE SKIP LOCKED is used. That’s reasonable, but please confirm this exception is acceptable under team conventions.

apps/gateway/src/chat/chat.ts (3)

10-10: Good move: centralized logger import

Adopting the shared logger here aligns with the PR’s goals and improves observability.


558-561: Nice: error is properly normalized to an Error instance

This ensures stacks are emitted by the logger. Looks good.


573-576: Consistent error normalization

Same as above—good practice to guarantee stack traces.

Comment thread apps/api/src/serve.ts
Comment on lines 85 to 93
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
logger.error("Uncaught exception", error);
process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
logger.error("Unhandled rejection", { promise, reason });
process.exit(1);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Type event handler params; avoid implicit any

Strengthens typing and aligns with repo rules.

-process.on("uncaughtException", (error) => {
+process.on("uncaughtException", (error: Error) => {
   logger.error("Uncaught exception", error);
   process.exit(1);
 });
 
-process.on("unhandledRejection", (reason, promise) => {
-  logger.error("Unhandled rejection", { promise, reason });
+process.on(
+  "unhandledRejection",
+  (reason: unknown, promise: Promise<unknown>) => {
+    logger.error("Unhandled rejection", { promise, reason });
+    process.exit(1);
+  },
+);
-  process.exit(1);
-});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
logger.error("Uncaught exception", error);
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
logger.error("Unhandled rejection", { promise, reason });
process.exit(1);
});
process.on("uncaughtException", (error: Error) => {
logger.error("Uncaught exception", error);
process.exit(1);
});
process.on(
"unhandledRejection",
(reason: unknown, promise: Promise<unknown>) => {
logger.error("Unhandled rejection", { promise, reason });
process.exit(1);
},
);
🤖 Prompt for AI Agents
In apps/api/src/serve.ts around lines 85 to 93, the process event handler
parameters are implicitly any; add explicit types: declare the uncaughtException
handler parameter as (error: Error) and the unhandledRejection handler
parameters as (reason: unknown, promise: Promise<unknown>), and update the
logger calls if needed (e.g., pass error directly and serialize/inspect reason)
so TypeScript no longer reports implicit any and typings align with repo rules.

Comment thread apps/api/src/stripe.ts
Comment on lines +178 to 179
logger.info(JSON.stringify({ kind: "stripe-event", payload: event }));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid logging full Stripe payloads; risk of PII and noisy logs.

Log minimal, structured fields; emit full payload only at debug in non-prod.

- logger.info(JSON.stringify({ kind: "stripe-event", payload: event }));
+ if (process.env.NODE_ENV !== "production") {
+   logger.debug("Stripe event received", {
+     id: event.id,
+     type: event.type,
+     livemode: (event as any).livemode, // keep minimal; remove if unsure
+   });
+ } else {
+   logger.info("Stripe event received", { id: event.id, type: event.type });
+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/src/stripe.ts around lines 178 to 179, the current code logs the
entire Stripe event payload which can leak PII and makes logs noisy; change the
logging to emit a minimal structured object (e.g., event.id, event.type,
event.created, event.livemode, event.data.object?.id or customer) via
logger.info, and only log the full JSON payload at logger.debug guarded by an
environment check (non-production) to avoid exposing sensitive data in
production logs.

Comment thread apps/api/src/stripe.ts
Comment on lines +271 to 274
logger.info(
`Successfully upgraded organization ${organizationId} to pro plan via checkout. Updated rows:`,
result.length,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Second arg to logger should be an object; don’t pass a bare number.

Wrap counts in an object for structured logs.

- logger.info(
-   `Successfully upgraded organization ${organizationId} to pro plan via checkout. Updated rows:`,
-   result.length,
- );
+ logger.info(
+   `Successfully upgraded organization ${organizationId} to pro plan via checkout.`,
+   { updatedRows: result.length },
+ );

- logger.info(
-   `Successfully upgraded organization ${organizationId} to pro plan. Updated rows:`,
-   result.length,
- );
- logger.info(
-   `Verification - organization plan is now: ${result && result[0]?.plan}`,
- );
+ logger.info(
+   `Successfully upgraded organization ${organizationId} to pro plan.`,
+   { updatedRows: result.length, newPlan: result?.[0]?.plan },
+ );

Also applies to: 631-639

🤖 Prompt for AI Agents
In apps/api/src/stripe.ts around lines 271-274 (and similarly at 631-639), the
logger.info call passes a bare number as the second argument which breaks
structured logging; change the call to pass an object containing the count (e.g.
{ updatedRows: result.length }) instead of the raw number so the log entry
remains structured and readable.

Comment on lines +74 to 75
logger.error(`Failed to encode chat messages in costs: ${error}`);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Log structured errors to retain stack traces.

Pass the Error object as the second argument; avoid string interpolation.

- logger.error(`Failed to encode chat messages in costs: ${error}`);
+ logger.error("Failed to encode chat messages in costs", error instanceof Error ? error : new Error(String(error)));

- logger.error(`Failed to encode prompt text: ${error}`);
+ logger.error("Failed to encode prompt text", error instanceof Error ? error : new Error(String(error)));

- logger.error(`Failed to encode completion text: ${error}`);
+ logger.error("Failed to encode completion text", error instanceof Error ? error : new Error(String(error)));

Also applies to: 82-83, 93-94

🤖 Prompt for AI Agents
In apps/gateway/src/lib/costs.ts around lines 74-75 (and similarly at 82-83 and
93-94), the review points out that logger.error currently interpolates the Error
into a string which drops structured error data and stack traces; change those
logger.error calls to pass the Error object as a separate argument (e.g.,
logger.error("Failed to encode chat messages in costs", error)) so the logger
can retain the error stack and structured fields while keeping a clear
descriptive message.

@@ -1,3 +1,5 @@
import { logger } from "@llmgateway/logger";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find other spots that may log raw image URLs
rg -nP -C2 '(logger\.(error|warn)\s*\(|`\s*[^`]*\$\{[^}]*image_url\.url[^}]*\}\s*`)' --type=ts

Length of output: 32285


Sanitize raw image_url.url in logs: In packages/models/src/provider-api.ts at line 256, you’re logging the full part.image_url.url; truncate or mask it (e.g. use substring(…,50)+"…") instead of logging the raw URL.

🤖 Prompt for AI Agents
In packages/models/src/provider-api.ts around line 256, the code currently logs
the full part.image_url.url; update the logging to sanitize the value by
truncating or masking it (e.g., take a safe substring of the URL like
url.substring(0,50) + "…" or replace the middle with "...") and ensure you
null-check part.image_url and part.image_url.url before accessing; use the
sanitized variable in the logger call instead of the raw URL so full URLs are
never emitted in logs.

}

try {
const response = await fetch(url);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add a timeout to external fetch.

Prevents hangs on slow/unreachable hosts.

Apply:

-		const response = await fetch(url);
+		const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await fetch(url);
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
🤖 Prompt for AI Agents
In packages/models/src/provider-api.ts around line 79, the external fetch call
(const response = await fetch(url);) lacks a timeout and can hang; wrap the
fetch with an AbortController, start a timer (e.g. config-driven default like
5000ms) that calls controller.abort() on expiry, pass signal: controller.signal
into fetch, clear the timer after fetch completes, and catch the abort/timeout
error (handle as a failed request with an appropriate error or retry logic) so
slow or unreachable hosts don't block execution.

Deleted unnecessary `no-console` overrides for test and UI files in the ESLint config.
- Transitioned package to a dual-module setup (`module` for ESM, `main` for CJS) for broader compatibility.
- Replaced local `tsup.config.ts` with a shared configuration export.
- Updated `scripts.build` to use both `tsc` and `tsup` for type checks and bundling.
- Adjusted TypeScript paths for cleaner dependencies resolution.
- Replace generic `error` parameter with `error as Error` type annotations in logger calls across `stripe.ts` and `cache.ts`.
- Improve logging clarity and help with TypeScript type safety.
- Explicitly declare public access modifiers for all class methods in `LLMGatewayLogger`.
- Improves code readability and aligns with TypeScript best practices.
- Add `pathname` dependency to `useEffect` for accurate onboarding redirect behavior.
- Use `void` with `invalidateQueries` calls to comply with TypeScript standards.
- Eliminate redundant onSuccess props and logic in `UpgradeToProDialog` and related components.
- Update dependency arrays in `useEffect` for accuracy and consistency.
- Add void usage to `invalidateQueries` for TypeScript compliance.
- Eliminate unused `_err` variable in `provider-api.ts` for cleaner code.
@steebchen steebchen changed the title feat: introduce @llmgateway/logger package and replace console logs with structured logging feat: introduce @llmgateway/logger package to replace logs Sep 1, 2025
@steebchen
steebchen enabled auto-merge September 1, 2025 00:22
@steebchen
steebchen added this pull request to the merge queue Sep 1, 2025
Merged via the queue into main with commit 01eed63 Sep 1, 2025
10 of 11 checks passed
@steebchen
steebchen deleted the fix/remove-debug-logs branch September 1, 2025 00:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/ui/src/app/playground/playground-client.tsx (2)

152-160: Revert dependency array change to avoid state oscillation.

Including selectedModel causes this effect to run before searchParams reflect the navigation, briefly reverting the user’s selection from stale URL params (race/back/forward). Keep this effect driven solely by URL changes.

-	}, [searchParams, selectedModel]);
+	}, [searchParams]);

296-336: Eliminate any types for images.

Project guideline forbids : any. Type images explicitly to match Message["images"].

type ImagePart = { type: "image_url"; image_url: { url: string } };

let fullContent = "";
let finalImages: ImagePart[] = [];
let hasReceivedImages = false;

// …
let imagesToSet: ImagePart[] | undefined;
apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx (2)

67-69: Don’t use window.location for navigation in apps/ui

Per guidelines, use Next.js router for programmatic navigation.

Apply:

+ "use client";
+ import { useRouter } from "next/navigation";
@@
 function UpgradeDialogContent({
@@
 }) {
+  const router = useRouter();
@@
-      // Redirect to Stripe Checkout
-      window.location.href = checkoutUrl;
+      // Redirect to Stripe Checkout
+      router.push(checkoutUrl);

Note: This file already uses React hooks; the "use client" directive is required at the top if not present elsewhere.


1-1: Add "use client" directive at the top of this component
Insert the line

"use client";

above the existing imports in apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx, since it uses useState and must be a client component in the Next.js App Router.

♻️ Duplicate comments (1)
packages/logger/src/index.ts (1)

21-34: Add security-focused log redaction configuration

The current implementation doesn't include redaction for sensitive data, which could lead to credentials or PII being logged, especially in production JSON logs.

🧹 Nitpick comments (5)
apps/ui/src/app/playground/playground-client.tsx (2)

136-151: Guard router.replace and drop searchParams from deps to prevent replace loops.

Avoid unnecessary replaces and effect thrash by comparing URLs and depending only on selectedModel (and router).

useEffect(() => {
  const current = window.location.pathname + window.location.search;
  const params = new URLSearchParams(window.location.search);
  if (selectedModel !== "gpt-4o-mini") params.set("model", selectedModel);
  else params.delete("model");
  const next = params.toString()
    ? `${window.location.pathname}?${params.toString()}`
    : window.location.pathname;

  if (next !== current) router.replace(next);
}, [selectedModel, router]);

97-99: Align with logger migration (or confirm UI exemption).

Multiple console.* calls remain. If UI is in scope for @llmgateway/logger, switch to it; otherwise confirm ESLint overrides allow console in apps/ui.

Also applies to: 195-197, 366-367, 401-402, 441-442, 448-451

apps/ui/src/hooks/useUser.ts (1)

118-121: Batch the invalidateQueries calls

Functionality is fine and the void prefix is correct. Minor optimization: batch the two invalidations to schedule a single microtask.

Apply:

-  onSuccess: () => {
-    void queryClient.invalidateQueries({ queryKey: ["user"] });
-    void queryClient.invalidateQueries({ queryKey: ["session"] });
-  },
+  onSuccess: () => {
+    void Promise.all([
+      queryClient.invalidateQueries({ queryKey: ["user"] }),
+      queryClient.invalidateQueries({ queryKey: ["session"] }),
+    ]);
+  },
apps/ui/src/components/settings/caching-settings.tsx (1)

70-76: Align with the void invalidate pattern used elsewhere

The callback looks good. For consistency with the rest of the PR (and to silence potential no-floating-promises rules), prefix the invalidation with void.

-      queryClient.invalidateQueries({ queryKey });
+      void queryClient.invalidateQueries({ queryKey });
packages/logger/src/index.ts (1)

98-103: Consider using a more robust child logger instantiation

The current child logger creation uses Object.create() and manual property assignment, which bypasses the constructor and its validation logic. This could lead to inconsistent behavior if the constructor logic changes.

Apply this diff for a more maintainable approach:

-	public child(bindings: object): LLMGatewayLogger {
-		const childPino = this.logger.child(bindings);
-		const childLogger = Object.create(LLMGatewayLogger.prototype);
-		childLogger.logger = childPino;
-		return childLogger;
-	}
+	public child(bindings: object): LLMGatewayLogger {
+		const childPino = this.logger.child(bindings);
+		const childLogger = new LLMGatewayLogger({});
+		childLogger.logger = childPino;
+		return childLogger;
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 88ec008 and a427edb.

📒 Files selected for processing (13)
  • apps/api/src/stripe.ts (35 hunks)
  • apps/gateway/src/lib/cache.ts (11 hunks)
  • apps/ui/src/app/playground/playground-client.tsx (1 hunks)
  • apps/ui/src/components/landing/pricing-plans.tsx (0 hunks)
  • apps/ui/src/components/settings/caching-settings.tsx (1 hunks)
  • apps/ui/src/components/settings/project-mode-settings.tsx (1 hunks)
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx (1 hunks)
  • apps/ui/src/hooks/useUser.ts (2 hunks)
  • packages/logger/package.json (1 hunks)
  • packages/logger/src/index.ts (1 hunks)
  • packages/logger/tsconfig.json (1 hunks)
  • packages/logger/tsup.config.ts (1 hunks)
  • packages/models/src/provider-api.ts (8 hunks)
💤 Files with no reviewable changes (1)
  • apps/ui/src/components/landing/pricing-plans.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/logger/tsconfig.json
  • apps/api/src/stripe.ts
  • packages/models/src/provider-api.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/gateway/src/lib/cache.ts
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • packages/logger/src/index.ts
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
  • packages/logger/tsup.config.ts
apps/ui/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In apps/ui (a tanstack router project), always use navigate() for navigation

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/gateway/src/lib/cache.ts
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • packages/logger/src/index.ts
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
  • packages/logger/tsup.config.ts
apps/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/ui/**/*.{ts,tsx}: In the Next.js UI, use next/link for links and next/navigation’s router for programmatic navigation
Use localStorage (not cookies) for client-side data persistence in the UI
Use TanStack Query for client-side state management in the UI
Use Radix UI with Tailwind CSS for UI components and styling in the frontend

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
apps/{ui,docs}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
apps/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/ui/**/*.{ts,tsx,js,jsx}: In apps/ui (Next.js App Router), use next/link for links
In apps/ui, use next/navigation router.push/replace or redirect() for programmatic navigation
In apps/ui, never use window.location for navigation

Files:

  • apps/ui/src/app/playground/playground-client.tsx
  • apps/ui/src/hooks/useUser.ts
  • apps/ui/src/components/settings/project-mode-settings.tsx
  • apps/ui/src/components/settings/caching-settings.tsx
  • apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx
**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/gateway/src/lib/cache.ts
  • apps/ui/src/hooks/useUser.ts
  • packages/logger/src/index.ts
  • packages/logger/tsup.config.ts
apps/{api,gateway}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{api,gateway}/**/*.{ts,tsx}: Use Zod schemas for validation in backend services
Use Hono as the web framework for backend routes and middleware

apps/{api,gateway}/**/*.{ts,tsx}: Use Drizzle with the latest object syntax for database operations
For read queries, use db().query.

.findMany() or db().query.
.findFirst()

Files:

  • apps/gateway/src/lib/cache.ts
apps/gateway/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In apps/gateway (Hono), always use Hono + Zod + OpenAPI for validation and typesafety

Files:

  • apps/gateway/src/lib/cache.ts
🧬 Code graph analysis (1)
apps/gateway/src/lib/cache.ts (1)
packages/logger/src/index.ts (2)
  • logger (107-107)
  • error (81-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e / run
  • GitHub Check: build / run
🔇 Additional comments (9)
apps/ui/src/hooks/useUser.ts (1)

49-71: Good call: include pathname in the onboarding redirect effect deps

Re-running the redirect check on route changes avoids stale decisions when users navigate client-side.

apps/ui/src/components/settings/project-mode-settings.tsx (1)

60-66: LGTM: onSuccess signature + void invalidation

Matches the new pattern and keeps cache refresh predictable.

apps/ui/src/components/shared/upgrade-to-pro-dialog.tsx (1)

30-37: Public API change: onSuccess prop removed and no remaining call-sites
Verified that no <UpgradeToProDialog> usage passes an onSuccess prop.

packages/logger/tsup.config.ts (1)

1-3: LGTM - Clean configuration reuse

The configuration correctly re-exports the shared tsup configuration from the root, maintaining consistency across the monorepo while allowing for potential future customization if needed.

packages/logger/package.json (1)

1-29: Package configuration looks solid with proper ESM/CJS dual exports

The package.json correctly configures:

  • Dual ESM/CJS exports with proper type definitions
  • Appropriate dependencies (pino ecosystem)
  • Build toolchain with TypeScript and tsup
  • Test framework integration

The structure follows modern Node.js package standards and should integrate well with the monorepo architecture.

packages/logger/src/index.ts (2)

37-46: LGTM - Appropriate environment-based log level defaults

The log level configuration properly handles different environments:

  • Test: warn to reduce noise during testing
  • Production: info for operational visibility without debug clutter
  • Development: debug for detailed troubleshooting

81-95: Excellent Error object handling

The error and fatal methods properly handle both Error instances and plain objects, using pino's err field convention for Error objects to enable proper stack trace serialization.

apps/gateway/src/lib/cache.ts (2)

2-2: LGTM - Clean migration to centralized logging

The import of the centralized logger aligns with the PR's objective to replace ad-hoc console logging across the codebase.


29-29: Consistent error logging migration with proper Error casting

All catch blocks have been consistently updated to use logger.error() with proper Error type casting. This provides structured logging while maintaining the existing error handling semantics and control flow.

The type casting as Error is appropriate here since these catch blocks are expected to receive Error objects from the underlying operations.

Also applies to: 41-41, 78-78, 106-106, 134-134, 171-171, 211-211, 250-250, 294-294, 308-308

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant