This package has been archived, and as such it is read-only.
This release is 1 version behind 24.6.2 — the latest version of @rivet-gg/actor. Jump to latest
🔩 Rivet Actors have built-in RPC, state, and events — the easiest way to build modern applications.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235import { type LevelName, type LogLevel, LogLevels } from "jsr:@std/log@^0.224.11"; export type LogEntry = [string, LogValue]; export type LogValue = string | number | boolean | null | undefined; const LOG_LEVEL_COLORS: Record<LogLevel, string> = { [LogLevels.CRITICAL]: "\x1b[31m", // Red [LogLevels.ERROR]: "\x1b[31m", // Red [LogLevels.WARN]: "\x1b[33m", // Yellow [LogLevels.INFO]: "\x1b[32m", // Green [LogLevels.DEBUG]: "\x1b[36m", // Cyan [LogLevels.NOTSET]: "\x1b[35m", // Magenta }; const RESET_COLOR = "\x1b[0m"; /** Builds the full entries for a log. */ export function buildLogEntries( level: LogLevel, message: string, ...data: LogEntry[] ): LogEntry[] { return [ ["ts", formatTimestamp(new Date())], ["level", level], ["msg", message], ...data, ]; } /** * Serializes logfmt line using orderer parameters. * * We use varargs because it's ordered & it has less overhead than an object. * * ## Styling Methodology * * The three things you need to know for every log line is the level, the * message, and who called it. These properties are highlighted in different colros * and sorted in th eorder that you usually read them. * * Once you've found a log line you care about, then you want to find the * property you need to see. The property names are bolded and the default color * while the rest of the data is dim. This lets you scan to find the property * name quickly then look closer to read the data associated with the * property. */ export function stringify(...data: LogEntry[]) { let line = ""; for (let i = 0; i < data.length; i++) { const [key, valueRaw] = data[i]; let isNull = false; let valueString: string; if (valueRaw == null) { isNull = true; valueString = ""; } else { valueString = valueRaw.toString(); } const needsQuoting = valueString.indexOf(" ") > -1 || valueString.indexOf("=") > -1; const needsEscaping = valueString.indexOf('"') > -1 || valueString.indexOf("\\") > -1; valueString = valueString.replace(/\n/g, "\\n"); if (needsEscaping) valueString = valueString.replace(/["\\]/g, "\\$&"); if (needsQuoting || needsEscaping) valueString = `"${valueString}"`; if (valueString === "" && !isNull) valueString = '""'; if (LOGGER_CONFIG.enableColor) { // With color // Special message colors let color = "\x1b[2m"; if (key === "level") { const level = LogLevels[valueString as LevelName]; const levelColor = LOG_LEVEL_COLORS[level]; if (levelColor) { color = levelColor; } } else if (key === "msg") { color = "\x1b[32m"; } else if (key === "trace") { color = "\x1b[34m"; } // Format line line += `\x1b[0m\x1b[1m${key}\x1b[0m\x1b[2m=\x1b[0m${color}${valueString}${RESET_COLOR}`; } else { // No color line += `${key}=${valueString}`; } if (i !== data.length - 1) { line += " "; } } return line; } export function formatTimestamp(date: Date): string { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, "0"); const day = String(date.getUTCDate()).padStart(2, "0"); const hours = String(date.getUTCHours()).padStart(2, "0"); const minutes = String(date.getUTCMinutes()).padStart(2, "0"); const seconds = String(date.getUTCSeconds()).padStart(2, "0"); const milliseconds = String(date.getUTCMilliseconds()).padStart(3, "0"); return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}Z`; } export function castToLogValue(v: unknown): LogValue { if ( typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null || v === undefined ) { return v; } if (v instanceof Error) { //args.push(...errorToLogEntries(k, v)); return String(v); } try { return JSON.stringify(v); } catch { return "[cannot stringify]"; } } // MARK: Config interface GlobalLoggerConfig { enableColor: boolean; enableSpreadObject: boolean; enableErrorStack: boolean; } export const LOGGER_CONFIG: GlobalLoggerConfig = { enableColor: false, enableSpreadObject: false, enableErrorStack: false, }; // MARK: Utils /** * Converts an object in to an easier to read KV of entries. */ export function spreadObjectToLogEntries( base: string, data: unknown, ): LogEntry[] { if ( LOGGER_CONFIG.enableSpreadObject && typeof data === "object" && !Array.isArray(data) && data !== null && Object.keys(data).length !== 0 && Object.keys(data).length < 16 ) { const logData: LogEntry[] = []; for (const key in data) { // logData.push([`${base}.${key}`, JSON.stringify((data as any)[key])]); logData.push( // biome-ignore lint/suspicious/noExplicitAny: unknown type ...spreadObjectToLogEntries(`${base}.${key}`, (data as any)[key]), ); } return logData; } else { return [[base, JSON.stringify(data)]]; } } export function errorToLogEntries(base: string, error: unknown): LogEntry[] { if (error instanceof Error) { return [ //[`${base}.name`, error.name], [`${base}.message`, error.message], ...(LOGGER_CONFIG.enableErrorStack && error.stack ? [[`${base}.stack`, formatStackTrace(error.stack)] as LogEntry] : []), ...(error.cause ? errorToLogEntries(`${base}.cause`, error.cause) : []), ]; } else { return [[base, `${error}`]]; } } // export function errorToLogEntries(base: string, error: unknown): LogEntry[] { // if (error instanceof RuntimeError) { // return [ // [`${base}.code`, error.code], // [`${base}.description`, error.errorConfig?.description], // [`${base}.module`, error.moduleName], // ...(error.trace ? [[`${base}.trace`, stringifyTrace(error.trace)] as LogEntry] : []), // ...(LOGGER_CONFIG.enableErrorStack && error.stack // ? [[`${base}.stack`, formatStackTrace(error.stack)] as LogEntry] // : []), // ...(error.meta ? [[`${base}.meta`, JSON.stringify(error.meta)] as LogEntry] : []), // ...(error.cause ? errorToLogEntries(`${base}.cause`, error.cause) : []), // ]; // } else if (error instanceof Error) { // return [ // [`${base}.name`, error.name], // [`${base}.message`, error.message], // ...(LOGGER_CONFIG.enableErrorStack && error.stack // ? [[`${base}.stack`, formatStackTrace(error.stack)] as LogEntry] // : []), // ...(error.cause ? errorToLogEntries(`${base}.cause`, error.cause) : []), // ]; // } else { // return [ // [base, `${error}`], // ]; // } // } /** * Formats a JS stack trace in to a legible one-liner. */ function formatStackTrace(stackTrace: string): string { const regex = /at (.+?)$/gm; const matches = [...stackTrace.matchAll(regex)]; // Reverse array since the stack goes from top level -> bottom level matches.reverse(); return matches.map((match) => match[1].trim()).join(" > "); }