Skip to content

Commit

Permalink
feat(logger): format logs
Browse files Browse the repository at this point in the history
  • Loading branch information
yjl9903 committed Aug 13, 2023
1 parent ff49496 commit b45d132
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 67 deletions.
7 changes: 5 additions & 2 deletions packages/logger/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { hasTTY, isDebug, isTest, isCI } from 'std-env';
import { BasicReporter } from './reporters/basic';
import { FancyReporter } from './reporters/fancy';

import type { LoggerOptions } from './types';

import { LogLevels } from './level';
import { BreadcLogger, LoggerOptions } from './logger';
import { BreadcLogger, BreadcLoggerInstance } from './logger';

export * from './level';
export * from './types';
export * from './logger';
export * from './reporters';

export const Logger = (
options: Partial<LoggerOptions> & { fancy?: boolean } = {}
) => {
): BreadcLoggerInstance => {
const level = getDefaultLogLevel();
const isFancy =
options.fancy === true || (options.fancy === undefined && hasTTY);
Expand Down
79 changes: 52 additions & 27 deletions packages/logger/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import type { Reporter, FormatOptions, InputLogObject } from './types';
import type {
InputLogItem,
InputLogObject,
LogObject,
LoggerOptions
} from './types';

import { LogLevel } from './level';

export interface LoggerPlugin {}

export interface LoggerOptions {
reporter: Reporter[];
level: LogLevel;
format: FormatOptions;
stdout?: NodeJS.WriteStream;
stderr?: NodeJS.WriteStream;
plugins: LoggerPlugin[];
}
import { LogLevels } from './level';

export class BreadcLogger {
readonly options: LoggerOptions;
Expand All @@ -29,26 +23,57 @@ export class BreadcLogger {
}

// --- Log ---
private print(defaults: InputLogObject, message: string, args: any[]) {
private shouldPrint(obj: LogObject) {
return obj.level <= this.level;
}

private print(defaults: InputLogObject, input: InputLogObject) {
const date = new Date();
for (const reporter of this.options.reporter) {
reporter.print({
...defaults,
level: 0,
type: 'info',
date,
message,
args
});
const obj: LogObject = {
level: LogLevels['log'],
type: 'log',
date,
...defaults,
...input
};
if (this.shouldPrint(obj)) {
for (const reporter of this.options.reporter) {
reporter.print(obj, { options: this.options });
}
}
}

private resolveInput(input: InputLogItem, args: any[]) {
if (typeof input === 'string') {
return { message: input, args };
} else if (typeof input === 'number') {
return { message: String(input), args };
} else {
if ('level' in input) {
delete input['level'];
}
if ('type' in input) {
delete input['type'];
}
if (Array.isArray(input.args)) {
input.args.push(...args);
}
return input;
}
}

public log(message: string, ...args: any[]) {
this.print({}, message, args);
public log(input: InputLogItem, ...args: any[]) {
const type = 'log';
const level = LogLevels[type];
const defaults: InputLogObject = { type, level };
this.print(defaults, this.resolveInput(input, args));
}

public info(message: string, ...args: any[]) {
this.print({}, message, args);
public info(input: InputLogItem, ...args: any[]) {
const type = 'info';
const level = LogLevels[type];
const defaults: InputLogObject = { type, level };
this.print(defaults, this.resolveInput(input, args));
}
}

Expand Down
41 changes: 36 additions & 5 deletions packages/logger/src/reporters/basic.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
import type { Reporter } from '../types';
import { formatWithOptions } from 'node:util';

export const BasicReporter = () => {
return <Reporter>{
print(obj) {
console.log(obj.message);
import type { LogObject, PrintContext, FormatOptions } from '../types';

import { LogLevels } from '../level';
import { bracket } from '../utils/format';
import { writeStream } from '../utils/stream';

import type { FormatReporter } from './types';

export const BasicReporter = (): FormatReporter => {
return {
formatArgs(opts: FormatOptions, message?: string, args: any[] = []) {
return formatWithOptions(opts, message, ...args);
},
formatLogObject(obj: LogObject, ctx: PrintContext) {
const message = this.formatArgs(
ctx.options.format,
obj.message,
obj.args
);

return [
bracket(obj.type === 'log' ? undefined : obj.type),
bracket(obj.tag),
message
]
.filter(Boolean)
.join(' ');
},
print(obj, ctx) {
const message = this.formatLogObject(obj, ctx);
const stream =
obj.level < LogLevels.log
? ctx.options.stderr || process.stderr
: ctx.options.stdout || process.stdout;
writeStream(message, stream);
}
};
};
39 changes: 34 additions & 5 deletions packages/logger/src/reporters/fancy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
import type { Reporter } from '../types';
import { formatWithOptions } from 'node:util';

export const FancyReporter = () => {
return <Reporter>{
print(obj) {
console.log(obj.message);
import { LogLevels } from '../level';
import { bracket } from '../utils/format';
import { writeStream } from '../utils/stream';

import type { FormatReporter } from './types';

export const FancyReporter = (): FormatReporter => {
return {
formatArgs(opts, message?: string, args: any[] = []) {
return formatWithOptions(opts, message, ...args);
},
formatLogObject(obj, ctx) {
const message = this.formatArgs(
ctx.options.format,
obj.message,
obj.args
);

return [
bracket(obj.type === 'log' ? undefined : obj.type),
bracket(obj.tag),
message
]
.filter(Boolean)
.join(' ');
},
print(obj, ctx) {
const message = this.formatLogObject(obj, ctx);
const stream =
obj.level < LogLevels.log
? ctx.options.stderr || process.stderr
: ctx.options.stdout || process.stdout;
writeStream(message, stream);
}
};
};
17 changes: 13 additions & 4 deletions packages/logger/src/reporters/mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type { LogObject, Reporter } from '../types';

import type { FormatReporter } from './types';

interface HistoryLog {
readonly output: string;

readonly object: LogObject;
}

export const MockReporter = (
history: LogObject[] = []
): Reporter & { history: LogObject[] } => {
reporter: FormatReporter,
history: HistoryLog[] = []
): Reporter & { history: HistoryLog[] } => {
return {
history,
print(obj) {
history.push(obj);
print(object, ctx) {
history.push({ output: reporter.formatLogObject(object, ctx), object });
}
};
};
12 changes: 12 additions & 0 deletions packages/logger/src/reporters/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {
Reporter,
LogObject,
PrintContext,
FormatOptions
} from '../types';

export interface FormatReporter extends Reporter {
formatArgs(opts: FormatOptions, message?: string, args?: any[]): string;

formatLogObject(obj: LogObject, ctx: PrintContext): string;
}
22 changes: 20 additions & 2 deletions packages/logger/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { LogLevel, LogType } from './level';

export interface LoggerPlugin {}

export interface LoggerOptions {
reporter: Reporter[];
level: LogLevel;
format: FormatOptions;
stdout?: NodeJS.WriteStream;
stderr?: NodeJS.WriteStream;
plugins: LoggerPlugin[];
}

export interface InputLogObject {
level?: LogLevel;
type?: LogType;
Expand All @@ -16,10 +27,17 @@ export interface LogObject extends InputLogObject {
date: Date;
}

export type InputLogItem = string | number | InputLogObject;
export type InputLogItem =
| string
| number
| Omit<InputLogObject, 'level' | 'type'>;

export interface PrintContext {
options: LoggerOptions;
}

export interface Reporter {
print: (log: LogObject) => void;
print: (log: LogObject, ctx: PrintContext) => void;
}

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/logger/src/utils/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const bracket = (text?: string) => {
if (text) {
return `[${text}]`;
} else {
return undefined;
}
};
3 changes: 3 additions & 0 deletions packages/logger/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './format';

export * from './stream';
4 changes: 4 additions & 0 deletions packages/logger/src/utils/stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function writeStream(data: any, stream: NodeJS.WriteStream) {
const write = (stream as any).__write || stream.write;
return write.call(stream, data);
}
43 changes: 21 additions & 22 deletions packages/logger/test/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { describe, expect, it } from 'vitest';

import { Logger, MockReporter } from '../src';
import { Logger, LogLevels, BasicReporter, MockReporter } from '../src';

describe('Basic Logger', () => {
it('should work', () => {
const reporter = MockReporter();
const logger = Logger({ reporter: [reporter] });
const reporter = MockReporter(BasicReporter());
const logger = Logger({ level: LogLevels.verbose, reporter: [reporter] });

logger.log('Hello');
logger.log('World');
expect(reporter.history.map((obj) => ({ ...obj, date: undefined })))
.toMatchInlineSnapshot(`
[
{
"args": [],
"date": undefined,
"level": 0,
"message": "Hello",
"type": "info",
},
{
"args": [],
"date": undefined,
"level": 0,
"message": "World",
"type": "info",
},
]
`);
expect(reporter.history.map((obj) => obj.output)).toMatchInlineSnapshot(`
[
"Hello",
"World",
]
`);
});

it('should format', () => {
const reporter = MockReporter(BasicReporter());
const logger = Logger({ level: LogLevels.verbose, reporter: [reporter] });

logger.log('Hello %s', 'world');
expect(reporter.history.map((obj) => obj.output)).toMatchInlineSnapshot(`
[
"Hello world",
]
`);
});
});

0 comments on commit b45d132

Please sign in to comment.