import { EventBus } from "../events/events"; import { logLevels } from "./constants"; import type { CoreLogContext, LogEntry, LogMetadata } from "./log-entry"; import { CoreLog } from "./log-entry"; import type { Writer } from "./writers/base"; export type LoggerType = "quiet" | "default" | "basic" | "json" | "ink"; export type LogLevel = (typeof logLevels)[number]; export declare function parseLogLevel(level: string): LogLevel; export declare const eventLogLevel: LogLevel; /** * Return the logger type, depending on what command line args are used. */ export declare function getTerminalWriterType({ silent, output, loggerType, }: { silent: boolean; output: boolean; loggerType: LoggerType | null; }): LoggerType; export declare function getTerminalWriterInstance(loggerType: LoggerType, level: LogLevel): Writer; export interface LoggerConfigBase { /** * The Garden log level. This get propagated to the actual writers which have their own * levels which may in some cases overwrite this. */ level: LogLevel; /** * Whether or not the log entries are stored in-memory on the logger instance. Useful for testing. */ storeEntries?: boolean; showTimestamps?: boolean; useEmoji?: boolean; } interface CreateLogParams { metadata?: LogMetadata; fixLevel?: LogLevel; /** * The name of the log context. Will be printed as the "section" part of the log lines * belonging to this context. */ name?: string; } interface LoggerWriters { display: Writer; file: Writer[]; } export interface Logger extends Required { events: EventBus; createLog(params?: CreateLogParams): CoreLog; log(entry: LogEntry): void; getLogEntries(): LogEntry[]; getWriters(): LoggerWriters; } interface LoggerInitParams extends LoggerConfigBase { /** * The type of display writer to use. This is configurable by the user * and exposed as a "logger type" which is a bit more user friendly. * * The logger also has a set of file writers that are set internally. */ displayWriterType: LoggerType; force?: boolean; } /** * What follows is a fairly lengthy code comment on how logging in Garden works. As such it's * liable to go out of date so don't hesitate to update this if you see anything wrong. * * --- * * There's a singleton "root" Logger instance which is the overall logs manager. It holds the "global" * log config for the given Garden run, including what writers are used. When a log line is written, * the root Logger calls the registered writers and optionally stores the entry in memory which is useful for testing. * * The writers in turn call the renderers which are just helper functions for printing log lines * for different contexts. * * The root Logger also creates the first Log instance which is what we pass around for writing * logs. * * The Log instance itself contains the methods for writing logs (log.info, log.silly, etc) * as well as some config and context that gets passed to the log entry proper. * * The Log instances are therefore responsible for holding log config for multiple entries * over some period of execution. * * That is: * - Consumer calls log.info() which creates a log entry which inherits the log's context. * - The log then calls the root logger with the entry which calls the writers which render the entry. * * There are different Log classes, e.g. CoreLog and ActionLog which is to ensure a given log entry * has the correct context in a type safe manner. * * Usage example: * * const firstLog = rootLogger.createLog({ name: "garden" }) // You could also do 'new CoreLog({ root })', createLog is just for convenience * firstLog.info("Getting started...") // Prints: ℹ garden → Getting started... * * const graphLog = firstLog.createLog({ name: "graph" }) // Again you can also do 'new CoreLog({ ... })' * graphLog.info("Resolving actions") // ℹ graph → Resolving actions... * * actionLog = new ActionLog({ actionName: "api" }) * actionLog.info("hello") // ℹ build.api → hello * * Some invariants: * You can't overwrite the action name for an ActionLog. * You can overwrite the name of a CoreLog. * * Other notes: * - The Log instances may apply some styling depending on the context. In general you should * not have to overwrite this and simply default to calling e.g. log.warn("oh noes") * as opposed to log.warn({ msg: styles.warning("oh noes"), symbol: "warning" }) * - A Log instance contains all it's parent Log configs so conceptually we can rebuild * the entire log graph, e.g. for testing. We're not using this as of writing. */ export declare abstract class LoggerBase implements Logger { events: EventBus; useEmoji: boolean; showTimestamps: boolean; level: LogLevel; entries: LogEntry[]; storeEntries: boolean; protected abstract writers: LoggerWriters; constructor(config: LoggerConfigBase); toSanitizedValue(): string; getWriters(): LoggerWriters; log(entry: LogEntry): void; /** * Creates a new CoreLog context from the root Logger. */ createLog({ metadata, fixLevel, name, origin, context, }?: { metadata?: LogMetadata; fixLevel?: LogLevel; /** * The name of the log context. Will be printed as the "section" part of the log lines * belonging to this context. */ name?: string; origin?: string; context?: Partial; }): CoreLog; /** * Returns all log entries. Throws if storeEntries=false * * @throws(CoreCrashed) */ getLogEntries(): LogEntry[]; /** * Returns latest log entry. Throws if storeEntries=false * * @throws(CoreCrashed) */ getLatestEntry(): LogEntry | undefined; } interface RootLoggerParams extends LoggerConfigBase { writers: LoggerWriters; } /** * The "root" Logger. Responsible for calling the log writers on log events * and holds the command-wide log configuration. * * Is initialized as a singleton class. * * Note that this class does not have methods for logging at different levels. Rather * that's handled by the 'Log' class which in turns calls the root Logger. */ export declare class RootLogger extends LoggerBase { private static instance?; protected writers: LoggerWriters; private constructor(); /** * Returns the already initialized Logger singleton instance. * * Throws and error if called before logger is initialized. * * @throws(CoreCrashed) */ static getInstance(): RootLogger; /** * Initializes the logger as a singleton from config. Also ensures that the logger settings make sense * in the context of environment variables and writer types. */ static initialize(config: LoggerInitParams): RootLogger; /** * Clears the singleton instance. Use this if you need to re-initialise the global logger singleton. */ static clearInstance(): void; addFileWriter(writer: Writer): void; /** * Reset the default terminal writer that the logger was initialized with. * * This is required because when we initialize the logger we don't know what writer * the command may require and we need to re-set it when we've resolved the command. */ setTerminalWriter(type: LoggerType): void; /** * WARNING: Only use for tests. * * The logger is a singleton which makes it hard to test. This is an escape hatch. */ static _createInstanceForTests(params: RootLoggerParams): RootLogger; } export declare function getRootLogger(): RootLogger; interface ServerLoggerParams extends LoggerConfigBase { defaultOrigin?: string; rootLogger: Logger; terminalLevel?: LogLevel; } export interface CreateEventLogParams extends CreateLogParams { origin: string; } /** * A Logger class for handling server requests. * * It writes entries to stdout at the silly level via the "main" root logger for the respective * Garden instance but emits log entry events at their regular level. This basically ensures * command logs for server requests are emitted but do not pollute the terminal. */ export declare class ServerLogger extends LoggerBase { protected writers: LoggerWriters; private rootLogger; /** * The log level to use for terminal output. Defaults to silly. */ private terminalLevel; constructor(config: ServerLoggerParams); log(entry: LogEntry): void; } export declare class VoidLogger extends LoggerBase { protected writers: LoggerWriters; log(): void; } export {};