Skip to content

Commit

Permalink
feat: Enable setting default log path with extras
Browse files Browse the repository at this point in the history
  • Loading branch information
FoxxMD committed Mar 28, 2024
1 parent 8fcf116 commit d38e328
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 34 deletions.
24 changes: 18 additions & 6 deletions src/funcs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import process from "process";
import {FileLogOptions, FileLogOptionsParsed, LOG_LEVEL_NAMES, LogLevel, LogOptions, LogOptionsParsed} from "./types.js";
import {
FileLogOptions,
FileLogOptionsParsed,
FileLogPathOptions,
LOG_LEVEL_NAMES,
LogLevel,
LogOptions,
LogOptionsParsed
} from "./types.js";
import {projectDir, logPathRelative} from "./constants.js";
import {isAbsolute, resolve} from 'node:path';

Expand Down Expand Up @@ -36,7 +44,7 @@ const isFileLogOptions = (obj: any): obj is FileLogOptions => {
/**
* Takes an object and parses it into a fully-populated LogOptions object based on opinionated defaults
* */
export const parseLogOptions = (config: LogOptions = {}, baseDir?: string): LogOptionsParsed => {
export const parseLogOptions = (config: LogOptions = {}, options?: FileLogPathOptions): LogOptionsParsed => {
if (!isLogOptions(config)) {
throw new Error(`Logging levels were not valid. Must be one of: 'silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', -- 'file' may be false.`)
}
Expand All @@ -55,7 +63,7 @@ export const parseLogOptions = (config: LogOptions = {}, baseDir?: string): LogO
if (file.level === false) {
fileObj = {level: false};
} else {
const path = typeof file.path === 'function' ? file.path : getLogPath(file.path, baseDir);
const path = typeof file.path === 'function' ? file.path : getLogPath(file.path, options);
fileObj = {
level: configLevel || defaultLevel,
...file,
Expand All @@ -67,7 +75,7 @@ export const parseLogOptions = (config: LogOptions = {}, baseDir?: string): LogO
} else {
fileObj = {
level: file,
path: getLogPath(undefined, baseDir)
path: getLogPath(undefined, options)
};
}

Expand All @@ -83,14 +91,18 @@ export const parseLogOptions = (config: LogOptions = {}, baseDir?: string): LogO
};
}

export const getLogPath = (path?: string, baseDir = projectDir) => {
export const getLogPath = (path?: string, options: FileLogPathOptions = {}) => {
const {
logBaseDir: baseDir = projectDir,
logDefaultPath = logPathRelative
} = options;
let pathVal: string;
if (path !== undefined) {
pathVal = path;
} else if (typeof process.env.LOG_PATH === 'string') {
pathVal = process.env.LOG_PATH;
} else {
pathVal = logPathRelative;
pathVal = logDefaultPath;
}

if (isAbsolute(pathVal)) {
Expand Down
6 changes: 2 additions & 4 deletions src/loggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,9 @@ export const loggerApp = (config: LogOptions | object = {}, extras?: LoggerAppEx
pretty = {},
destinations = [],
pino,
logBaseDir
} = extras || {};

const options = parseLogOptions(config, logBaseDir);
const options = parseLogOptions(config, extras);
const streams: LogLevelStreamEntry[] = [
buildDestinationStdout(options.console, pretty),
...destinations
Expand Down Expand Up @@ -135,10 +134,9 @@ export const loggerAppRolling = async (config: LogOptions | object = {}, extras?
pretty = {},
destinations = [],
pino,
logBaseDir
} = extras || {};

const options = parseLogOptions(config, logBaseDir);
const options = parseLogOptions(config, extras);
const streams: LogLevelStreamEntry[] = [
buildDestinationStdout(options.console, pretty),
...destinations
Expand Down
32 changes: 22 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,31 @@ export type JsonPrettyDestination = StreamDestination & {

export type LogOptionsParsed = Omit<Required<LogOptions>, 'file'> & { file: FileLogOptionsParsed }

export interface FileLogPathOptions {
/**
* The base path to use when parsing file logging options.
*
* @see FileOptions
*
* @default 'CWD'
* */
logBaseDir?: string
/**
* The default path to use when parsing file logging options.
*
* If this path is relative it is joined with `logBaseDir`
*
* @see FileOptions
*
* @default './logs/app.log'
* */
logDefaultPath?: string
}

/**
* Additional settings and Pino Transports to apply to the returned Logger.
* */
export interface LoggerAppExtras {
export interface LoggerAppExtras extends FileLogPathOptions {
/**
* Additional pino-pretty options that are applied to the built-in console/log streams
* */
Expand All @@ -238,15 +259,6 @@ export interface LoggerAppExtras {
* Additional [Pino Log options](https://getpino.io/#/docs/api?id=options) that are passed to `pino()` on logger creation
* */
pino?: PinoLoggerOptions

/**
* The base path to use when parsing file logging options.
*
* @see FileOptions
*
* @default 'CWD'
* */
logBaseDir?: string
}

/**
Expand Down
124 changes: 110 additions & 14 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {PassThrough, Transform} from "node:stream";
import chai, {expect} from "chai";
import {pEvent} from 'p-event';
import {sleep} from "../src/util.js";
import {LogData, LOG_LEVEL_NAMES, PRETTY_ISO8601} from "../src/types.js";
import { LogData, LOG_LEVEL_NAMES, PRETTY_ISO8601, FileLogPathOptions } from "../src/types.js";
import withLocalTmpDir from 'with-local-tmp-dir';
import {readdirSync,} from 'node:fs';
import {
Expand All @@ -29,7 +29,7 @@ const dateFormat = dateFormatDef as unknown as typeof dateFormatDef.default;


const testConsoleLogger = (config?: object, colorize = false): [Logger, Transform, Transform] => {
const opts = parseLogOptions(config, process.cwd());
const opts = parseLogOptions(config, {logBaseDir: process.cwd()});
const testStream = new PassThrough();
const rawStream = new PassThrough();
const logger = buildLogger('debug', [
Expand All @@ -50,7 +50,7 @@ const testConsoleLogger = (config?: object, colorize = false): [Logger, Transfor
}

const testObjectLogger = (config?: object, object?: boolean): [Logger, Transform, Transform] => {
const opts = parseLogOptions(config, process.cwd());
const opts = parseLogOptions(config, {logBaseDir: process.cwd()});
const testStream = new PassThrough({objectMode: true});
const rawStream = new PassThrough();
const logger = buildLogger('debug', [
Expand All @@ -71,8 +71,12 @@ const testObjectLogger = (config?: object, object?: boolean): [Logger, Transform
return [logger, testStream, rawStream];
}

const testFileRollingLogger = async (config?: object, logBaseDir = process.cwd()) => {
const opts = parseLogOptions(config, logBaseDir);
const testFileRollingLogger = async (config?: object, options: FileLogPathOptions = {}) => {
const fileOpts: FileLogPathOptions = {
logBaseDir: process.cwd(),
...options
}
const opts = parseLogOptions(config, fileOpts);
const {
file: {
level,
Expand All @@ -95,8 +99,12 @@ const testFileRollingLogger = async (config?: object, logBaseDir = process.cwd()
]);
};

const testFileLogger = async (config?: object, logBaseDir = process.cwd()) => {
const opts = parseLogOptions(config, logBaseDir);
const testFileLogger = async (config?: object, options: FileLogPathOptions = {}) => {
const fileOpts: FileLogPathOptions = {
logBaseDir: process.cwd(),
...options
}
const opts = parseLogOptions(config, fileOpts);
const {
file: {
path: logPath,
Expand All @@ -118,8 +126,8 @@ const testFileLogger = async (config?: object, logBaseDir = process.cwd()) => {
};

const testRollingAppLogger = async (config: LogOptions | object = {}, extras: LoggerAppExtras = {}): Promise<[Logger, Transform, Transform]> => {
const {destinations = [], pretty, logBaseDir = process.cwd(), ...restExtras} = extras;
const opts = parseLogOptions(config, logBaseDir);
const {destinations = [], pretty, logBaseDir, ...restExtras} = extras;
const opts = parseLogOptions(config, {logBaseDir: process.cwd(), ...extras});
const testStream = new PassThrough();
const rawStream = new PassThrough();
const streams: LogLevelStreamEntry[] = [
Expand All @@ -136,14 +144,13 @@ const testRollingAppLogger = async (config: LogOptions | object = {}, extras: Lo
stream: rawStream
}
];
const logger = await loggerAppRolling({...config, console: 'silent'}, {destinations: [...destinations, ...streams], pretty, logBaseDir, ...restExtras});
const logger = await loggerAppRolling({...opts, console: 'silent'}, {destinations: [...destinations, ...streams], pretty, logBaseDir, ...restExtras});
return [logger, testStream, rawStream];
}

const testAppLogger = (config: LogOptions | object = {}, extras: LoggerAppExtras = {}): [Logger, Transform, Transform] => {
const {destinations = [], pretty = {}, logBaseDir = process.cwd(), ...restExtras} = extras;

const opts = parseLogOptions(config, logBaseDir);
const {destinations = [], pretty = {}, logBaseDir, ...restExtras} = extras;
const opts = parseLogOptions(config, {logBaseDir: process.cwd(), ...extras});
const testStream = new PassThrough();
const rawStream = new PassThrough();

Expand All @@ -163,7 +170,7 @@ const testAppLogger = (config: LogOptions | object = {}, extras: LoggerAppExtras
stream: rawStream
},
];
const logger = loggerApp({...config, console: 'silent'}, {destinations: [...destinations, ...streams], pretty, logBaseDir, ...restExtras});
const logger = loggerApp({...opts, console: 'silent'}, {destinations: [...destinations, ...streams], pretty, logBaseDir, ...restExtras});
return [logger, testStream, rawStream];
}

Expand Down Expand Up @@ -218,6 +225,79 @@ describe('Config Parsing', function () {
expect(config.console).eq('warn')
expect(config.file.level).eq('error')
});

describe('Log File Options', function() {

it(`uses CWD for base path when none is specified`, async function () {
const config = parseLogOptions({
level: 'debug'
});
expect(config.file.path).includes(process.cwd())
});

it(`uses user-specified base path when specified`, async function () {
await withLocalTmpDir(async () => {
const config = parseLogOptions({
level: 'debug'
}, {logBaseDir: process.cwd()});
expect(config.file.path).includes(process.cwd())
}, {unsafeCleanup: false});
});

it(`uses 'logs/app.log' for default log path when none is specified`, async function () {
const config = parseLogOptions({
level: 'debug'
});
expect(config.file.path).includes('logs/app.log')
});

it(`uses user-specified default log path when none is specified`, async function () {
const config = parseLogOptions({
level: 'debug',
}, {logDefaultPath: 'logs/myApp.log'});
expect(config.file.path).includes('logs/myApp.log')
});

it(`uses config-specified absolute path`, async function () {
const specificPath = '/my/absolute/path/app.log';
const config = parseLogOptions({
level: 'debug',
file: {
path: specificPath
}
});
expect(config.file.path).eq(specificPath)
});

it(`uses config-specified relative path with base path`, async function () {
const relativePath = './my/relative/path/app.log';
const config = parseLogOptions({
level: 'debug',
file: {
path: relativePath
}
});
expect(config.file.path).eq(path.join(process.cwd(), relativePath));

const configWithDefault = parseLogOptions({
level: 'debug',
file: {
path: relativePath
}
}, {logDefaultPath: 'logs/myApp.log'});
expect(configWithDefault.file.path).eq(path.join(process.cwd(), relativePath));
});

it(`uses ENV-specified path`, async function () {
const specificPath = '/my/absolute/path/app.log';
process.env.LOG_PATH = specificPath;
const config = parseLogOptions({
level: 'debug',
});
delete process.env.LOG_PATH;
expect(config.file.path).eq(specificPath)
});
});
})

describe('Transports', function () {
Expand Down Expand Up @@ -464,6 +544,22 @@ describe('Transports', function () {
expect(paths.length).eq(1);
}, {unsafeCleanup: true});
});

it('It writes to file with a different default log path', async function () {
await withLocalTmpDir(async () => {
const [logger, testStream, rawStream] = testAppLogger({file: 'debug'}, {logDefaultPath: './myApp.log'});
const race = Promise.race([
pEvent(testStream, 'data'),
sleep(10)
]) as Promise<Buffer>;
logger.debug('Test');
await sleep(20);
const res = await race;
const paths = readdirSync('.');
expect(paths.length).eq(1);
expect(paths[0]).includes('myApp.log');
}, {unsafeCleanup: true});
});
});
});

Expand Down

0 comments on commit d38e328

Please sign in to comment.