Logger Architecture Migration Guide
Problem We Solved
Section titled “Problem We Solved”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:
- ❌ Astro v5 ESM build failures (“require is not defined”)
- ❌ Transitive bundling of CommonJS OTEL code
- ❌ Circular dependency risks
- ❌ Forced observability overhead even for simple apps like CRM
Solution: Two-Layer Logger Architecture
Section titled “Solution: Two-Layer Logger Architecture”New Structure
Section titled “New Structure”@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 observabilityKey Benefits
Section titled “Key Benefits”✅ 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
Migration Steps
Section titled “Migration Steps”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 thisTo:
import { logger } from '@repo/utils/logger'; // ✅ Do this insteadWhy? 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 supportOr initialize OTEL manually:
import { logger } from '@repo/utils/logger';import { initializeOpenTelemetry } from '@repo/observability/otel-config';
// Initialize OTEL only if endpoint is configuredif (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) { const sdk = initializeOpenTelemetry();}
// Logs now include trace context via OTEL instrumentationlogger.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
Testing the Migration
Section titled “Testing the Migration”✅ Verify Base Logger Works
Section titled “✅ Verify Base Logger Works”cd /Users/mini-boone/Dev/erp-unlockedpnpm --filter @repo/utils test✅ Verify CRM Builds (no OTEL errors)
Section titled “✅ Verify CRM Builds (no OTEL errors)”pnpm --filter @erp-unlocked/crm build✅ Verify Webapp Still Works (with observability)
Section titled “✅ Verify Webapp Still Works (with observability)”pnpm --filter @erp-unlocked/webapp build✅ Verify Shared Packages Work
Section titled “✅ Verify Shared Packages Work”pnpm --filter @repo/auth buildpnpm --filter @repo/db buildpnpm --filter @repo/ai buildFiles Modified
Section titled “Files Modified”-
packages/utils/src/logger.ts- Now contains base Pino logger implementation
- No longer re-exports from observability
- Exported
LogLikeinterface for type safety - Added comprehensive usage documentation
-
packages/utils/LOGGER_ARCHITECTURE.md- New: Architecture documentation
- Decision rationale
- Usage patterns
- Troubleshooting guide
Backward Compatibility
Section titled “Backward Compatibility”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
Implementation Highlights
Section titled “Implementation Highlights”Lazy Loading Pattern
Section titled “Lazy Loading Pattern”// Pino only loads when first called on serverlet _logger: LogLike | null = null;
const loggerProxy = { get info() { return (_logger ??= createLogger()).info.bind(_logger); }, // ... other methods};Environment Detection
Section titled “Environment Detection”// 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;Fallback Strategy
Section titled “Fallback Strategy”// Prerendering context detected → Simple Pino// Normal server context → Full Pino with transports// Browser context → Console loggingVerification Checklist
Section titled “Verification Checklist”-
packages/utils/src/logger.tshas base Pino implementation -
packages/utils/LOGGER_ARCHITECTURE.mdcreated -
@repo/observability/loggerstill 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
Questions?
Section titled “Questions?”See packages/utils/LOGGER_ARCHITECTURE.md for detailed information on:
- Architecture rationale
- When to use each logger
- Troubleshooting common issues
- Future enhancements