Files
sexy/packages/frontend/src/lib/logger.ts
Sebastian Krüger efc7624ba3
All checks were successful
Build and Push Backend Image / build (push) Successful in 46s
Build and Push Frontend Image / build (push) Successful in 5m12s
style: apply prettier formatting to all files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 22:27:54 +01:00

146 lines
3.8 KiB
TypeScript

/**
* Server-side logging utility for sexy.pivoine.art
* Provides structured logging with context and request tracing
*/
export type LogLevel = "debug" | "info" | "warn" | "error";
interface LogContext {
timestamp: string;
level: LogLevel;
message: string;
context?: Record<string, unknown>;
requestId?: string;
userId?: string;
path?: string;
method?: string;
duration?: number;
error?: Error;
}
class Logger {
private isDev = process.env.NODE_ENV === "development";
private serviceName = "sexy.pivoine.art";
private formatLog(ctx: LogContext): string {
const { timestamp, level, message, context, requestId, userId, path, method, duration, error } =
ctx;
const parts = [
`[${timestamp}]`,
`[${level.toUpperCase()}]`,
requestId ? `[${requestId}]` : null,
method && path ? `${method} ${path}` : null,
message,
userId ? `user=${userId}` : null,
duration !== undefined ? `${duration}ms` : null,
].filter(Boolean);
let logString = parts.join(" ");
if (context && Object.keys(context).length > 0) {
logString += " " + JSON.stringify(context);
}
if (error) {
logString += `\n Error: ${error.message}\n Stack: ${error.stack}`;
}
return logString;
}
private log(level: LogLevel, message: string, meta: Partial<LogContext> = {}) {
const timestamp = new Date().toISOString();
const logContext: LogContext = {
timestamp,
level,
message,
...meta,
};
const formattedLog = this.formatLog(logContext);
switch (level) {
case "debug":
if (this.isDev) console.debug(formattedLog);
break;
case "info":
console.info(formattedLog);
break;
case "warn":
console.warn(formattedLog);
break;
case "error":
console.error(formattedLog);
break;
}
}
debug(message: string, meta?: Partial<LogContext>) {
this.log("debug", message, meta);
}
info(message: string, meta?: Partial<LogContext>) {
this.log("info", message, meta);
}
warn(message: string, meta?: Partial<LogContext>) {
this.log("warn", message, meta);
}
error(message: string, meta?: Partial<LogContext>) {
this.log("error", message, meta);
}
// Request logging helper
request(method: string, path: string, meta: Partial<LogContext> = {}) {
this.info("→ Request received", { method, path, ...meta });
}
response(
method: string,
path: string,
status: number,
duration: number,
meta: Partial<LogContext> = {},
) {
const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
this.log(level, `← Response ${status}`, { method, path, duration, ...meta });
}
// Authentication logging
auth(action: string, success: boolean, meta: Partial<LogContext> = {}) {
this.info(`🔐 Auth: ${action} ${success ? "success" : "failed"}`, meta);
}
// Startup logging
startup() {
const env = {
NODE_ENV: process.env.NODE_ENV,
PUBLIC_API_URL: process.env.PUBLIC_API_URL,
PUBLIC_URL: process.env.PUBLIC_URL,
PUBLIC_UMAMI_ID: process.env.PUBLIC_UMAMI_ID ? "***set***" : "not set",
PUBLIC_UMAMI_SCRIPT: process.env.PUBLIC_UMAMI_SCRIPT || "not set",
PORT: process.env.PORT || "3000",
HOST: process.env.HOST || "0.0.0.0",
};
console.log("\n" + "=".repeat(60));
console.log("🍑 sexy.pivoine.art - Server Starting 💜");
console.log("=".repeat(60));
console.log("\n📋 Environment Configuration:");
Object.entries(env).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
console.log("\n" + "=".repeat(60) + "\n");
}
}
// Singleton instance
export const logger = new Logger();
// Generate request ID
export function generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}