Logger Usage Quick Reference
π― The Rule
Section titled βπ― The Ruleβ| Category | Import From | Reason |
|---|---|---|
| Shared Packages | @repo/utils/logger | Never pull in observability |
| Apps WITH Observability | @repo/observability/logger | Full OTEL support |
| Apps WITHOUT Observability | @repo/utils/logger | Lightweight, no OTEL |
π¦ Shared Packages (Use Base Logger)
Section titled βπ¦ Shared Packages (Use Base Logger)βAlways use @repo/utils/logger in:
Section titled βAlways use @repo/utils/logger in:β- β
packages/auth/ - β
packages/db/ - β
packages/utils/ - β
packages/ai/ - β
packages/crm/ - β
packages/ui/(if logging) - β
packages/observability/(for any app-independent code)
Example:
Section titled βExample:β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 WITH Full Observability
Section titled βπ Apps WITH Full ObservabilityβUse @repo/observability/logger in:
Section titled βUse @repo/observability/logger in:β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 WITHOUT Observability
Section titled βπ οΈ Apps WITHOUT ObservabilityβUse @repo/utils/logger in:
Section titled βUse @repo/utils/logger in:β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
π Migration Checklist
Section titled βπ Migration ChecklistβStep 1: Identify Current Imports
Section titled βStep 1: Identify Current Importsβ# Find all logger importsgrep -r "from '@repo/observability/logger'" packages/*/srcgrep -r "from '@repo/observability/logger'" apps/*/srcStep 2: Update Shared Packages
Section titled βStep 2: Update Shared PackagesβChange all imports in these packages:
// β BEFOREimport { logger } from '@repo/observability/logger';
// β
AFTERimport { logger } from '@repo/utils/logger';Packages to update:
-
packages/auth/ -
packages/db/(if used) -
packages/ai/ -
packages/crm/ -
packages/utils/(internal usages)
Step 3: Verify Apps Still Work
Section titled βStep 3: Verify Apps Still Workβ# TypeScript/JavaScript apps should continue to work unchangedpnpm --filter @erp-unlocked/webapp build
# CRM should now build without OTEL errorspnpm --filter @erp-unlocked/crm buildNote: Python services (pdf-api, pdf-worker) use Python-based testing tools (pytest) and are not tested via pnpm commands.
Step 4: Test Everything
Section titled βStep 4: Test Everythingβpnpm buildpnpm testpnpm lintπ Complete Package Summary
Section titled βπ Complete Package SummaryβBase Packages (use @repo/utils/logger)
Section titled βBase Packages (use @repo/utils/logger)β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.
π Understanding the Architecture
Section titled βπ Understanding the ArchitectureβBase Logger Loads Once, On-Demand
Section titled βBase Logger Loads Once, On-Demandβ// First importimport { logger } from '@repo/utils/logger';
// Pino not loaded yet (lazy)logger.info('Message'); // β Pino loads here
// Next import - reuses same instanceimport { logger as logger2 } from '@repo/utils/logger';logger2.info('Message'); // β Uses cached instanceEnvironment-Specific Behavior
Section titled βEnvironment-Specific Behaviorβimport { logger } from '@repo/utils/logger';
// Node.js serverlogger.info('message'); // β Pino structured log to stdout
// Browser/Clientlogger.info('message'); // β console.log
// Astro build timelogger.info('message'); // β Simple Pino (no transports)
// With OTEL enabled// (if app initializes observability)logger.info('message'); // β Pino + OTEL instrumentationβ οΈ Common Mistakes
Section titled ββ οΈ Common Mistakesββ DONβT: Import observability logger in shared packages
Section titled ββ DONβT: Import observability logger in shared packagesβimport { logger } from '@repo/observability/logger'; // β WRONGProblem: 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β DONβT: Re-export logger with new names
Section titled ββ DONβT: Re-export logger with new namesβexport { logger as crmLogger } from '@repo/observability/logger'; // β WRONGProblem: Defeats the purpose of the architecture
Solution: Re-export base logger if needed
export { logger } from '@repo/utils/logger'; // β
CORRECTβ Best Practices
Section titled ββ Best Practicesβ1. Always import at module level (not in functions)
Section titled β1. Always import at module level (not in functions)β// β
GOODimport { 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');}2. Use child loggers for context
Section titled β2. Use child loggers for contextβ// β
GOOD - adds context to all child logsconst requestLogger = logger.child({ requestId, userId });requestLogger.info('Processing');requestLogger.error('Failed');3. Log structured data, not strings
Section titled β3. Log structured data, not stringsβ// β
GOODlogger.info('User created', { userId, email, timestamp: Date.now() });
// β BADlogger.info(`User ${userId} created at ${new Date()}`);4. Use appropriate log levels
Section titled β4. Use appropriate log levelsβlogger.debug('Detailed trace info'); // β
Developmentlogger.info('Normal operation'); // β
Important eventslogger.warn('Recoverable issue'); // β
Warningslogger.error('Error details', { error }); // β
Failuresπ Related Documentation
Section titled βπ Related Documentationβ- 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).