Feature logs

Deskripsi:
- Fitur baru log untuk melihat error pada server upload
- Baru di terapkan di create profile
This commit is contained in:
2024-12-16 15:41:02 +08:00
parent 4dd98c6183
commit 0bdc25b1e6
19 changed files with 815 additions and 349 deletions

37
src/util/backendLogger.ts Normal file
View File

@@ -0,0 +1,37 @@
// src/utils/backendLogger.ts
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import path from "path";
const backendLogger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
// Error logs
new DailyRotateFile({
filename: path.join("logs/backend/error-%DATE%.log"),
datePattern: "YYYY-MM-DD",
level: "error",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
}),
// Combined logs
new DailyRotateFile({
filename: path.join("logs/backend/combined-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
}),
// Console output in development
...(process.env.NODE_ENV !== "production"
? [new winston.transports.Console()]
: []),
],
});
export default backendLogger;

87
src/util/clientLogger.ts Normal file
View File

@@ -0,0 +1,87 @@
// src/utils/clientLogger.ts
interface LogEntry {
level: "info" | "warn" | "error";
message: string;
data?: any;
timestamp?: string;
}
class ClientLogger {
private queue: LogEntry[] = [];
private readonly maxQueueSize: number = 10;
private readonly apiEndpoint: string = "/api/logs";
private isSending: boolean = false;
private async sendLogs(): Promise<void> {
if (this.isSending || this.queue.length === 0) return;
this.isSending = true;
const logsToSend = [...this.queue];
this.queue = [];
try {
const response = await fetch(this.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(logsToSend),
});
if (!response.ok) {
console.error("Failed to send logs:", response.statusText);
// Restore logs to queue if send failed
this.queue = [...logsToSend, ...this.queue];
}
} catch (error) {
console.error("Error sending logs:", error);
// Restore logs to queue if send failed
this.queue = [...logsToSend, ...this.queue];
} finally {
this.isSending = false;
}
}
private addToQueue(entry: LogEntry): void {
this.queue.push({
...entry,
timestamp: new Date().toISOString(),
});
if (this.queue.length >= this.maxQueueSize) {
this.sendLogs();
}
}
public info(message: string, data?: any): void {
this.addToQueue({ level: "info", message, data });
}
public warn(message: string, data?: any): void {
this.addToQueue({ level: "warn", message, data });
// Send immediately for warnings
this.sendLogs();
}
public error(message: string, error?: Error | any): void {
const errorData =
error instanceof Error
? {
name: error.name,
message: error.message,
stack: error.stack,
}
: error;
this.addToQueue({ level: "error", message, data: errorData });
// Send immediately for errors
this.sendLogs();
}
// Flush remaining logs (useful when page is about to unload)
public flush(): void {
this.sendLogs();
}
}
export const clientLogger = new ClientLogger();

101
src/util/frontend-logger.ts Normal file
View File

@@ -0,0 +1,101 @@
// utils/frontend-logger.ts
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import path from "path";
// Define log levels and their priorities
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
// Define interface for log entries
export interface LogEntry {
level: keyof typeof levels;
message: string;
data?: any;
timestamp?: string;
userAgent?: string;
ip?: string;
url?: string;
}
// Custom format for log entries
const logFormat = winston.format.combine(
winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
winston.format.metadata({ fillExcept: ["message", "level", "timestamp"] }),
winston.format.json()
);
// Create the logger instance
const frontendLogger: winston.Logger = winston.createLogger({
levels,
level: process.env.LOG_LEVEL || "info",
format: logFormat,
transports: [
// Daily Rotate File for errors
new DailyRotateFile({
filename: path.join(process.cwd(), "logs/frontend/error-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
level: "error",
format: logFormat,
}),
// Daily Rotate File for all logs
new DailyRotateFile({
filename: path.join(process.cwd(), "logs/frontend/combined-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
format: logFormat,
}),
],
// Handle errors from the logger itself
exitOnError: false,
});
// Add console transport in development
if (process.env.NODE_ENV !== "production") {
frontendLogger.add(
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
})
);
}
// Helper functions for type-safe logging
export function logError(message: string, data?: any) {
frontendLogger.error(message, { data });
}
export function logWarn(message: string, data?: any) {
frontendLogger.warn(message, { data });
}
export function logInfo(message: string, data?: any) {
frontendLogger.info(message, { data });
}
export function logDebug(message: string, data?: any) {
frontendLogger.debug(message, { data });
}
// Helper function for dynamic logging
export function log(entry: LogEntry) {
const { level, message, ...metadata } = entry;
frontendLogger[level](message, metadata);
}
// Export the logger instance as default
export default frontendLogger;