Skip to content

Commit

Permalink
More logging cleanup
Browse files Browse the repository at this point in the history
* Cleanup types, simplify, and move into Logging infra file
* Remove unused winston and pino imports
  • Loading branch information
FoxxMD committed Feb 23, 2024
1 parent c011fe2 commit cd8d243
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 141 deletions.
4 changes: 2 additions & 2 deletions src/common/config/ConfigBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import merge from 'deepmerge';
import {Schema} from 'ajv';
import * as AjvNS from 'ajv';
import Ajv from 'ajv';
import {LogLevel} from "../infrastructure/Atomic.js";
import {overwriteMerge} from "../../utils/index.js";
import {AppLogger, getPinoLogger, initPinoLogger} from "../logging.js";
import {AppLogger, initPinoLogger} from "../logging.js";
import {DiscordConfig, GotifyConfig, NtfyConfig, WebhookConfig} from "../infrastructure/webhooks.js";
import path from "path";
import {LogLevel} from "../infrastructure/Logging.js";

export const createAjvFactory = (logger: AppLogger): AjvNS.default => {
const validator = new Ajv.default({logger: logger, verbose: true, strict: "log", allowUnionTypes: true});
Expand Down
51 changes: 0 additions & 51 deletions src/common/infrastructure/Atomic.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,9 @@
import {MESSAGE} from 'triple-beam';
import {Dayjs} from "dayjs";
import {Address4, Address6} from "ip-address";
import {Duration} from "dayjs/plugin/duration.js";
export type LogLevel = "error" | "warn" | "safety" | "info" | "verbose" | "debug" | "silent";
export const logLevels = ['error', 'warn', 'info', 'verbose', 'debug', 'silent'];

export type ConfigFormat = 'yaml';

export interface LogConfig {
level?: string
file?: string | false
console?: string
}

export interface LogOptions {
/**
* Specify the minimum log level for all log outputs without their own level specified.
*
* Defaults to env `LOG_LEVEL` or `info` if not specified.
*
* @default 'info'
* */
level?: LogLevel
/**
* Specify the minimum log level to output to rotating files. If `false` no log files will be created.
* */
file?: LogLevel | false
/**
* Specify the minimum log level streamed to the console (or docker container)
* */
console?: LogLevel
}

export const asLogOptions = (obj: LogConfig = {}): obj is LogOptions => {
return Object.entries(obj).every(([key, val]) => {
if(key !== 'file') {
return val === undefined || logLevels.includes(val.toLocaleLowerCase());
}
return val === undefined || val === false || logLevels.includes(val.toLocaleLowerCase());
});
}

export interface LogInfo extends LogInfoMeta {
message: string
[MESSAGE]: string,
level: string
timestamp: string
transport?: string[]
stack?: string
}

export interface LogInfoMeta {
labels?: string[]
[key: string]: any
}

export interface NamedGroup {
[name: string]: any
}
Expand Down
35 changes: 24 additions & 11 deletions src/common/infrastructure/Logging.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import {LogLevel} from "./Atomic.js";
import {Logger, LoggerExtras} from 'pino';
import {Level, Logger, StreamEntry} from 'pino';

export interface LoggingOptions {
export type AdditionalLevels = "verbose" | "log";
export type AllLevels = Level | AdditionalLevels;
export type LogLevel = AllLevels;
export const logLevels: LogLevel[] = ['fatal', 'error', 'warn', 'info', 'verbose', 'debug'];

export interface LogConfig {
level?: string
file?: string | false
console?: string
}

export interface LogOptions {
/**
* Specify the minimum log level for all log outputs without their own level specified.
*
Expand All @@ -14,21 +24,24 @@ export interface LoggingOptions {
* Specify the minimum log level to output to rotating files. If `false` no log files will be created.
* */
file?: LogLevel | false
/**
* Specify the minimum log level streamed to the UI
* */
stream?: LogLevel
/**
* Specify the minimum log level streamed to the console (or docker container)
* */
console?: LogLevel
}

db?: boolean

discord?: LogLevel
export const asLogOptions = (obj: LogConfig = {}): obj is LogOptions => {
return Object.entries(obj).every(([key, val]) => {
if (key !== 'file') {
return val === undefined || logLevels.includes(val.toLocaleLowerCase());
}
return val === undefined || val === false || logLevels.includes(val.toLocaleLowerCase());
});
}

export type LabelledLogger = Logger<"verbose" | "log"> & {
export type LabelledLogger = Logger<AllLevels> & {
labels?: any[]
addLabel: (value: any) => void
}

export type AllLevelStreamEntry = StreamEntry<AllLevels>
4 changes: 2 additions & 2 deletions src/common/infrastructure/OperatorConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import YamlConfigDocument from "../config/YamlConfigDocument.js";
import {LoggingOptions} from "./Logging.js";
import {LogOptions} from "./Logging.js";
import {WebhookConfig} from "./webhooks.js";

export class YamlOperatorConfigDocument extends YamlConfigDocument<OperatorConfig> {
Expand All @@ -20,7 +20,7 @@ export interface OperatorConfig extends OperatorJsonConfig {


export interface OperatorJsonConfig {
logging?: LoggingOptions,
logging?: LogOptions,
notifiers: WebhookConfig[]
endlessDir?: string
mapquestKey?: string
Expand Down
95 changes: 21 additions & 74 deletions src/common/logging.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import path from "path";
import {configDir} from "./index.js";
import {asLogOptions, LogConfig, LogLevel, LogOptions} from "./infrastructure/Atomic.js";
import process from "process";
import {ErrorWithCause} from "pony-cause";
import dayjs from "dayjs";
import {fileOrDirectoryIsWriteable} from "../utils/io.js";
import {LabelledLogger} from "./infrastructure/Logging.js";
import {
AllLevelStreamEntry,
asLogOptions,
LabelledLogger,
LogConfig,
LogLevel,
LogOptions
} from "./infrastructure/Logging.js";
import {
pino,
TransportTargetOptions,
Level, StreamEntry,
LevelWithSilentOrString,
} from 'pino';
import pRoll from 'pino-roll';
import prettyDef, {PrettyOptions, PinoPretty, colorizerFactory} from 'pino-pretty';
Expand All @@ -24,8 +29,6 @@ if (typeof process.env.CONFIG_DIR === 'string') {
export type AppLogger = LabelledLogger

const CWD = process.cwd();
export const pinoLoggers: Map<string, LabelledLogger> = new Map();

const prettyOptsFactory = (opts: PrettyOptions = {}) => {
const {colorize} = opts;
const colorizeOpts: undefined | {useColor: boolean} = colorize === undefined ? undefined : {useColor: colorize};
Expand Down Expand Up @@ -76,78 +79,22 @@ const buildParsedLogOptions = (config: LogConfig = {}): Required<LogOptions> =>
}

const {level: configLevel} = config;
const defaultLevel = process.env.LOG_LEVEL || 'info';
const envLevel = process.env.LOG_LEVEL as LogLevel | undefined;
const defaultLevel = envLevel ?? 'info';
const {
level = configLevel || defaultLevel,
file = configLevel || defaultLevel,
console = configLevel || 'debug'
} = config;

return {
level: level as LogLevel,
level,
file: file as LogLevel | false,
console
};
}

export const getPinoLogger = async (config: LogConfig = {}, name = 'App'): Promise<LabelledLogger> => {

if(pinoLoggers.has(name)) {
return pinoLoggers.get(name);
}

const errors: (Error | string)[] = [];

let options: LogOptions = {};
if (asLogOptions(config)) {
options = config;
} else {
errors.push(`Logging levels were not valid. Must be one of: 'error', 'warn', 'info', 'verbose', 'debug', 'silent' -- 'file' may be false.`);
}

const {level: configLevel} = options;
const defaultLevel = process.env.LOG_LEVEL || 'info';
const {
level = configLevel || defaultLevel,
file = configLevel || defaultLevel,
console = configLevel || 'debug'
} = options;

const streams: StreamEntry[] = [
{
level: configLevel as Level,
stream: prettyDef.default({...prettyConsole, destination: 1, sync: true})
}
]

if(file !== false) {
try {
fileOrDirectoryIsWriteable(logPath);
const rollingDest = await pRoll({
file: path.resolve(logPath, 'app'),
size: 10,
frequency: 'daily',
get extension() {return `-${dayjs().format('YYYY-MM-DD')}.log`},// '.log',
mkdir: true,
sync: false,
});

streams.push({
level: file as Level,
stream: prettyDef.default({...prettyFile, destination: rollingDest})
})
} catch (e: any) {
const msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory';
errors.push(new ErrorWithCause<Error>(msg, {cause: e as Error}));
}
}

const plogger = buildPinoLogger(level as Level, streams);
pinoLoggers.set(name, plogger);
return plogger;
}

const buildPinoFileStream = async (options: Required<LogOptions>): Promise<StreamEntry | undefined> => {
const buildPinoFileStream = async (options: Required<LogOptions>): Promise<AllLevelStreamEntry | undefined> => {
const {file} = options;
if(file === false) {
return undefined;
Expand All @@ -159,28 +106,28 @@ const buildPinoFileStream = async (options: Required<LogOptions>): Promise<Strea
file: path.resolve(logPath, 'app'),
size: 10,
frequency: 'daily',
get extension() {return `-${dayjs().format('YYYY-MM-DD')}.log`},// '.log',
get extension() {return `-${dayjs().format('YYYY-MM-DD')}.log`},
mkdir: true,
sync: false,
});

return {
level: file as Level,
level: file as LogLevel,
stream: prettyDef.default({...prettyFile, destination: rollingDest})
};
} catch (e: any) {
throw new ErrorWithCause<Error>('WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory', {cause: e as Error});
}
}

const buildPinoConsoleStream = (options: Required<LogOptions>): StreamEntry => {
const buildPinoConsoleStream = (options: Required<LogOptions>): AllLevelStreamEntry => {
return {
level: options.console as Level,
level: options.console as LogLevel,
stream: prettyDef.default({...prettyConsole, destination: 1, sync: true})
}
}

const buildPinoLogger = (defaultLevel: Level, streams: StreamEntry[]): LabelledLogger => {
const buildPinoLogger = (defaultLevel: LevelWithSilentOrString, streams: AllLevelStreamEntry[]): LabelledLogger => {
const plogger = pino({
// @ts-ignore
mixin: (obj, num, loggerThis) => {
Expand All @@ -205,15 +152,15 @@ const buildPinoLogger = (defaultLevel: Level, streams: StreamEntry[]): LabelledL
return plogger;
}

export const testPinoLogger = buildPinoLogger(('silent' as Level), [buildPinoConsoleStream(buildParsedLogOptions({level: 'silent'}))]);
export const testPinoLogger = buildPinoLogger('silent', [buildPinoConsoleStream(buildParsedLogOptions({level: 'debug'}))]);

export const initPinoLogger = buildPinoLogger(('debug' as Level), [buildPinoConsoleStream(buildParsedLogOptions({level: 'debug'}))]);
export const initPinoLogger = buildPinoLogger('debug', [buildPinoConsoleStream(buildParsedLogOptions({level: 'debug'}))]);

export const appPinoLogger = async (config: LogConfig = {}, name = 'App') => {
const options = buildParsedLogOptions(config);
const stream = buildPinoConsoleStream(options);
const file = await buildPinoFileStream(options);
return buildPinoLogger('debug' as Level, [stream, file]);
return buildPinoLogger('debug' as LevelWithSilentOrString, [stream, file]);
}

export const createChildPinoLogger = (parent: LabelledLogger, labelsVal: any | any[] = [], context: object = {}, options = {}) => {
Expand Down
1 change: 0 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {Duration} from "dayjs/plugin/duration.js";
import {ErrorWithCause, getErrorCause} from "pony-cause";
import InvalidRegexError from "../common/errors/InvalidRegexError.js";
import {Address4, Address6} from "ip-address";
import {format} from "logform";

export const overwriteMerge = (destinationArray: any[], sourceArray: any[], options: any): any[] => sourceArray;

Expand Down

0 comments on commit cd8d243

Please sign in to comment.