Skip to content

Logger Usage Quick Reference

CategoryImport FromReason
Shared Packages@repo/utils/loggerNever pull in observability
Apps WITH Observability@repo/observability/loggerFull OTEL support
Apps WITHOUT Observability@repo/utils/loggerLightweight, no OTEL
  • βœ… packages/auth/
  • βœ… packages/db/
  • βœ… packages/utils/
  • βœ… packages/ai/
  • βœ… packages/crm/
  • βœ… packages/ui/ (if logging)
  • βœ… packages/observability/ (for any app-independent code)
packages/auth/src/middleware.ts
import { logger } from '@repo/utils/logger';
export async function validateRequest(req: Request) {
logger.info('Validating request');
// ...
}
  • Keeps shared packages independent
  • Avoids circular dependencies
  • Prevents forced observability bundling
  • CRM (and other apps) can use without OTEL

apps/webapp/

import { logger } from '@repo/observability/logger';
export const POST: APIRoute = async ({ request }) => {
logger.info('Processing request'); // βœ… Includes trace context
// ...
};

Note: Python apps (pdf-api, pdf-worker) use their own Python-based logging and OpenTelemetry instrumentation, not the TypeScript @repo/observability package.

  • Gets full OTEL trace context automatically
  • Logs sent to observability backend
  • Performance metrics included
  • Debugging with trace correlation

apps/crm/src/utils/logger.ts

import { logger } from '@repo/utils/logger';
export default logger;

New lightweight app (hypothetical)

import { logger } from '@repo/utils/logger';
logger.info('App started');
logger.error('Something failed', error);
  • No OTEL overhead
  • Faster builds
  • Simpler dependencies
  • Still get structured Pino logging

Terminal window
# Find all logger imports
grep -r "from '@repo/observability/logger'" packages/*/src
grep -r "from '@repo/observability/logger'" apps/*/src

Change all imports in these packages:

// ❌ BEFORE
import { logger } from '@repo/observability/logger';
// βœ… AFTER
import { logger } from '@repo/utils/logger';

Packages to update:

  • packages/auth/
  • packages/db/ (if used)
  • packages/ai/
  • packages/crm/
  • packages/utils/ (internal usages)
Terminal window
# TypeScript/JavaScript apps should continue to work unchanged
pnpm --filter @erp-unlocked/webapp build
# CRM should now build without OTEL errors
pnpm --filter @erp-unlocked/crm build

Note: Python services (pdf-api, pdf-worker) use Python-based testing tools (pytest) and are not tested via pnpm commands.

Terminal window
pnpm build
pnpm test
pnpm lint

packages/
β”œβ”€β”€ auth/ ← βœ… import from @repo/utils/logger
β”œβ”€β”€ db/ ← βœ… import from @repo/utils/logger
β”œβ”€β”€ ui/ ← βœ… import from @repo/utils/logger
β”œβ”€β”€ utils/ ← βœ… import from @repo/utils/logger
β”œβ”€β”€ ai/ ← βœ… import from @repo/utils/logger
β”œβ”€β”€ crm/ ← βœ… import from @repo/utils/logger
└── observability/ ← βœ… Enhanced logger layer (OTEL)
apps/
β”œβ”€β”€ webapp/ ← πŸš€ import from @repo/observability/logger (full OTEL)
β”œβ”€β”€ crm/ ← πŸ› οΈ import from @repo/utils/logger (lightweight)
└── marketing/ ← πŸ› οΈ import from @repo/utils/logger (static site)

Note: Python services (pdf-api, pdf-worker) use their own Python-based logging and OpenTelemetry instrumentation, not the TypeScript @repo/observability package.


// First import
import { logger } from '@repo/utils/logger';
// Pino not loaded yet (lazy)
logger.info('Message'); // ← Pino loads here
// Next import - reuses same instance
import { logger as logger2 } from '@repo/utils/logger';
logger2.info('Message'); // ← Uses cached instance
import { logger } from '@repo/utils/logger';
// Node.js server
logger.info('message'); // β†’ Pino structured log to stdout
// Browser/Client
logger.info('message'); // β†’ console.log
// Astro build time
logger.info('message'); // β†’ Simple Pino (no transports)
// With OTEL enabled
// (if app initializes observability)
logger.info('message'); // β†’ Pino + OTEL instrumentation

❌ DON’T: Import observability logger in shared packages

Section titled β€œβŒ DON’T: Import observability logger in shared packages”
packages/auth/src/server.ts
import { logger } from '@repo/observability/logger'; // ❌ WRONG

Problem: Forces OTEL on all consumers

Solution: Import from utils instead

import { logger } from '@repo/utils/logger'; // βœ… CORRECT

❌ DON’T: Conditionally import based on observability

Section titled β€œβŒ DON’T: Conditionally import based on observability”
let logger;
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
logger = await import('@repo/observability/logger');
} else {
logger = await import('@repo/utils/logger');
}

Problem: Confusing and unnecessary

Solution: Always use base logger, let app layer handle OTEL

import { logger } from '@repo/utils/logger'; // βœ… SIMPLE
// Observability is app-level concern, not package-level
packages/crm/src/logger.ts
export { logger as crmLogger } from '@repo/observability/logger'; // ❌ WRONG

Problem: Defeats the purpose of the architecture

Solution: Re-export base logger if needed

export { logger } from '@repo/utils/logger'; // βœ… CORRECT

// βœ… GOOD
import { logger } from '@repo/utils/logger';
export function myFunction() {
logger.info('message');
}
// ❌ BAD (creates new import each time)
export function myFunction() {
const { logger } = await import('@repo/utils/logger');
logger.info('message');
}
// βœ… GOOD - adds context to all child logs
const requestLogger = logger.child({ requestId, userId });
requestLogger.info('Processing');
requestLogger.error('Failed');
// βœ… GOOD
logger.info('User created', { userId, email, timestamp: Date.now() });
// ❌ BAD
logger.info(`User ${userId} created at ${new Date()}`);
logger.debug('Detailed trace info'); // βœ… Development
logger.info('Normal operation'); // βœ… Important events
logger.warn('Recoverable issue'); // βœ… Warnings
logger.error('Error details', { error }); // βœ… Failures

  • LOGGER_ARCHITECTURE.md - Detailed architecture
  • LOGGER_MIGRATION_GUIDE.md - Step-by-step migration
  • LOGGER_SOLUTION_SUMMARY.md - Solution overview
  • BEFORE_AFTER_COMPARISON.md - Problem vs solution

Key Takeaway: Use @repo/utils/logger everywhere by default. Only use @repo/observability/logger in TypeScript/JavaScript apps that explicitly need full observability (e.g., webapp).