nightingale 14.2.1 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +204 -57
- package/dist/definitions/config.d.ts +1 -1
- package/dist/definitions/debug/debug.d.ts +5 -0
- package/dist/definitions/debug/debug.d.ts.map +1 -0
- package/dist/definitions/debug/debug.test.d.ts +2 -0
- package/dist/definitions/debug/debug.test.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/formatObject.d.ts +9 -0
- package/dist/definitions/formatter-utils/formatObject.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/formatObject.test.d.ts +2 -0
- package/dist/definitions/formatter-utils/formatObject.test.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/formatRecordToString.d.ts +4 -0
- package/dist/definitions/formatter-utils/formatRecordToString.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/index.d.ts +15 -0
- package/dist/definitions/formatter-utils/index.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/index.test.d.ts +2 -0
- package/dist/definitions/formatter-utils/index.test.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/levelToStyles.d.ts +3 -0
- package/dist/definitions/formatter-utils/levelToStyles.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/levelToSymbol.d.ts +3 -0
- package/dist/definitions/formatter-utils/levelToSymbol.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/styleToHexColor.d.ts +7 -0
- package/dist/definitions/formatter-utils/styleToHexColor.d.ts.map +1 -0
- package/dist/definitions/formatter-utils/styleToHtmlStyle.d.ts +109 -0
- package/dist/definitions/formatter-utils/styleToHtmlStyle.d.ts.map +1 -0
- package/dist/definitions/formatters/ANSIFormatter.d.ts +6 -0
- package/dist/definitions/formatters/ANSIFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/ANSIFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/ANSIFormatter.test.d.ts.map +1 -0
- package/dist/definitions/formatters/BrowserConsoleFormatter.d.ts +10 -0
- package/dist/definitions/formatters/BrowserConsoleFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/BrowserConsoleFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/BrowserConsoleFormatter.test.d.ts.map +1 -0
- package/dist/definitions/formatters/HTMLFormatter.d.ts +5 -0
- package/dist/definitions/formatters/HTMLFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/HTMLFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/HTMLFormatter.test.d.ts.map +1 -0
- package/dist/definitions/formatters/JSONFormatter.d.ts +4 -0
- package/dist/definitions/formatters/JSONFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/JSONFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/JSONFormatter.test.d.ts.map +1 -0
- package/dist/definitions/formatters/MarkdownFormatter.d.ts +5 -0
- package/dist/definitions/formatters/MarkdownFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/MarkdownFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/MarkdownFormatter.test.d.ts.map +1 -0
- package/dist/definitions/formatters/RawFormatter.d.ts +5 -0
- package/dist/definitions/formatters/RawFormatter.d.ts.map +1 -0
- package/dist/definitions/formatters/RawFormatter.test.d.ts +2 -0
- package/dist/definitions/formatters/RawFormatter.test.d.ts.map +1 -0
- package/dist/definitions/handlers/BrowserConsoleHandler.d.ts +14 -0
- package/dist/definitions/handlers/BrowserConsoleHandler.d.ts.map +1 -0
- package/dist/definitions/handlers/ConsoleCLIHandler.d.ts +12 -0
- package/dist/definitions/handlers/ConsoleCLIHandler.d.ts.map +1 -0
- package/dist/definitions/handlers/ConsoleHandler.d.ts +14 -0
- package/dist/definitions/handlers/ConsoleHandler.d.ts.map +1 -0
- package/dist/definitions/handlers/StringHandler.d.ts +9 -0
- package/dist/definitions/handlers/StringHandler.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +18 -4
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/loggers/LoggerCLI.d.ts +23 -0
- package/dist/definitions/loggers/LoggerCLI.d.ts.map +1 -0
- package/dist/definitions/outputs/cliConsoleOutput.d.ts +3 -0
- package/dist/definitions/outputs/cliConsoleOutput.d.ts.map +1 -0
- package/dist/definitions/outputs/consoleOutput.d.ts +3 -0
- package/dist/definitions/outputs/consoleOutput.d.ts.map +1 -0
- package/dist/index-browser.es.js +936 -46
- package/dist/index-browser.es.js.map +1 -1
- package/dist/index-node20.mjs +1023 -0
- package/dist/index-node20.mjs.map +1 -0
- package/package.json +31 -37
- package/src/config.ts +12 -12
- package/src/debug/debug.test.ts +50 -0
- package/src/debug/debug.ts +100 -0
- package/src/formatter-utils/formatObject.test.ts +153 -0
- package/src/formatter-utils/formatObject.ts +462 -0
- package/src/formatter-utils/formatRecordToString.ts +67 -0
- package/src/formatter-utils/index.test.ts +33 -0
- package/src/formatter-utils/index.ts +20 -0
- package/src/formatter-utils/levelToStyles.ts +14 -0
- package/src/formatter-utils/levelToSymbol.ts +14 -0
- package/src/formatter-utils/styleToHexColor.ts +9 -0
- package/src/formatter-utils/styleToHtmlStyle.ts +69 -0
- package/src/formatters/ANSIFormatter.test.ts +27 -0
- package/src/formatters/ANSIFormatter.ts +68 -0
- package/src/formatters/BrowserConsoleFormatter.test.ts +59 -0
- package/src/formatters/BrowserConsoleFormatter.ts +45 -0
- package/src/formatters/HTMLFormatter.test.ts +23 -0
- package/src/formatters/HTMLFormatter.ts +28 -0
- package/src/formatters/JSONFormatter.test.ts +62 -0
- package/src/formatters/JSONFormatter.ts +62 -0
- package/src/formatters/MarkdownFormatter.test.ts +19 -0
- package/src/formatters/MarkdownFormatter.ts +31 -0
- package/src/formatters/RawFormatter.test.ts +21 -0
- package/src/formatters/RawFormatter.ts +13 -0
- package/src/handlers/BrowserConsoleHandler.ts +78 -0
- package/src/handlers/ConsoleCLIHandler.ts +41 -0
- package/src/handlers/ConsoleHandler.ts +55 -0
- package/src/handlers/StringHandler.ts +21 -0
- package/src/index.test.ts +29 -29
- package/src/index.ts +24 -10
- package/src/loggers/LoggerCLI.ts +91 -0
- package/src/outputs/cliConsoleOutput.ts +17 -0
- package/src/outputs/consoleOutput.ts +18 -0
- package/dist/index-browsermodern.es.js +0 -120
- package/dist/index-browsermodern.es.js.map +0 -1
- package/dist/index-node18.mjs +0 -120
- package/dist/index-node18.mjs.map +0 -1
- package/src/.eslintrc.json +0 -30
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LogRecord, Metadata } from "nightingale-types";
|
|
2
|
+
|
|
3
|
+
export { levelToStyles } from "./levelToStyles";
|
|
4
|
+
export { levelToSymbol } from "./levelToSymbol";
|
|
5
|
+
export {
|
|
6
|
+
styleToHtmlStyleThemeDark,
|
|
7
|
+
styleToHtmlStyleThemeLight,
|
|
8
|
+
} from "./styleToHtmlStyle";
|
|
9
|
+
export type { StyleToHtmlStyle } from "./styleToHtmlStyle";
|
|
10
|
+
export { styleToHexColor } from "./styleToHexColor";
|
|
11
|
+
export { formatObject } from "./formatObject";
|
|
12
|
+
export { formatRecordToString } from "./formatRecordToString";
|
|
13
|
+
|
|
14
|
+
export interface NightingaleFormatter {
|
|
15
|
+
format: <T extends Metadata>(record: LogRecord<T>) => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StringArrayNightingaleFormatter {
|
|
19
|
+
format: <T extends Metadata>(record: LogRecord<T>) => string[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
|
|
3
|
+
export type LevelToStyles = Readonly<Record<number, string[]>>;
|
|
4
|
+
|
|
5
|
+
export const levelToStyles: LevelToStyles = {
|
|
6
|
+
[Level.TRACE]: ["gray"],
|
|
7
|
+
[Level.DEBUG]: ["gray"],
|
|
8
|
+
// [Level.INFO]: ['gray'],
|
|
9
|
+
[Level.WARN]: ["yellow"],
|
|
10
|
+
[Level.ERROR]: ["red", "bold"],
|
|
11
|
+
[Level.CRITICAL]: ["red", "bold"],
|
|
12
|
+
[Level.FATAL]: ["bgRed", "white"],
|
|
13
|
+
[Level.EMERGENCY]: ["bgRed", "white"],
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
|
|
3
|
+
export type LevelToSymbol = Readonly<Record<number, string>>;
|
|
4
|
+
|
|
5
|
+
export const levelToSymbol: LevelToSymbol = {
|
|
6
|
+
[Level.TRACE]: "•",
|
|
7
|
+
[Level.DEBUG]: "•",
|
|
8
|
+
[Level.INFO]: "→",
|
|
9
|
+
[Level.WARN]: "⚠",
|
|
10
|
+
[Level.ERROR]: "✖",
|
|
11
|
+
[Level.CRITICAL]: "!",
|
|
12
|
+
[Level.FATAL]: "‼",
|
|
13
|
+
[Level.EMERGENCY]: "‼",
|
|
14
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { styleToHexColor } from "./styleToHexColor";
|
|
2
|
+
|
|
3
|
+
export interface HtmlStyle {
|
|
4
|
+
readonly open: string;
|
|
5
|
+
readonly close: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const styleToHtmlStyleThemeLight = {
|
|
9
|
+
// text style
|
|
10
|
+
bold: { open: "font-weight: bold", close: "font-weight: normal" },
|
|
11
|
+
italic: { open: "font-style: italic", close: "font-style: normal" },
|
|
12
|
+
underline: {
|
|
13
|
+
open: "text-decoration: underline",
|
|
14
|
+
close: "text-decoration: none",
|
|
15
|
+
},
|
|
16
|
+
inverse: {
|
|
17
|
+
open: "unicode-bidi: bidi-override; direction: rtl",
|
|
18
|
+
close: "unicode-bidi: normal; direction: ltr",
|
|
19
|
+
},
|
|
20
|
+
strikethrough: {
|
|
21
|
+
open: "text-decoration: line-through",
|
|
22
|
+
close: "text-decoration: none",
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
black: { open: "color: black", close: "color: currentcolor" },
|
|
26
|
+
red: { open: "color: #ff0020", close: "color: currentcolor" },
|
|
27
|
+
green: { open: "color: #00b317", close: "color: currentcolor" },
|
|
28
|
+
yellow: { open: "color: #ffcc00", close: "color: currentcolor" },
|
|
29
|
+
blue: { open: "color: #00a0ff", close: "color: currentcolor" },
|
|
30
|
+
magenta: { open: "color: #ff00a0", close: "color: currentcolor" },
|
|
31
|
+
cyan: { open: "color: #00cfd8", close: "color: currentcolor" },
|
|
32
|
+
white: { open: "color: white", close: "color: currentcolor" },
|
|
33
|
+
gray: { open: "color: gray", close: "color: currentcolor" },
|
|
34
|
+
|
|
35
|
+
bgBlack: { open: "background: black", close: "background: initial" },
|
|
36
|
+
bgRed: { open: "background: #ff0020", close: "background: initial" },
|
|
37
|
+
bgGreen: { open: "background: #00b317", close: "background: initial" },
|
|
38
|
+
bgYellow: { open: "background: #ffcc00", close: "background: initial" },
|
|
39
|
+
bgBlue: { open: "background: #00a0ff", close: "background: initial" },
|
|
40
|
+
bgMagenta: { open: "background: #ff00a0", close: "background: initial" },
|
|
41
|
+
bgCyan: { open: "background: #00cfd8", close: "background: initial" },
|
|
42
|
+
bgWhite: { open: "background: white", close: "background: initial" },
|
|
43
|
+
|
|
44
|
+
orange: {
|
|
45
|
+
open: `color: #${styleToHexColor.orange}`,
|
|
46
|
+
close: "color: currentcolor",
|
|
47
|
+
},
|
|
48
|
+
grayLight: {
|
|
49
|
+
open: `color: #${styleToHexColor.grayLight}`,
|
|
50
|
+
close: "color: currentcolor",
|
|
51
|
+
},
|
|
52
|
+
"gray-light": {
|
|
53
|
+
open: `color: #${styleToHexColor.grayLight}`,
|
|
54
|
+
close: "color: currentcolor",
|
|
55
|
+
},
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
export type StyleToHtmlStyle = Readonly<
|
|
59
|
+
Record<keyof typeof styleToHtmlStyleThemeLight, HtmlStyle>
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
export const styleToHtmlStyleThemeDark: StyleToHtmlStyle = {
|
|
63
|
+
...styleToHtmlStyleThemeLight,
|
|
64
|
+
black: styleToHtmlStyleThemeLight.white,
|
|
65
|
+
bgBlack: styleToHtmlStyleThemeLight.bgWhite,
|
|
66
|
+
white: styleToHtmlStyleThemeLight.black,
|
|
67
|
+
bgWhite: styleToHtmlStyleThemeLight.bgBlack,
|
|
68
|
+
gray: { open: "color: lightgray", close: "color: currentcolor" },
|
|
69
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* eslint-disable no-control-regex */
|
|
2
|
+
import { ANSIFormatter, style } from "./ANSIFormatter";
|
|
3
|
+
|
|
4
|
+
test("style: blue bold color", () => {
|
|
5
|
+
expect(style(["blue", "bold"], "test").replace(/\u001B/g, "ESC")).toBe(
|
|
6
|
+
"ESC[1mESC[34mtestESC[39mESC[22m",
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("style: ansi256 color", () => {
|
|
11
|
+
expect(style(["orange"], "test").replace(/\u001B/g, "ESC")).toBe(
|
|
12
|
+
"ESC[38;5;208mtestESC[39m",
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("format simple message", () => {
|
|
17
|
+
expect(
|
|
18
|
+
ANSIFormatter.format({
|
|
19
|
+
key: "test",
|
|
20
|
+
level: 100,
|
|
21
|
+
message: "test",
|
|
22
|
+
datetime: new Date(2000, 1, 1, 1, 1, 1),
|
|
23
|
+
}).replace(/\u001B/g, "ESC"),
|
|
24
|
+
).toBe(
|
|
25
|
+
"ESC[38;5;244mtestESC[39m ESC[1mESC[90m01:01:01ESC[39mESC[22m ESC[90m• testESC[39m",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ansi from "ansi-styles";
|
|
2
|
+
import type { NightingaleFormatter } from "nightingale";
|
|
3
|
+
import type { Styles } from "nightingale-types";
|
|
4
|
+
import { formatRecordToString, styleToHexColor } from "../formatter-utils";
|
|
5
|
+
|
|
6
|
+
export type { Styles } from "nightingale-types";
|
|
7
|
+
|
|
8
|
+
interface CodePair {
|
|
9
|
+
open: string;
|
|
10
|
+
close: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type AnsiStyles = Record<string, CodePair | undefined>;
|
|
14
|
+
|
|
15
|
+
const ansiStyles: AnsiStyles = {
|
|
16
|
+
black: ansi.black,
|
|
17
|
+
red: ansi.red,
|
|
18
|
+
green: ansi.green,
|
|
19
|
+
yellow: ansi.yellow,
|
|
20
|
+
blue: ansi.blue,
|
|
21
|
+
magenta: ansi.magenta,
|
|
22
|
+
cyan: ansi.cyan,
|
|
23
|
+
white: ansi.white,
|
|
24
|
+
gray: ansi.gray,
|
|
25
|
+
|
|
26
|
+
bgBlack: ansi.bgBlack,
|
|
27
|
+
bgRed: ansi.bgRed,
|
|
28
|
+
bgGreen: ansi.bgGreen,
|
|
29
|
+
bgYellow: ansi.bgYellow,
|
|
30
|
+
bgBlue: ansi.bgBlue,
|
|
31
|
+
bgMagenta: ansi.bgMagenta,
|
|
32
|
+
bgCyan: ansi.bgCyan,
|
|
33
|
+
bgWhite: ansi.bgWhite,
|
|
34
|
+
|
|
35
|
+
bold: ansi.bold,
|
|
36
|
+
underline: ansi.underline,
|
|
37
|
+
|
|
38
|
+
// http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
|
|
39
|
+
orange: {
|
|
40
|
+
open: ansi.color.ansi256(ansi.hexToAnsi256(styleToHexColor.orange)),
|
|
41
|
+
close: ansi.color.close,
|
|
42
|
+
},
|
|
43
|
+
"gray-light": {
|
|
44
|
+
open: ansi.color.ansi256(ansi.hexToAnsi256(styleToHexColor["gray-light"])),
|
|
45
|
+
close: ansi.color.close,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function style(styles: Styles, string: string): string {
|
|
50
|
+
if (!styles || styles.length === 0 || !string) {
|
|
51
|
+
return string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
55
|
+
return styles.reduce((styledString: string, styleName: string) => {
|
|
56
|
+
const codePair: CodePair | undefined = ansiStyles[styleName];
|
|
57
|
+
|
|
58
|
+
if (!codePair) {
|
|
59
|
+
throw new Error(`Unknown style: ${styleName}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return codePair.open + styledString + codePair.close;
|
|
63
|
+
}, string);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ANSIFormatter: NightingaleFormatter = {
|
|
67
|
+
format: (record) => formatRecordToString(record, style),
|
|
68
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { StyleToHtmlStyle } from "nightingale";
|
|
2
|
+
import { Level } from "nightingale-levels";
|
|
3
|
+
import { styleToHtmlStyleThemeLight } from "../formatter-utils";
|
|
4
|
+
import { BrowserConsoleFormatter, style } from "./BrowserConsoleFormatter";
|
|
5
|
+
|
|
6
|
+
const styleToHtmlStyle: StyleToHtmlStyle = styleToHtmlStyleThemeLight;
|
|
7
|
+
const formatterWithLightTheme = new BrowserConsoleFormatter("light");
|
|
8
|
+
const formatterWithDarkTheme = new BrowserConsoleFormatter("dark");
|
|
9
|
+
|
|
10
|
+
test("style: blue bold color", () => {
|
|
11
|
+
const args: string[] = [];
|
|
12
|
+
expect(style(styleToHtmlStyle, args)(["blue", "bold"], "test")).toBe(
|
|
13
|
+
"%ctest%c",
|
|
14
|
+
);
|
|
15
|
+
expect(args).toEqual([
|
|
16
|
+
"color: #00a0ff; font-weight: bold",
|
|
17
|
+
"color: currentcolor; font-weight: normal",
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("format simple message, with light theme", () => {
|
|
22
|
+
const record = {
|
|
23
|
+
key: "record.key",
|
|
24
|
+
level: Level.INFO,
|
|
25
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
26
|
+
message: "test",
|
|
27
|
+
metadata: {},
|
|
28
|
+
extra: {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const [string, ...args] = formatterWithLightTheme.format(record);
|
|
32
|
+
expect(string).toBe("%crecord.key%c %c01:00:00%c → test");
|
|
33
|
+
expect(args).toEqual([
|
|
34
|
+
"color: #808080",
|
|
35
|
+
"color: currentcolor",
|
|
36
|
+
"color: gray; font-weight: bold",
|
|
37
|
+
"color: currentcolor; font-weight: normal",
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("format simple message, with dark theme", () => {
|
|
42
|
+
const record = {
|
|
43
|
+
key: "record.key",
|
|
44
|
+
level: Level.INFO,
|
|
45
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
46
|
+
message: "test",
|
|
47
|
+
metadata: {},
|
|
48
|
+
extra: {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const [string, ...args] = formatterWithDarkTheme.format(record);
|
|
52
|
+
expect(string).toBe("%crecord.key%c %c01:00:00%c → test");
|
|
53
|
+
expect(args).toEqual([
|
|
54
|
+
"color: #808080",
|
|
55
|
+
"color: currentcolor",
|
|
56
|
+
"color: lightgray; font-weight: bold",
|
|
57
|
+
"color: currentcolor; font-weight: normal",
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StyleToHtmlStyle } from "nightingale";
|
|
2
|
+
import type { LogRecord, Metadata, Styles } from "nightingale-types";
|
|
3
|
+
import {
|
|
4
|
+
formatRecordToString,
|
|
5
|
+
styleToHtmlStyleThemeDark,
|
|
6
|
+
styleToHtmlStyleThemeLight,
|
|
7
|
+
} from "../formatter-utils";
|
|
8
|
+
import type { StringArrayNightingaleFormatter } from "../formatter-utils";
|
|
9
|
+
|
|
10
|
+
export const style =
|
|
11
|
+
(styleToHtmlStyle: StyleToHtmlStyle, args: string[]) =>
|
|
12
|
+
(styles: Styles, string: string): string => {
|
|
13
|
+
if (!styles || styles.length === 0 || !string) {
|
|
14
|
+
return string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const htmlStyles = styles.map(
|
|
18
|
+
(styleName) => styleToHtmlStyle[styleName as keyof StyleToHtmlStyle],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
args.push(
|
|
22
|
+
htmlStyles.map((s) => s.open).join("; "),
|
|
23
|
+
htmlStyles.map((s) => s.close).join("; "),
|
|
24
|
+
);
|
|
25
|
+
return `%c${string}%c`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class BrowserConsoleFormatter
|
|
29
|
+
implements StringArrayNightingaleFormatter
|
|
30
|
+
{
|
|
31
|
+
styleToHtmlStyle: StyleToHtmlStyle;
|
|
32
|
+
constructor(theme: "dark" | "light" = "light") {
|
|
33
|
+
this.styleToHtmlStyle =
|
|
34
|
+
theme === "dark" ? styleToHtmlStyleThemeDark : styleToHtmlStyleThemeLight;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
format<T extends Metadata>(record: LogRecord<T>): string[] {
|
|
38
|
+
const args: string[] = [];
|
|
39
|
+
const string = formatRecordToString(
|
|
40
|
+
record,
|
|
41
|
+
style(this.styleToHtmlStyle, args),
|
|
42
|
+
);
|
|
43
|
+
return [string, ...args];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
import { HTMLFormatter, style } from "./HTMLFormatter";
|
|
3
|
+
|
|
4
|
+
test("blue bold color", () => {
|
|
5
|
+
expect(style(["blue", "bold"], "test")).toBe(
|
|
6
|
+
'<span style="color: #00a0ff; font-weight: bold">test</span>',
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("format simple message", () => {
|
|
11
|
+
const record = {
|
|
12
|
+
key: "record.key",
|
|
13
|
+
level: Level.INFO,
|
|
14
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
15
|
+
message: "record.message",
|
|
16
|
+
metadata: {},
|
|
17
|
+
extra: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
expect(HTMLFormatter.format(record)).toBe(
|
|
21
|
+
'<span style="color: #808080">record.key</span> <span style="color: gray; font-weight: bold">01:00:00</span> → record.message',
|
|
22
|
+
);
|
|
23
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Styles } from "nightingale-types";
|
|
2
|
+
import type {
|
|
3
|
+
NightingaleFormatter,
|
|
4
|
+
StyleToHtmlStyle,
|
|
5
|
+
} from "../formatter-utils";
|
|
6
|
+
import {
|
|
7
|
+
formatRecordToString,
|
|
8
|
+
styleToHtmlStyleThemeLight,
|
|
9
|
+
} from "../formatter-utils";
|
|
10
|
+
|
|
11
|
+
export function style(styles: Styles, string: string): string {
|
|
12
|
+
if (!styles || styles.length === 0 || !string) {
|
|
13
|
+
return string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `<span style="${styles
|
|
17
|
+
.map(
|
|
18
|
+
(styleName) =>
|
|
19
|
+
styleToHtmlStyleThemeLight[styleName as keyof StyleToHtmlStyle].open,
|
|
20
|
+
)
|
|
21
|
+
.join("; ")}">${string}</span>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const HTMLFormatter: NightingaleFormatter = {
|
|
25
|
+
format(record) {
|
|
26
|
+
return formatRecordToString(record, style);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
import { JSONFormatter } from "./JSONFormatter";
|
|
3
|
+
|
|
4
|
+
test("format record", () => {
|
|
5
|
+
const record = {
|
|
6
|
+
key: "record.key",
|
|
7
|
+
level: Level.INFO,
|
|
8
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
9
|
+
message: "record.message",
|
|
10
|
+
metadata: {},
|
|
11
|
+
extra: {},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
expect(JSONFormatter.format(record)).toBe(JSON.stringify(record));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("format error", () => {
|
|
18
|
+
const error = new Error("test message");
|
|
19
|
+
const record = {
|
|
20
|
+
key: "record.key",
|
|
21
|
+
level: Level.INFO,
|
|
22
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
23
|
+
message: "record.message",
|
|
24
|
+
metadata: {
|
|
25
|
+
error,
|
|
26
|
+
},
|
|
27
|
+
extra: {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
expect(JSONFormatter.format(record)).toBe(
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
...record,
|
|
33
|
+
metadata: { error: { message: "test message", stack: error.stack } },
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("format map", () => {
|
|
39
|
+
const map = new Map<unknown, unknown>([
|
|
40
|
+
[1, "value1"],
|
|
41
|
+
["2", "value2"],
|
|
42
|
+
["3", 3],
|
|
43
|
+
[{}, "ignore value"],
|
|
44
|
+
]);
|
|
45
|
+
const record = {
|
|
46
|
+
key: "record.key",
|
|
47
|
+
level: Level.INFO,
|
|
48
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
49
|
+
message: "record.message",
|
|
50
|
+
metadata: {
|
|
51
|
+
map,
|
|
52
|
+
},
|
|
53
|
+
extra: {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
expect(JSONFormatter.format(record)).toBe(
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
...record,
|
|
59
|
+
metadata: { map: { "1": "value1", "2": "value2", "3": 3 } },
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NightingaleFormatter } from "../formatter-utils";
|
|
2
|
+
|
|
3
|
+
function map2object(map: Map<unknown, unknown>): unknown {
|
|
4
|
+
const object: Record<string, unknown> = {};
|
|
5
|
+
|
|
6
|
+
map.forEach((value, key) => {
|
|
7
|
+
if (typeof key === "object") {
|
|
8
|
+
// ignore key
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
13
|
+
object[String(key)] = value;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return object;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stringify(value: unknown, space?: number | string): string {
|
|
20
|
+
return JSON.stringify(
|
|
21
|
+
value,
|
|
22
|
+
(key, objectValue) => {
|
|
23
|
+
if (objectValue instanceof Map) {
|
|
24
|
+
return map2object(objectValue);
|
|
25
|
+
}
|
|
26
|
+
if (objectValue instanceof Error) {
|
|
27
|
+
return {
|
|
28
|
+
message: objectValue.message,
|
|
29
|
+
stack: objectValue.stack,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return objectValue as unknown;
|
|
34
|
+
},
|
|
35
|
+
space,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const JSONFormatter: NightingaleFormatter = {
|
|
40
|
+
format(record) {
|
|
41
|
+
return stringify({
|
|
42
|
+
key: record.key,
|
|
43
|
+
level: record.level,
|
|
44
|
+
datetime: record.datetime,
|
|
45
|
+
message: record.message,
|
|
46
|
+
metadata: record.metadata,
|
|
47
|
+
extra: record.extra,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const JSONCLIFormatter: NightingaleFormatter = {
|
|
53
|
+
format(record) {
|
|
54
|
+
return stringify({
|
|
55
|
+
key: record.key,
|
|
56
|
+
time: record.datetime.toTimeString().split(" ", 2)[0]!,
|
|
57
|
+
message: record.message,
|
|
58
|
+
...record.metadata,
|
|
59
|
+
...record.extra,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
import { MarkdownFormatter, style } from "./MarkdownFormatter";
|
|
3
|
+
|
|
4
|
+
test("style: blue bold color", () => {
|
|
5
|
+
expect(style(["blue", "bold"], "test")).toBe("*test*");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test("format simple message", () => {
|
|
9
|
+
const record = {
|
|
10
|
+
key: "record.key",
|
|
11
|
+
level: Level.INFO,
|
|
12
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
13
|
+
message: "test",
|
|
14
|
+
metadata: {},
|
|
15
|
+
extra: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
expect(MarkdownFormatter.format(record)).toBe("record.key *01:00:00* → test");
|
|
19
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Styles } from "nightingale-types";
|
|
2
|
+
import type { NightingaleFormatter } from "../formatter-utils";
|
|
3
|
+
import { formatRecordToString } from "../formatter-utils";
|
|
4
|
+
|
|
5
|
+
export function style(styles: Styles, string: string): string {
|
|
6
|
+
if (!styles || styles.length === 0 || !string) {
|
|
7
|
+
return string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
11
|
+
return styles.reduce((part, styleName) => {
|
|
12
|
+
switch (styleName) {
|
|
13
|
+
case "bold":
|
|
14
|
+
return `*${part}*`;
|
|
15
|
+
case "italic":
|
|
16
|
+
return `_${part}_`;
|
|
17
|
+
case "strikethrough":
|
|
18
|
+
return `~${part}~`;
|
|
19
|
+
|
|
20
|
+
// no default
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return part;
|
|
24
|
+
}, string);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const MarkdownFormatter: NightingaleFormatter = {
|
|
28
|
+
format(record) {
|
|
29
|
+
return formatRecordToString(record, style);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Level } from "nightingale-levels";
|
|
2
|
+
import { RawFormatter, style } from "./RawFormatter";
|
|
3
|
+
|
|
4
|
+
test("style: blue bold color", () => {
|
|
5
|
+
expect(style(["blue", "bold"], "test")).toBe("test");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test("format simple message", () => {
|
|
9
|
+
const record = {
|
|
10
|
+
key: "record.key",
|
|
11
|
+
level: Level.INFO,
|
|
12
|
+
datetime: new Date(2000, 1, 1, 1, 0, 0),
|
|
13
|
+
message: "record.message",
|
|
14
|
+
metadata: {},
|
|
15
|
+
extra: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
expect(RawFormatter.format(record)).toBe(
|
|
19
|
+
"record.key 01:00:00 → record.message",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Styles } from "nightingale-types";
|
|
2
|
+
import type { NightingaleFormatter } from "../formatter-utils";
|
|
3
|
+
import { formatRecordToString } from "../formatter-utils";
|
|
4
|
+
|
|
5
|
+
export function style(styles: Styles, value: string): string {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const RawFormatter: NightingaleFormatter = {
|
|
10
|
+
format(record) {
|
|
11
|
+
return formatRecordToString(record, style);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Handle,
|
|
3
|
+
Handler,
|
|
4
|
+
IsHandling,
|
|
5
|
+
Level,
|
|
6
|
+
LogRecord,
|
|
7
|
+
Metadata,
|
|
8
|
+
} from "nightingale-types";
|
|
9
|
+
import { createFindDebugLevel } from "../debug/debug";
|
|
10
|
+
import { BrowserConsoleFormatter } from "../formatters/BrowserConsoleFormatter";
|
|
11
|
+
import { consoleOutput } from "../outputs/consoleOutput";
|
|
12
|
+
|
|
13
|
+
export function getDebugString(): string {
|
|
14
|
+
const querystring = document.location.search;
|
|
15
|
+
const debugFromLocalStorage =
|
|
16
|
+
// eslint-disable-next-line unicorn/prefer-global-this, @typescript-eslint/no-unnecessary-condition
|
|
17
|
+
window.localStorage?.getItem("debug") || "";
|
|
18
|
+
|
|
19
|
+
if (!querystring) {
|
|
20
|
+
return debugFromLocalStorage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/URLUtils/search#Get_the_value_of_a_single_search_param
|
|
24
|
+
const debugFromQueryString = decodeURI(
|
|
25
|
+
querystring.replace(
|
|
26
|
+
// eslint-disable-next-line prefer-regex-literals, regexp/no-super-linear-backtracking
|
|
27
|
+
new RegExp("^(?:.*[&?]DEBUG(?:=([^&]*))?)?.*$", "i"),
|
|
28
|
+
"$1",
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
(debugFromLocalStorage ? `${debugFromLocalStorage},` : "") +
|
|
34
|
+
debugFromQueryString
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// debug string can change any time (localStorage), so we need a new object each time.
|
|
39
|
+
const findDebugLevel = (minLevel: Level, key: string): Level =>
|
|
40
|
+
createFindDebugLevel(getDebugString())(minLevel, key);
|
|
41
|
+
|
|
42
|
+
type Theme = "dark" | "light";
|
|
43
|
+
|
|
44
|
+
const getDefaultTheme = (): Theme => {
|
|
45
|
+
try {
|
|
46
|
+
const configInLocalStorage = localStorage.getItem("NIGHTINGALE_THEME");
|
|
47
|
+
if (configInLocalStorage && configInLocalStorage === "dark") {
|
|
48
|
+
return configInLocalStorage;
|
|
49
|
+
}
|
|
50
|
+
} catch {}
|
|
51
|
+
return "light";
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const createHandler = (theme: Theme = getDefaultTheme()): Handle => {
|
|
55
|
+
const browserConsoleFormatter = new BrowserConsoleFormatter(theme);
|
|
56
|
+
return <T extends Metadata>(record: LogRecord<T>) => {
|
|
57
|
+
consoleOutput(browserConsoleFormatter.format(record), record);
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface BrowserConsoleHandlerOptions {
|
|
62
|
+
theme?: Theme;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class BrowserConsoleHandler implements Handler {
|
|
66
|
+
minLevel: Level = 0;
|
|
67
|
+
|
|
68
|
+
handle: Handle;
|
|
69
|
+
|
|
70
|
+
isHandling: IsHandling;
|
|
71
|
+
|
|
72
|
+
constructor(minLevel: Level, options: BrowserConsoleHandlerOptions = {}) {
|
|
73
|
+
this.isHandling = (level: Level, key: string) =>
|
|
74
|
+
level >= findDebugLevel(minLevel, key);
|
|
75
|
+
|
|
76
|
+
this.handle = createHandler(options.theme);
|
|
77
|
+
}
|
|
78
|
+
}
|