Skip to content

Logger Architecture Migration Guide

Previously, the monorepo had two separate loggers:

  • @repo/observability/logger (OTEL-enhanced, heavy)
  • @repo/utils/logger (which just re-exported from observability)

This forced every shared package to pull in OpenTelemetry dependencies, which caused:

  1. ❌ Astro v5 ESM build failures (“require is not defined”)
  2. ❌ Transitive bundling of CommonJS OTEL code
  3. ❌ Circular dependency risks
  4. ❌ Forced observability overhead even for simple apps like CRM
@repo/utils/logger (Base Layer)
├─ Pure Pino structured logging
├─ No OTEL dependencies
├─ Lazy-loading to avoid build issues
└─ Used by: shared packages, simple apps
@repo/observability/logger (Enhancement Layer)
├─ Wraps OTEL Logs API
├─ Traces context injection
└─ Used by: apps that need full observability

Observability is now OPTIONAL

  • Apps choose their logging level
  • CRM can use simple Pino without OTEL overhead
  • Webapp gets full observability when enabled

Build issues are ELIMINATED

  • No forced CommonJS bundling
  • ESM Astro v5 builds work everywhere
  • No more “require is not defined” errors

Shared packages STAY CLEAN

  • Can import logger without forcing observability
  • Prevents circular dependencies
  • Single dependency: @repo/utils/logger

For Shared Packages (@repo/auth, @repo/db, @repo/utils, @repo/ai, @repo/crm)

Section titled “For Shared Packages (@repo/auth, @repo/db, @repo/utils, @repo/ai, @repo/crm)”

Change all logger imports from:

import { logger } from '@repo/observability/logger'; // ❌ Don't do this

To:

import { logger } from '@repo/utils/logger'; // ✅ Do this instead

Why? Shared packages should never pull in app-level observability dependencies.

For TypeScript/JavaScript Apps WITH Observability (webapp)

Section titled “For TypeScript/JavaScript Apps WITH Observability (webapp)”

Keep using:

import { logger } from '@repo/observability/logger'; // ✅ Full OTEL support

Or initialize OTEL manually:

import { logger } from '@repo/utils/logger';
import { initializeOpenTelemetry } from '@repo/observability/otel-config';
// Initialize OTEL only if endpoint is configured
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
const sdk = initializeOpenTelemetry();
}
// Logs now include trace context via OTEL instrumentation
logger.info('message');

For Apps WITHOUT Observability (CRM, standalone tools)

Section titled “For Apps WITHOUT Observability (CRM, standalone tools)”

Use the base logger:

import { logger } from '@repo/utils/logger';
logger.info('Simple structured logging without OTEL');

Benefits:

  • No OTEL dependencies in bundle
  • Faster builds
  • Cleaner dependency tree
  • Still get Pino’s structured logging
Terminal window
cd /Users/mini-boone/Dev/erp-unlocked
pnpm --filter @repo/utils test
Terminal window
pnpm --filter @erp-unlocked/crm build

✅ Verify Webapp Still Works (with observability)

Section titled “✅ Verify Webapp Still Works (with observability)”
Terminal window
pnpm --filter @erp-unlocked/webapp build
Terminal window
pnpm --filter @repo/auth build
pnpm --filter @repo/db build
pnpm --filter @repo/ai build
  1. packages/utils/src/logger.ts

    • Now contains base Pino logger implementation
    • No longer re-exports from observability
    • Exported LogLike interface for type safety
    • Added comprehensive usage documentation
  2. packages/utils/LOGGER_ARCHITECTURE.md

    • New: Architecture documentation
    • Decision rationale
    • Usage patterns
    • Troubleshooting guide

Apps Already Using @repo/observability/logger

Section titled “Apps Already Using @repo/observability/logger”

NO CHANGES REQUIRED - These continue to work exactly as before

  • Webapp imports from @repo/observability/logger → ✅ Works

Shared Packages Importing @repo/utils/logger

Section titled “Shared Packages Importing @repo/utils/logger”

NOW WORK BETTER - Observability dependency eliminated

  • @repo/auth → No longer forces OTEL bundling
  • @repo/db → Lighter bundle
  • @repo/ai → No OTEL overhead
// Pino only loads when first called on server
let _logger: LogLike | null = null;
const loggerProxy = {
get info() {
return (_logger ??= createLogger()).info.bind(_logger);
},
// ... other methods
};
// Works in browser (uses console)
// Works in Node (uses Pino)
// Works during Astro builds (graceful fallback)
const isNode = typeof process !== 'undefined' && !!(process as any).versions?.node;
// Prerendering context detected → Simple Pino
// Normal server context → Full Pino with transports
// Browser context → Console logging
  • packages/utils/src/logger.ts has base Pino implementation
  • packages/utils/LOGGER_ARCHITECTURE.md created
  • @repo/observability/logger still exports enhanced logger
  • All imports in shared packages point to @repo/utils/logger
  • CRM builds without “require is not defined” error
  • Webapp builds with full observability
  • Tests pass for all packages
  • No circular dependency warnings

See packages/utils/LOGGER_ARCHITECTURE.md for detailed information on:

  • Architecture rationale
  • When to use each logger
  • Troubleshooting common issues
  • Future enhancements