alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
250 lines (224 loc) • 8.49 kB
text/typescript
import { $module, type Static, z } from "alepha";
import { $logger } from "./primitives/$logger.ts";
import { CliFormatterProvider } from "./providers/CliFormatterProvider.ts";
import { ConsoleColorProvider } from "./providers/ConsoleColorProvider.ts";
import { ConsoleDestinationProvider } from "./providers/ConsoleDestinationProvider.ts";
import { JsonFormatterProvider } from "./providers/JsonFormatterProvider.ts";
import { LogDestinationProvider } from "./providers/LogDestinationProvider.ts";
import { LogFormatterProvider } from "./providers/LogFormatterProvider.ts";
import { MemoryDestinationProvider } from "./providers/MemoryDestinationProvider.ts";
import { PrettyFormatterProvider } from "./providers/PrettyFormatterProvider.ts";
import { RawFormatterProvider } from "./providers/RawFormatterProvider.ts";
import type { LogEntry } from "./schemas/logEntrySchema.ts";
import { Logger } from "./services/Logger.ts";
// ---------------------------------------------------------------------------------------------------------------------
export * from "./primitives/$logger.ts";
export * from "./providers/CliFormatterProvider.ts";
export * from "./providers/ConsoleColorProvider.ts";
export * from "./providers/ConsoleDestinationProvider.ts";
export * from "./providers/JsonFormatterProvider.ts";
export * from "./providers/LogDestinationProvider.ts";
export * from "./providers/LogFormatterProvider.ts";
export * from "./providers/MemoryDestinationProvider.ts";
export * from "./providers/PrettyFormatterProvider.ts";
export * from "./providers/RawFormatterProvider.ts";
export * from "./schemas/logEntrySchema.ts";
export * from "./services/Logger.ts";
// ---------------------------------------------------------------------------------------------------------------------
/**
* Configurable logging with multiple outputs.
*
* **Features:**
* - Global logger access
* - JSON format
* - Pretty colored output
* - Compact CLI format
* - Raw text format
* - Console destination
* - Memory destination (for devtools)
* - Custom handlers
* - Configuration via `LOG_LEVEL`, `LOG_FORMAT`, and `DEBUG`
*
* @module alepha.logger
*/
export const AlephaLogger = $module({
name: "alepha.logger",
primitives: [$logger],
services: [Logger, ConsoleColorProvider],
variants: [
ConsoleDestinationProvider,
MemoryDestinationProvider,
JsonFormatterProvider,
PrettyFormatterProvider,
RawFormatterProvider,
CliFormatterProvider,
],
register: (alepha) => {
const env = alepha.parseEnv(envSchema);
// Support DEBUG env var (debug package convention) as shorthand for LOG_LEVEL/LOG_FORMAT.
// DEBUG=1 → LOG_LEVEL=trace LOG_FORMAT=pretty
// DEBUG=alepha:* → LOG_LEVEL=alepha.*:debug,info LOG_FORMAT=pretty
let logLevel = env.LOG_LEVEL;
let logFormat = env.LOG_FORMAT;
if (env.DEBUG) {
if (env.DEBUG === "1" || env.DEBUG === "true") {
logLevel ??= "trace";
} else {
const patterns = env.DEBUG.split(",")
.map((p) => p.trim().replaceAll(":", "."))
.filter(Boolean);
logLevel ??= `${patterns.map((p) => `${p}:debug`).join(",")},info`;
}
logFormat ??= "pretty";
}
const getLogDestinationProvider = () => {
// in test mode, if no LOG_LEVEL is set, use MemoryDestinationProvider to capture logs for inspection.
// logs will be printed to console only if the test fails.
if (alepha.isTest() && !logLevel) {
const printOnError = (ev: any) => {
if (ev.task?.result?.state === "fail") {
const output = alepha.inject(MemoryDestinationProvider);
for (const log of output.logs) {
console.log(log.formatted);
}
}
};
try {
alepha.store.get("alepha.test.afterEach")?.(printOnError);
alepha.store.get("alepha.test.onTestFinished")?.(printOnError);
} catch {
// ignore
}
return MemoryDestinationProvider;
}
return ConsoleDestinationProvider;
};
const getLogFormatterProvider = () => {
if (logFormat) {
if (logFormat === "json") {
return JsonFormatterProvider;
}
if (logFormat === "raw") {
return RawFormatterProvider;
}
if (logFormat === "cli") {
return CliFormatterProvider;
}
return PrettyFormatterProvider;
}
if (alepha.isProduction() && !alepha.isBrowser()) {
return JsonFormatterProvider;
}
return PrettyFormatterProvider;
};
alepha.with({
optional: true,
provide: LogDestinationProvider,
use: getLogDestinationProvider(),
});
alepha.with({
optional: true,
provide: LogFormatterProvider,
use: getLogFormatterProvider(),
});
alepha.store.set(
"alepha.logger",
alepha.inject(Logger, {
lifetime: "transient",
args: ["Alepha", "alepha.core"],
}),
);
alepha.store.set(
"alepha.logger.level",
logLevel ??
(alepha.isTest()
? "trace"
: alepha.isProduction() && alepha.isBrowser()
? "warn"
: "info"),
);
},
});
// ---------------------------------------------------------------------------------------------------------------------
const envSchema = z.object({
/**
* Enable debug logging for specific modules using the `debug` package convention.
*
* @example
* DEBUG=1 # Shorthand for LOG_LEVEL=trace LOG_FORMAT=pretty
* DEBUG=alepha:* # Enable debug logging for all alepha modules
* DEBUG=alepha:orm:* # Enable debug logging for alepha.orm modules
* DEBUG=* # Enable debug logging for all modules
*/
DEBUG: z
.text({
description:
"Enable debug logging for specific modules using the debug package convention. Example: DEBUG=alepha:*",
})
.optional(),
/**
* Default log level for the application.
*
* Default by environment:
* - dev = info
* - prod = info
* - test = error
*
* Levels are: "trace" | "debug" | "info" | "warn" | "error" | "silent"
*
* Level can be set for a specific module:
*
* @example
* LOG_LEVEL=my.module.name:debug,info # Set debug level for my.module.name and info for all other modules
* LOG_LEVEL=alepha:trace, info # Set trace level for all alepha modules and info for all other modules
*/
LOG_LEVEL: z
.text({
description: `Application log level on startup.
Levels are: trace, debug, info, warn, error, silent
Level can be set for a specific module:
"my.module.name:debug,info" -> Set debug level for my.module.name and info for all other modules
"alepha:trace,info" -> Set trace level for all alepha modules and info for all other modules`,
lowercase: true,
})
.optional(),
/**
* Built-in log formats.
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
* - "pretty" - Full text format, human-readable, with colors and module/context. {@link PrettyFormatterProvider}
* - "cli" - Compact format for CLI sessions: time, level initial, message, json. {@link CliFormatterProvider}
* - "raw" - Raw format, no formatting, just the message (best for piping). {@link RawFormatterProvider}
*/
LOG_FORMAT: z
.enum(["json", "pretty", "raw", "cli"])
.meta({ lowercase: true })
.describe("Default log format for the application.")
.optional(),
});
// ---------------------------------------------------------------------------------------------------------------------
declare module "alepha" {
export interface Env extends Partial<Static<typeof envSchema>> {}
export interface State {
/**
* Current log level for the application or specific modules.
*/
"alepha.logger.level"?: string;
/**
* Runtime override for the log format (`json` | `pretty` | `raw`).
*
* Unset → the formatter chosen at register time from `LOG_FORMAT`
* (honoring any custom `LogFormatterProvider` substitution) is used.
* When set, it overrides both the active formatter and the CLI's
* dynamic/"stylish" task UI (which is enabled only for `raw`). Set it
* to flip output at runtime — e.g. the CLI `--verbose` flag forces
* `pretty`.
*/
"alepha.logger.format"?: string;
}
export interface Hooks {
log: {
message?: string;
entry: LogEntry;
};
}
}