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/pricing/ - β
packages/integrations/ - β
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
- Lightweight apps (
internal, Cloudflare Workers) can consume packages 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/apps/ts-temporal-worker/
import { logger } from '@repo/observability/logger';
export const POST: APIRoute = async ({ request }) => { logger.info('Processing request'); // β
Includes trace context // ...};Note: Python apps (temporal-worker, dagster) use their own Python-based logging and OpenTelemetry instrumentation (apps/temporal-worker/observability.py, apps/dagster/erp_pipeline/observability.py), not the TypeScript @repo/observability package.
- Gets full OTEL trace context automatically
- Supports
@opentelemetry/api-logs(OTEL_LOGS_EXPORTER=otlp) and@logtail/pinoalongsidestdout - 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/internal/(Cloud Run with_HAS_OTEL=false)- Cloudflare Workers (
apps/email-worker/,apps/ops-mcp/,apps/content-mcp/,apps/partner-data-api/,apps/tidycal-hubspot-sync/) apps/docs/(static site)
import { logger } from '@repo/utils/logger';
logger.info('App started');logger.error('Something failed', error);- No OTEL overhead
- Faster builds and smaller worker bundles
- Simpler dependencies
- Still get structured Pino logging on Node or formatted console output on Workers/browser
π Argument Normalization (Both Call Styles Supported)
Section titled βπ Argument Normalization (Both Call Styles Supported)βStandard Pino expects (mergingObject, message) and silently drops objects passed after the message string. Both @repo/utils/logger (normalizeLogArgs) and @repo/observability/logger (buildOtelLogRecord / toPinoArgs) normalize arguments automatically so both call styles preserve all structured fields:
// β
Message-first (with context object and/or Error in any trailing position)logger.info('Order extracted', { orderId, itemCount: 5 });logger.error('ERP submission failed', err, { orderId, connectionId });logger.error('ERP submission failed', { orderId, connectionId }, err);
// β
Pino-style object-firstlogger.info({ orderId, itemCount: 5 }, 'Order extracted');- Plain objects are merged into top-level structured log attributes.
Errorinstances are automatically extracted (errin@repo/utils/logger;error.messageanderror.stackin@repo/observability/logger).
π Verification Checklist
Section titled βπ Verification 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: Keep Shared Packages on @repo/utils/logger
Section titled βStep 2: Keep Shared Packages on @repo/utils/loggerβ// β BEFOREimport { logger } from '@repo/observability/logger';
// β
AFTERimport { logger } from '@repo/utils/logger';Step 3: Verify Apps Build
Section titled βStep 3: Verify Apps Buildβpnpm --filter webapp buildpnpm --filter internal buildNote: Python services (temporal-worker, dagster, packages/pdf-shared) use pytest and are not tested via pnpm build commands.
π 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βββ pricing/ β β
import from @repo/utils/loggerβββ integrations/ β β
import from @repo/utils/loggerβββ observability/ β β
Enhanced logger layer (OTEL)apps/βββ webapp/ β π import from @repo/observability/logger (full OTEL)βββ ts-temporal-worker/ β π import from @repo/observability/logger (full OTEL)βββ internal/ β π οΈ import from @repo/utils/logger (lightweight)βββ email-worker/ β π οΈ import from @repo/utils/logger (Cloudflare Worker)βββ ops-mcp/ β π οΈ import from @repo/utils/logger (Cloudflare Worker)βββ docs/ β π οΈ import from @repo/utils/logger (static site)π 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)β οΈ 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 in shared code; let the app layer handle OTEL.
β 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');}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 interpolated strings
Section titled β3. Log structured data, not interpolated 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, { context }); // β
Failuresπ Related Documentation
Section titled βπ Related Documentationβ- Logging Configuration - 4-tier logger resolution & argument normalization
- Observability Overview - Platform logging, tracing, and alerting stack
Key Takeaway: Use @repo/utils/logger everywhere by default. Only use @repo/observability/logger in TypeScript/JavaScript apps that explicitly run with OpenTelemetry (webapp, ts-temporal-worker).