feat: initial boilerplate
This commit is contained in:
48
api/libraries/logger/chalk.ts
Normal file
48
api/libraries/logger/chalk.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { HexValue } from "./color/hex.ts";
|
||||
import { type BGColor, type Color, hexToBgColor, hexToColor, type Modifier, styles } from "./color/styles.ts";
|
||||
|
||||
export const chalk = {
|
||||
color(hex: HexValue): (value: string) => string {
|
||||
const color = hexToColor(hex);
|
||||
return (value: string) => `${color}${value}${styles.modifier.reset}`;
|
||||
},
|
||||
bgColor(hex: HexValue): (value: string) => string {
|
||||
const color = hexToBgColor(hex);
|
||||
return (value: string) => `${color}${value}${styles.modifier.reset}`;
|
||||
},
|
||||
} as Chalk;
|
||||
|
||||
for (const key in styles.modifier) {
|
||||
chalk[key as Modifier] = function (value: string) {
|
||||
return toModifiedValue(key as Modifier, value);
|
||||
};
|
||||
}
|
||||
|
||||
for (const key in styles.color) {
|
||||
chalk[key as Color] = function (value: string) {
|
||||
return toColorValue(key as Color, value);
|
||||
};
|
||||
}
|
||||
|
||||
for (const key in styles.bgColor) {
|
||||
chalk[key as BGColor] = function (value: string) {
|
||||
return toBGColorValue(key as BGColor, value);
|
||||
};
|
||||
}
|
||||
|
||||
function toModifiedValue(key: Modifier, value: string): string {
|
||||
return `${styles.modifier[key]}${value}${styles.modifier.reset}`;
|
||||
}
|
||||
|
||||
function toColorValue(key: Color, value: string): string {
|
||||
return `${styles.color[key]}${value}${styles.modifier.reset}`;
|
||||
}
|
||||
|
||||
function toBGColorValue(key: BGColor, value: string): string {
|
||||
return `${styles.bgColor[key]}${value}${styles.modifier.reset}`;
|
||||
}
|
||||
|
||||
type Chalk = Record<Modifier | Color | BGColor, (value: string) => string> & {
|
||||
color(hex: HexValue): (value: string) => string;
|
||||
bgColor(hex: HexValue): (value: string) => string;
|
||||
};
|
||||
28
api/libraries/logger/color/hex.ts
Normal file
28
api/libraries/logger/color/hex.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { rgbToAnsi256 } from "./rgb.ts";
|
||||
|
||||
/**
|
||||
* Convert provided hex value to closest 256-Color value.
|
||||
*
|
||||
* @param hex - Hex to convert.
|
||||
*/
|
||||
export function hexToAnsi256(hex: HexValue) {
|
||||
const { r, g, b } = hexToRGB(hex);
|
||||
return rgbToAnsi256(r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a hex value and return its RGB values.
|
||||
*
|
||||
* @param hex - Hex to convert to RGB
|
||||
* @returns
|
||||
*/
|
||||
export function hexToRGB(hex: HexValue): { r: number; g: number; b: number } {
|
||||
return {
|
||||
r: parseInt(hex.slice(1, 3), 16),
|
||||
g: parseInt(hex.slice(3, 5), 16),
|
||||
b: parseInt(hex.slice(5, 7), 16),
|
||||
};
|
||||
}
|
||||
|
||||
export type HexValue =
|
||||
`#${string | number}${string | number}${string | number}${string | number}${string | number}${string | number}`;
|
||||
24
api/libraries/logger/color/rgb.ts
Normal file
24
api/libraries/logger/color/rgb.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Convert RGB to the nearest 256-color ANSI value
|
||||
*
|
||||
* @param r - Red value.
|
||||
* @param g - Green value.
|
||||
* @param b - Blue value.
|
||||
*/
|
||||
export function rgbToAnsi256(r: number, g: number, b: number): number {
|
||||
if (r === g && g === b) {
|
||||
if (r < 8) return 16;
|
||||
if (r > 248) return 231;
|
||||
return Math.round(((r - 8) / 247) * 24) + 232;
|
||||
}
|
||||
|
||||
// Map RGB to 6×6×6 color cube (16–231)
|
||||
const conv = (val: number) => Math.round(val / 51);
|
||||
const ri = conv(r);
|
||||
const gi = conv(g);
|
||||
const bi = conv(b);
|
||||
|
||||
return 16 + 36 * ri + 6 * gi + bi;
|
||||
}
|
||||
|
||||
export type RGB = { r: number; g: number; b: number };
|
||||
76
api/libraries/logger/color/styles.ts
Normal file
76
api/libraries/logger/color/styles.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { hexToAnsi256, HexValue } from "./hex.ts";
|
||||
import { toEscapeSequence } from "./utilities.ts";
|
||||
|
||||
export const styles = {
|
||||
modifier: {
|
||||
reset: toEscapeSequence(0), // Reset to normal
|
||||
bold: toEscapeSequence(1), // Bold text
|
||||
dim: toEscapeSequence(2), // Dim text
|
||||
italic: toEscapeSequence(3), // Italic text
|
||||
underline: toEscapeSequence(4), // Underlined text
|
||||
overline: toEscapeSequence(53), // Overline text
|
||||
inverse: toEscapeSequence(7), // Inverse
|
||||
hidden: toEscapeSequence(8), // Hidden text
|
||||
strikethrough: toEscapeSequence(9), // Strikethrough
|
||||
},
|
||||
|
||||
color: {
|
||||
black: toEscapeSequence(30), // Black color
|
||||
red: toEscapeSequence(31), // Red color
|
||||
green: toEscapeSequence(32), // Green color
|
||||
yellow: toEscapeSequence(33), // Yellow color
|
||||
blue: toEscapeSequence(34), // Blue color
|
||||
magenta: toEscapeSequence(35), // Magenta color
|
||||
cyan: toEscapeSequence(36), // Cyan color
|
||||
white: toEscapeSequence(37), // White color
|
||||
orange: hexToColor("#FFA500"),
|
||||
|
||||
// Bright colors
|
||||
blackBright: toEscapeSequence(90),
|
||||
gray: toEscapeSequence(90), // Alias for blackBright
|
||||
grey: toEscapeSequence(90), // Alias for blackBright
|
||||
redBright: toEscapeSequence(91),
|
||||
greenBright: toEscapeSequence(92),
|
||||
yellowBright: toEscapeSequence(93),
|
||||
blueBright: toEscapeSequence(94),
|
||||
magentaBright: toEscapeSequence(95),
|
||||
cyanBright: toEscapeSequence(96),
|
||||
whiteBright: toEscapeSequence(97),
|
||||
},
|
||||
|
||||
bgColor: {
|
||||
bgBlack: toEscapeSequence(40),
|
||||
bgRed: toEscapeSequence(41),
|
||||
bgGreen: toEscapeSequence(42),
|
||||
bgYellow: toEscapeSequence(43),
|
||||
bgBlue: toEscapeSequence(44),
|
||||
bgMagenta: toEscapeSequence(45),
|
||||
bgCyan: toEscapeSequence(46),
|
||||
bgWhite: toEscapeSequence(47),
|
||||
bgOrange: hexToBgColor("#FFA500"),
|
||||
|
||||
// Bright background colors
|
||||
bgBlackBright: toEscapeSequence(100),
|
||||
bgGray: toEscapeSequence(100), // Alias for bgBlackBright
|
||||
bgGrey: toEscapeSequence(100), // Alias for bgBlackBright
|
||||
bgRedBright: toEscapeSequence(101),
|
||||
bgGreenBright: toEscapeSequence(102),
|
||||
bgYellowBright: toEscapeSequence(103),
|
||||
bgBlueBright: toEscapeSequence(104),
|
||||
bgMagentaBright: toEscapeSequence(105),
|
||||
bgCyanBright: toEscapeSequence(106),
|
||||
bgWhiteBright: toEscapeSequence(107),
|
||||
},
|
||||
};
|
||||
|
||||
export function hexToColor(hex: HexValue): string {
|
||||
return toEscapeSequence(`38;5;${hexToAnsi256(hex)}`); // Foreground color
|
||||
}
|
||||
|
||||
export function hexToBgColor(hex: HexValue): string {
|
||||
return toEscapeSequence(`48;5;${hexToAnsi256(hex)}`); // Background color
|
||||
}
|
||||
|
||||
export type Modifier = keyof typeof styles.modifier;
|
||||
export type Color = keyof typeof styles.color;
|
||||
export type BGColor = keyof typeof styles.bgColor;
|
||||
3
api/libraries/logger/color/utilities.ts
Normal file
3
api/libraries/logger/color/utilities.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function toEscapeSequence(value: string | number): `\x1b[${string}m` {
|
||||
return `\x1b[${value}m`;
|
||||
}
|
||||
5
api/libraries/logger/config.ts
Normal file
5
api/libraries/logger/config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { getArgsVariable } from "~libraries/config/mod.ts";
|
||||
|
||||
export const config = {
|
||||
level: getArgsVariable("LOG_LEVEL", "info"),
|
||||
};
|
||||
19
api/libraries/logger/format/event-store.ts
Normal file
19
api/libraries/logger/format/event-store.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { EventValidationError } from "@valkyr/event-store";
|
||||
|
||||
import type { Level } from "../level.ts";
|
||||
import { getTracedAt } from "../stack.ts";
|
||||
|
||||
export function toEventStoreLog(arg: any, level: Level): any {
|
||||
if (arg instanceof EventValidationError) {
|
||||
const obj: any = {
|
||||
origin: "EventStore",
|
||||
message: arg.message,
|
||||
at: getTracedAt(arg.stack, "/api/domains"),
|
||||
data: arg.errors,
|
||||
};
|
||||
if (level === "debug") {
|
||||
obj.stack = arg.stack;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
18
api/libraries/logger/format/server.ts
Normal file
18
api/libraries/logger/format/server.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ServerError } from "@spec/relay";
|
||||
|
||||
import type { Level } from "../level.ts";
|
||||
import { getTracedAt } from "../stack.ts";
|
||||
|
||||
export function toServerLog(arg: any, level: Level): any {
|
||||
if (arg instanceof ServerError) {
|
||||
const obj: any = {
|
||||
message: arg.message,
|
||||
data: arg.data,
|
||||
at: getTracedAt(arg.stack, "/api/domains"),
|
||||
};
|
||||
if (level === "debug") {
|
||||
obj.stack = arg.stack;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
8
api/libraries/logger/level.ts
Normal file
8
api/libraries/logger/level.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const logLevel = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warning: 2,
|
||||
error: 3,
|
||||
};
|
||||
|
||||
export type Level = "debug" | "error" | "warning" | "info";
|
||||
95
api/libraries/logger/logger.ts
Normal file
95
api/libraries/logger/logger.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { chalk } from "./chalk.ts";
|
||||
import { type Level, logLevel } from "./level.ts";
|
||||
|
||||
export class Logger {
|
||||
#level: Level = "info";
|
||||
#config: Config;
|
||||
|
||||
constructor(config: Config) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
get #prefix(): [string?] {
|
||||
if (this.#config.prefix !== undefined) {
|
||||
return [chalk.bold(chalk.green(this.#config.prefix))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the highest logging level in the order of debug, info, warn, error.
|
||||
*
|
||||
* When value is 'info', info, warn and error will be logged and debug
|
||||
* will be ignored.
|
||||
*
|
||||
* @param value Highest log level.
|
||||
*/
|
||||
level(value: Level): this {
|
||||
this.#level = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new logger instance with the given name as prefix.
|
||||
*
|
||||
* @param name - Prefix name.
|
||||
*/
|
||||
prefix(name: string): Logger {
|
||||
return new Logger({ prefix: name, loggers: this.#config.loggers }).level(this.#level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a debug message to terminal.
|
||||
*/
|
||||
debug(...args: any[]) {
|
||||
if (this.#isLevelEnabled(0)) {
|
||||
console.log(new Date(), chalk.bold("Debug"), ...this.#prefix, ...args.map(this.#toFormattedArg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a info message to terminal.
|
||||
*/
|
||||
info(...args: any[]) {
|
||||
if (this.#isLevelEnabled(1)) {
|
||||
console.log(new Date(), chalk.bold(chalk.blue("Info")), ...this.#prefix, ...args.map(this.#toFormattedArg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a warning message to terminal.
|
||||
*/
|
||||
warn(...args: any[]) {
|
||||
if (this.#isLevelEnabled(2)) {
|
||||
console.log(new Date(), chalk.bold(chalk.orange("Warning")), ...this.#prefix, ...args.map(this.#toFormattedArg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a errpr message to terminal.
|
||||
*/
|
||||
error(...args: any[]) {
|
||||
if (this.#isLevelEnabled(3)) {
|
||||
console.log(new Date(), chalk.bold(chalk.red("Error")), ...this.#prefix, ...args.map(this.#toFormattedArg));
|
||||
}
|
||||
}
|
||||
|
||||
#isLevelEnabled(level: 0 | 1 | 2 | 3): boolean {
|
||||
return level >= logLevel[this.#level];
|
||||
}
|
||||
|
||||
#toFormattedArg = (arg: any): string => {
|
||||
for (const logger of this.#config.loggers) {
|
||||
const res = logger(arg, this.#level);
|
||||
if (res !== undefined) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
};
|
||||
}
|
||||
|
||||
type Config = {
|
||||
prefix?: string;
|
||||
loggers: ((arg: any, level: Level) => any)[];
|
||||
};
|
||||
7
api/libraries/logger/mod.ts
Normal file
7
api/libraries/logger/mod.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { toEventStoreLog } from "./format/event-store.ts";
|
||||
import { toServerLog } from "./format/server.ts";
|
||||
import { Logger } from "./logger.ts";
|
||||
|
||||
export const logger = new Logger({
|
||||
loggers: [toServerLog, toEventStoreLog],
|
||||
});
|
||||
20
api/libraries/logger/stack.ts
Normal file
20
api/libraries/logger/stack.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Fetch the most closest relevant error from the local code base so it can
|
||||
* be more easily traced to its source.
|
||||
*
|
||||
* @param stack - Error stack.
|
||||
* @param search - Relevant stack line search value.
|
||||
*/
|
||||
export function getTracedAt(stack: string | undefined, search: string): string | undefined {
|
||||
if (stack === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const firstMatch = stack.split("\n").find((line) => line.includes(search));
|
||||
if (firstMatch === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return firstMatch
|
||||
.replace(/^.*?(file:\/\/\/)/, "$1")
|
||||
.replace(/\)$/, "")
|
||||
.trim();
|
||||
}
|
||||
Reference in New Issue
Block a user