Packages4 MIN READ114 / 116

Internal module guide. This module is now part of @astrology/logger; use the public package README for application setup.

logger module

@astrology/logger provides package-scoped Consola logging for Node.js workspace consumers. Configure it once in the host application; packages obtain their own tagged logger. Imports produce no output and never replace console or wrap standard streams.

import { configureLogging, getLogger, type LoggingPlugin } from '@astrology/logger';

const applicationLogging: LoggingPlugin = {
  name: 'application-profile',
  configure: ({ environment }) => ({
    level: environment.APP_PROFILE === 'production' ? 'warn' : 'info',
    packages: {
      '@astrology': 'info',
      '@astrology/ephemeris/swiss': 'debug',
      '@astrology/ephemeris-validation': 'warn',
    },
  }),
};

configureLogging({ plugins: [applicationLogging] });
getLogger('@astrology/my-app').info('Application ready', { profile: 'production' });

Configuration follows Spring-style property precedence: defaults → plugins in registration order → explicit configuration → environment. Each layer merges individual package properties; later values replace earlier values for the same property. Reporter arrays replace earlier reporter arrays. Each configureLogging() call starts fresh from defaults, and updates existing logger handles. Invalid levels, malformed maps, or failing plugins throw before changing the active configuration.

The default root level is info. Named thresholds are fatal, error, warn, info, debug, trace, and silent; a threshold permits that severity and more severe messages. Configuration and environment values accept uppercase or lowercase, with Spring-style OFF/off mapping to silent and ALL/all mapping to trace. silent disables every method, including fatal. Each handle exposes its effective canonical .level and .isEnabled('debug') for guarding expensive diagnostic work.

Environment configuration

export LOGGING_LEVEL_ROOT=INFO
export LOGGING_LEVEL_ASTROLOGY_SWISS_EPHEMERIS_ADAPTER=DEBUG
export LOGGING_LEVEL_ASTROLOGY_DRIFT_VALIDATOR=WARN

LOG_LEVEL is a root-level alias; LOGGING_LEVEL_ROOT wins if both exist. Per-package environment keys remove a leading @, replace punctuation with underscores, and uppercase the name. @astrology/ephemeris becomes LOGGING_LEVEL_ASTROLOGY_PROVIDER_ROUTER.

For exact names, including names that collide under relaxed spelling, use a JSON property map:

export LOGGING_LEVEL_PACKAGES='{"@astrology":"warn","@astrology/ephemeris":"debug","@astrology/ephemeris.cache":"error"}'

Namespace rules inherit from the most specific parent: @astrology → @astrology/provider-router → @astrology/provider-router.cache. Siblings such as provider-router-extra do not inherit provider-router. A more specific package rule can be more or less verbose than root; root is a default, not a hard ceiling. At the same namespace, an individual environment key wins over JSON, which wins over explicit/plugin configuration. A specific configured child still overrides a broader environment parent, just as a package logger overrides a root logger in Spring. Prefer JSON when two namespaces normalize to the same environment spelling, and omit the conflicting relaxed key.

Environment values are read on first logger use, then on each explicit configuration call. Changing process.env alone does not alter an existing configuration. configureLogging({ environment: {} }) disables environment overrides for that configuration. Node’s --env-file can load a deployment env file; this package does not search for files automatically.

Plugins and reporters

Plugins are synchronous, named configuration providers. The host can use a plugin to read its existing JSON/YAML configuration, select a deployment profile, or install Consola reporters. The plugin receives a frozen environment snapshot, returns LoggingConfiguration, and can be shared across applications. Hosts choose and invoke plugins explicitly; importing a library never registers application policy.

configureLogging({
  plugins: [applicationLogging],
  packages: { '@astrology/ephemeris': 'debug' },
  reporters: [{
    log(event) {
      // Replace with the host's transport, or emit structured JSON.
      process.stderr.write(JSON.stringify({
        timestamp: event.date.toISOString(),
        level: event.type, package: event.tag, arguments: event.args,
      }) + '\n');
    },
  }],
});

Consola’s default reporter is used when no reporter array is supplied. reporters: [] discards output. Synchronous reporter exceptions are isolated from package operations; asynchronous delivery/retry belongs to the host reporter. Log calls accept a message and contextual arguments; no automatic redaction or remote transport is installed.

Configuration is shared within one process/module instance. new Logging() creates an isolated context with .configure() and .getLogger() for tests or embedded consumers; it does not reconfigure the shared workspace loggers. Cross-process configuration is host-owned. Swiss logs worker lifecycle/results in the parent, so its events use the host’s configured reporters; worker stdout/stderr remain isolated.

Workspace events and verification

Swiss logs operation summaries at debug, explicit capability probes at info, and capacity/provider failures at warn. The canonical normalizer logs summaries at debug and invalid/degraded answers at warn. Routing logs primary decisions at debug and fallback/refusal decisions at warn. Drift validation logs batch starts at debug, clean completions at info, and incomplete/alerting outcomes at warn. Semantics logs aspect-profile summaries at debug and invalid aspect policy at warn. Routine scalar lookups do not emit logs. Full requests, instants, observer coordinates, result geometry, native errors and filesystem paths are omitted from these events. The adapter contract remains free of runtime logging dependencies.

Run pnpm build:logger, pnpm check:logger, and pnpm test:logger from the workspace root. Consumers declare "@astrology/logger": "workspace:*". Root consumer build/test commands build the logger first. Tests exercise actual Consola reporters, isolated/shared configuration, real process environment, and real Swiss integration; no logger implementation is mocked.