Skip to content

Commit e36e023

Browse files
authored
Merge pull request #1 from tclxshunquan-wang/feat/logger
feat: add logger package with customizable logging and plugin support
2 parents c8b42bd + 7a48bfd commit e36e023

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2459
-44
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"@commitlint/cli": "^19.8.1",
6363
"@commitlint/config-conventional": "^19.8.1",
6464
"@hyperse/eslint-config-hyperse": "^1.4.5",
65+
"@hyperse/ts-node": "^1.0.3",
6566
"commitizen": "^4.3.1",
6667
"cz-conventional-changelog": "^3.3.0",
6768
"eslint": "^9.29.0",

packages/logger-plugin-console/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
"package.json"
3131
],
3232
"scripts": {
33+
"console": "yarn node --import=@hyperse/ts-node/register ./tests/console.ts",
3334
"build:client": "tsup --config ./tsup.config.ts",
34-
"build": "rimraf dist && tsc -p ./tsconfig.build.json && yarn build:client && npm run minify",
35+
"build": "rimraf dist && tsc -p ./tsconfig.build.json && yarn build:client",
3536
"?build-release": "When https://github.com/atlassian/changesets/issues/432 has a solution we can remove this trick",
3637
"build-release": "yarn build && rimraf ./_release && yarn pack && mkdir ./_release && tar zxvf ./package.tgz --directory ./_release && rm ./package.tgz",
37-
"minify": "node ../../scripts/minify.mjs --dest=dist",
3838
"clean": "rimraf --no-glob ./dist ./_release ./coverage ./tsconfig.tsbuildinfo",
3939
"lint": "eslint .",
4040
"test": "run-s test-unit",
@@ -44,7 +44,8 @@
4444
"typecheck": "tsc --project ./tsconfig.json --noEmit"
4545
},
4646
"dependencies": {
47-
"@hyperse/logger": "workspace:*"
47+
"@hyperse/logger": "workspace:*",
48+
"picocolors": "^1.1.1"
4849
},
4950
"devDependencies": {
5051
"@hyperse/eslint-config-hyperse": "^1.4.5",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { LogLevel } from '@hyperse/logger';
2+
import type { Color } from './types/type-color.js';
3+
import type { ConsoleOptions } from './types/type-options.js';
4+
5+
export const defaultLevelColor: Record<LogLevel, Color[]> = {
6+
[LogLevel.Error]: ['red'],
7+
[LogLevel.Warn]: ['yellow'],
8+
[LogLevel.Info]: ['green'],
9+
[LogLevel.Debug]: ['blue'],
10+
[LogLevel.Verbose]: ['magenta'],
11+
};
12+
13+
export const defaultPrefixColor: Color[] = ['bold', 'magenta'];
14+
export const defaultLoggerNameColor: Color[] = ['bold', 'cyan'];
15+
export const defaultPluginNameColor: Color[] = ['bold', 'cyan'];
16+
17+
export const defaultConfig: Required<ConsoleOptions> = {
18+
disable: false,
19+
showTimestamp: true,
20+
showLoggerName: false,
21+
capitalizeLoggerName: false,
22+
showPluginName: false,
23+
capitalizePluginName: false,
24+
showPrefix: true,
25+
showLevelName: true,
26+
capitalizeLevelName: true,
27+
showDate: false,
28+
use24HourClock: true,
29+
showArrow: false,
30+
noColor: false,
31+
levelColor: defaultLevelColor,
32+
prefixColor: defaultPrefixColor,
33+
loggerNameColor: defaultLoggerNameColor,
34+
pluginNameColor: defaultPluginNameColor,
35+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { definePlugin } from '@hyperse/logger';
2+
import { assertMessage } from './helpers/helper-assert-message.js';
3+
import { formatMessage } from './helpers/helper-format-message.js';
4+
import { isLoggable } from './helpers/helper-is-loggable.js';
5+
import { mergeConsoleOptions } from './helpers/helper-merge-optons.js';
6+
import type { ConsoleOptions } from './types/type-options.js';
7+
import type {
8+
ConsolePluginContext,
9+
ConsolePluginMessage,
10+
} from './types/type-plugin.js';
11+
12+
export const createConsolePlugin = (options?: ConsoleOptions) => {
13+
const newOptions = mergeConsoleOptions(options);
14+
return definePlugin<ConsolePluginContext, ConsolePluginMessage | string>({
15+
pluginName: 'hps-logger-plugin-console',
16+
execute: async ({ ctx, level, message, pipe, exitPipe }) => {
17+
await pipe(
18+
() => {
19+
const { disable } = newOptions;
20+
if (disable || !isLoggable(ctx, level)) {
21+
return exitPipe('level is too low');
22+
}
23+
return {
24+
inputMessage: assertMessage(message),
25+
};
26+
},
27+
({ inputMessage }) => {
28+
const formatOptions = {
29+
ctx,
30+
level,
31+
inputMessage,
32+
options: newOptions,
33+
};
34+
const outputMessage = formatMessage(formatOptions);
35+
return {
36+
outputMessage,
37+
};
38+
},
39+
({ outputMessage }) => {
40+
console.log(outputMessage);
41+
}
42+
)();
43+
},
44+
});
45+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { LoggerMessageObject } from '@hyperse/logger';
2+
3+
export const assertMessage = <T extends LoggerMessageObject>(
4+
message: T | string
5+
): T => {
6+
if (typeof message === 'string') {
7+
const newMessage = {
8+
message: message,
9+
name: undefined,
10+
stack: undefined,
11+
};
12+
return newMessage as T;
13+
}
14+
return message;
15+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Picocolors from 'picocolors';
2+
import type { Color } from '../types/type-color.js';
3+
4+
/**
5+
* Terminal output formatting with ANSI colors
6+
* @param colors The colors for the console output
7+
* @param noColor Removes colors from the console output
8+
* @returns
9+
*/
10+
export function terminalColor(colors: readonly Color[], noColor?: boolean) {
11+
if (noColor || !colors.length) {
12+
// Pure text output.
13+
return (x: string) => x;
14+
}
15+
return (x: string) => {
16+
let out: string = x;
17+
for (let i = 0; i < colors.length; i++) {
18+
out = Picocolors[colors[i]](out);
19+
}
20+
return out;
21+
};
22+
}
23+
24+
export const getColorApplier = (
25+
colorType: 'COLOR' | 'DECORATION',
26+
levelColors: readonly Color[],
27+
noColor: boolean
28+
) => {
29+
const colors = levelColors.filter((colorName) => {
30+
const isDecoration =
31+
colorName === 'strikethrough' || colorName === 'underline';
32+
33+
return colorType === 'DECORATION' ? isDecoration : !isDecoration;
34+
});
35+
36+
return terminalColor(colors, noColor);
37+
};
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import type { LoggerPluginContext, LogLevel } from '@hyperse/logger';
2+
import type { ConsoleOptions } from '../types/type-options.js';
3+
import type {
4+
ConsolePluginContext,
5+
ConsolePluginMessage,
6+
} from '../types/type-plugin.js';
7+
import { getColorApplier, terminalColor } from './helper-color-applier.js';
8+
import { formatStack } from './helper-format-stack.js';
9+
import { normalizeLevelData } from './helper-normalize-level.js';
10+
import { strTimePad } from './helper-str-pad.js';
11+
12+
export const formatMessage = (formatOptions: {
13+
ctx: LoggerPluginContext<ConsolePluginContext>;
14+
level: LogLevel;
15+
inputMessage: ConsolePluginMessage;
16+
options: Required<ConsoleOptions>;
17+
}) => {
18+
const { ctx, level, inputMessage, options } = formatOptions;
19+
const { name: loggerName, pluginName } = ctx;
20+
const { message, name: messageName, stack, prefix } = inputMessage;
21+
const {
22+
showLoggerName,
23+
capitalizeLoggerName,
24+
showPluginName,
25+
capitalizePluginName,
26+
showPrefix,
27+
showLevelName,
28+
capitalizeLevelName,
29+
showDate,
30+
showTimestamp,
31+
use24HourClock,
32+
showArrow,
33+
noColor,
34+
prefixColor,
35+
loggerNameColor,
36+
pluginNameColor,
37+
} = options;
38+
39+
const time = new Date();
40+
let output = '';
41+
const levelData = normalizeLevelData(level, options);
42+
43+
const color = getColorApplier('COLOR', levelData.color, noColor);
44+
const decorate = getColorApplier('DECORATION', levelData.color, noColor);
45+
const context: string[] = [];
46+
47+
// date and timestamp
48+
if (showDate || showTimestamp) {
49+
output += '[';
50+
if (showDate) {
51+
output += color(
52+
' ' +
53+
decorate(
54+
`${time.getFullYear()}-${time.getMonth() + 1}-${time.getDate()}`
55+
) +
56+
' '
57+
);
58+
}
59+
60+
if (showDate && showTimestamp) {
61+
output += '|';
62+
}
63+
64+
if (showTimestamp) {
65+
const hours = time.getHours();
66+
67+
output += color(
68+
' ' +
69+
decorate(
70+
`${strTimePad(
71+
use24HourClock || !(hours >= 13 || hours === 0)
72+
? hours
73+
: Math.abs(hours - 12)
74+
)}:${strTimePad(time.getMinutes())}:${strTimePad(time.getSeconds())}`
75+
) +
76+
' ' +
77+
(use24HourClock ? '' : decorate(hours >= 13 ? 'PM' : 'AM') + ' ')
78+
);
79+
}
80+
81+
output += '] ';
82+
}
83+
84+
// level name
85+
if (showLevelName) {
86+
output +=
87+
'[ ' +
88+
color(
89+
capitalizeLevelName ? levelData.name.toUpperCase() : levelData.name
90+
) +
91+
' ] ';
92+
}
93+
94+
// prefix
95+
if (showPrefix && prefix) {
96+
const ctxColor = getColorApplier('COLOR', prefixColor, noColor);
97+
context.push(' ' + ctxColor(prefix.toUpperCase()) + ' ');
98+
}
99+
100+
// logger name
101+
if (showLoggerName && loggerName) {
102+
const ctxColor = getColorApplier('COLOR', loggerNameColor, noColor);
103+
context.push(
104+
' ' +
105+
ctxColor(capitalizeLoggerName ? loggerName.toUpperCase() : loggerName) +
106+
' '
107+
);
108+
}
109+
110+
// plugin name
111+
if (showPluginName && pluginName) {
112+
const ctxColor = getColorApplier('COLOR', pluginNameColor, noColor);
113+
context.push(
114+
' ' +
115+
ctxColor(capitalizePluginName ? pluginName.toUpperCase() : pluginName) +
116+
' '
117+
);
118+
}
119+
120+
// level context
121+
if (context.length) {
122+
output += '[' + context.join(':') + '] ';
123+
}
124+
125+
// arrow
126+
if (showArrow) {
127+
output += ` ${terminalColor(['bold'])('>>')} `;
128+
}
129+
130+
// message name
131+
if (messageName) {
132+
output += `${messageName} `;
133+
}
134+
135+
// message
136+
if (message) {
137+
output += `${message} `;
138+
}
139+
140+
// stack
141+
if (stack) {
142+
output += `${formatStack(stack)}`;
143+
}
144+
return output;
145+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { terminalColor } from './helper-color-applier.js';
2+
3+
function parseStack(stack: string) {
4+
if (!stack || typeof stack !== 'string') {
5+
return [];
6+
}
7+
const lines = stack
8+
.split('\n')
9+
.splice(1)
10+
.map((l) => l.trim().replace('file://', ''));
11+
12+
return lines;
13+
}
14+
15+
export const formatStack = (stack: string) =>
16+
`\n${parseStack(stack)
17+
.map((line) => ` ${terminalColor(['red'])(line)}`)
18+
.join('\n')}`;

0 commit comments

Comments
 (0)