mongoku
Version:
[](https://github.com/huggingface/Mongoku/actions/workflows/ci.yml)
185 lines (164 loc) • 4.18 kB
text/typescript
import { env } from "$env/dynamic/private";
import { contextStore } from "$lib/server/contextStore";
import { inspect } from "node:util";
type LogLevel = "debug" | "info" | "warn" | "error";
/**
* Logger class that wraps console methods for centralized logging
*/
class Logger {
private structuredLogging: boolean;
private logHeaders: string[];
constructor() {
this.structuredLogging = env.MONGOKU_STRUCTURED_LOG === "true";
this.logHeaders = env.MONGOKU_LOG_HEADERS
? env.MONGOKU_LOG_HEADERS.split(",")
.map((h) => h.trim().toLowerCase())
.filter(Boolean)
: [];
}
/**
* Get request context information from the current request
*/
private getRequestContext() {
const event = contextStore.getStore();
if (!event) {
return undefined;
}
const url = new URL(event.request.url);
const context: Record<string, unknown> = {
requestId: event.locals.requestId,
method: event.request.method,
url: url.pathname + url.search,
ip: event.getClientAddress(),
userAgent: event.request.headers.get("user-agent") || undefined,
};
// Add custom headers if configured
if (this.logHeaders.length > 0) {
const headers: Record<string, string> = {};
for (const headerName of this.logHeaders) {
const value = event.request.headers.get(headerName);
if (value) {
headers[headerName] = value;
}
}
if (Object.keys(headers).length > 0) {
context.headers = headers;
}
}
return context;
}
/**
* Format arguments for logging
*/
private formatArgs(args: unknown[]): string {
return args
.map((arg) => {
if (arg instanceof Error) {
return (
(arg.stack ?? `${arg.name}: ${arg.message}`) +
(arg.cause instanceof Error
? `\ncause: ${arg.cause.stack ?? `${arg.cause.name}: ${arg.cause.message}`}`
: "")
);
} else if (typeof arg === "string") {
return arg;
} else {
return inspect(arg, { depth: 20, colors: !this.structuredLogging });
}
})
.join(" ");
}
/**
* Internal log method that handles both structured and unstructured logging
*/
private _log(level: LogLevel, args: unknown[]): void {
const message = this.formatArgs(args);
if (this.structuredLogging) {
const logEntry = {
level,
time: new Date().toISOString(),
message,
...this.getRequestContext(),
};
console.log(JSON.stringify(logEntry));
} else {
// Use appropriate console method based on level
switch (level) {
case "error":
console.error(message);
break;
case "warn":
console.warn(message);
break;
case "debug":
console.debug(message);
break;
default:
console.log(message);
}
}
}
/**
* Log an informational message
*/
log(...args: unknown[]): void {
this._log("info", args);
}
/**
* Log an informational message (alias for log)
*/
info(...args: unknown[]): void {
this._log("info", args);
}
/**
* Log an error message
*/
error(...args: unknown[]): void {
this._log("error", args);
}
/**
* Log a warning message
*/
warn(...args: unknown[]): void {
this._log("warn", args);
}
/**
* Log a debug message
*/
debug(...args: unknown[]): void {
this._log("debug", args);
}
/**
* Log an HTTP request
*/
logRequest(statusCode: number, durationMs: number): void {
const duration = Math.round(durationMs * 100) / 100; // Round to 2 decimal places
if (this.structuredLogging) {
const logEntry = {
level: "info" as LogLevel,
time: new Date().toISOString(),
type: "request",
statusCode,
duration,
...this.getRequestContext(),
};
console.log(JSON.stringify(logEntry));
} else {
// Simple console log for non-structured mode
const event = contextStore.getStore();
if (event) {
const url = new URL(event.request.url);
const urlPath = url.pathname + url.search;
const method = event.request.method;
const message = `${statusCode} ${method} ${urlPath} ${duration}ms`;
if (statusCode >= 400) {
console.error(message);
} else {
console.log(message);
}
}
}
}
}
// Export a singleton instance
export const logger = new Logger();