feat(logging): enhance logging functionality with detailed request and error logging
Deploy Application / deploy (push) Successful in 46s
Details
Deploy Application / deploy (push) Successful in 46s
Details
This commit is contained in:
parent
7b08aafa72
commit
d3c269505a
|
|
@ -320,11 +320,11 @@ model transactions_lines {
|
|||
line_no Int @default(1)
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
c_payment_id Decimal? @db.Decimal(10, 0)
|
||||
documentno String? @db.VarChar(255)
|
||||
date DateTime? @db.Timestamptz(6)
|
||||
invoicepartner_id String? @db.Uuid
|
||||
documentno String? @db.VarChar(255)
|
||||
invoicepartner invoicepartner? @relation(fields: [invoicepartner_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
database database? @relation(fields: [db_id], references: [db_id], onDelete: NoAction, onUpdate: NoAction)
|
||||
invoicepartner invoicepartner? @relation(fields: [invoicepartner_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
transactions transactions @relation(fields: [transaction_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
|
|
@ -342,19 +342,19 @@ model invoicepartner {
|
|||
id String @id(map: "invoicePartner_pkey") @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
costumerno String @db.VarChar(50)
|
||||
c_bpartner_id Decimal? @db.Decimal(10, 0)
|
||||
documentno String? @db.VarChar(30)
|
||||
db_id String? @db.Uuid
|
||||
c_invoice_id Decimal? @db.Decimal(10, 0)
|
||||
is_active Boolean @default(true)
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
is_pay Boolean @default(false)
|
||||
grandtotal Decimal? @db.Decimal(20, 2)
|
||||
amount Decimal? @db.Decimal(20, 2)
|
||||
rv_openitem_id String? @db.Uuid
|
||||
documentno String? @db.VarChar(30)
|
||||
grandtotal Decimal? @db.Decimal(20, 2)
|
||||
invoiceLines invoice_lines[]
|
||||
rv_openitem rv_openitem? @relation(fields: [rv_openitem_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "invoicePartner_rv_openitem_id_fkey")
|
||||
transactionsLines transactions_lines[]
|
||||
invoiceLines invoice_lines[]
|
||||
}
|
||||
|
||||
model invoice {
|
||||
|
|
@ -390,7 +390,6 @@ model invoice_lines {
|
|||
description String? @db.VarChar(255)
|
||||
amount Decimal? @db.Decimal(20, 2)
|
||||
c_invoice_id Decimal? @db.Decimal(10, 0)
|
||||
invoicepartner_id String? @db.Uuid
|
||||
invoice_id String? @db.Uuid
|
||||
db_id String? @db.Uuid
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
|
|
@ -400,9 +399,10 @@ model invoice_lines {
|
|||
line_no Int @default(1)
|
||||
billcode String? @db.VarChar(10)
|
||||
billname String? @db.VarChar(50)
|
||||
invoicepartner invoicepartner? @relation(fields: [invoicepartner_id], references: [id])
|
||||
invoicepartner_id String? @db.Uuid
|
||||
database database? @relation(fields: [db_id], references: [db_id], onDelete: NoAction, onUpdate: NoAction)
|
||||
invoice invoice? @relation(fields: [invoice_id], references: [id])
|
||||
invoicepartner invoicepartner? @relation(fields: [invoicepartner_id], references: [id])
|
||||
}
|
||||
|
||||
model sequences {
|
||||
|
|
@ -424,3 +424,21 @@ model whitelistcors {
|
|||
is_active String @default("Y") @db.Char(1)
|
||||
tenants tenants @relation(fields: [tenant_id], references: [tenant_id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model roles {
|
||||
role_id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String @unique @db.VarChar(100)
|
||||
description String? @db.VarChar(255)
|
||||
is_active Boolean @default(true)
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
}
|
||||
|
||||
model user_roles {
|
||||
user_role_id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
user_id String @db.Uuid
|
||||
role_id String @db.Uuid
|
||||
is_active Boolean @default(true)
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { Request, Response } from "express";
|
||||
import prisma from "../prisma";
|
||||
import { hash } from "bcryptjs";
|
||||
import { createLogger, LogLevel } from "../lib/logger";
|
||||
|
||||
// Logger untuk database controller
|
||||
const dbLogger = createLogger({
|
||||
logDir: "./logs/log",
|
||||
minLevel: LogLevel.INFO,
|
||||
});
|
||||
|
||||
type DbRequest = {
|
||||
table: string;
|
||||
|
|
@ -11,12 +18,22 @@ type DbRequest = {
|
|||
export class DbController {
|
||||
async handle(req: Request, res: Response) {
|
||||
const body: DbRequest = req.body;
|
||||
if (!body || !body.table || !body.action)
|
||||
if (!body || !body.table || !body.action) {
|
||||
dbLogger.warning("Invalid DB request - missing table or action", {
|
||||
body,
|
||||
// ip: req.ip,
|
||||
});
|
||||
return res.status(400).json({ error: "table and action required" });
|
||||
}
|
||||
|
||||
const model = (prisma as any)[body.table];
|
||||
if (!model)
|
||||
if (!model) {
|
||||
dbLogger.warning("Unknown table access attempt", {
|
||||
table: body.table,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(400).json({ error: `unknown table ${body.table}` });
|
||||
}
|
||||
|
||||
// Only allow safe actions
|
||||
const allowed = [
|
||||
|
|
@ -29,8 +46,14 @@ export class DbController {
|
|||
"upsert",
|
||||
"count",
|
||||
];
|
||||
if (!allowed.includes(body.action))
|
||||
if (!allowed.includes(body.action)) {
|
||||
dbLogger.warning("Disallowed action attempt", {
|
||||
table: body.table,
|
||||
action: body.action,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(400).json({ error: "action not allowed" });
|
||||
}
|
||||
|
||||
// Tables that require access checks
|
||||
const protectedTables = new Set([
|
||||
|
|
@ -91,6 +114,11 @@ export class DbController {
|
|||
if (body.table === "users" && body.action === "create") {
|
||||
const payload = body.data?.data || body.data || {};
|
||||
if (!payload.username || !payload.password) {
|
||||
dbLogger.warning("User creation missing credentials", {
|
||||
hasUsername: !!payload.username,
|
||||
hasPassword: !!payload.password,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(400).json({
|
||||
error: "username and password are required for users.create",
|
||||
});
|
||||
|
|
@ -100,8 +128,13 @@ export class DbController {
|
|||
const existing = await prisma.users.findFirst({
|
||||
where: { username: payload.username },
|
||||
});
|
||||
if (existing)
|
||||
if (existing) {
|
||||
dbLogger.warning("Duplicate username creation attempt", {
|
||||
username: payload.username,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(409).json({ error: "username already exists" });
|
||||
}
|
||||
|
||||
const hashed = await hash(payload.password, 10);
|
||||
const createData = {
|
||||
|
|
@ -112,6 +145,11 @@ export class DbController {
|
|||
// Ensure payload is nested under data if using Prisma create syntax
|
||||
const createArg = { data: createData };
|
||||
const result = await model.create(createArg);
|
||||
dbLogger.info("User created successfully", {
|
||||
username: payload.username,
|
||||
userId: result.user_id,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(201).json({ result });
|
||||
}
|
||||
|
||||
|
|
@ -120,10 +158,16 @@ export class DbController {
|
|||
// Expecting Prisma update signature: { where: {...}, data: {...} }
|
||||
const where = body.data?.where;
|
||||
const data = body.data?.data || body.data;
|
||||
if (!where || !data)
|
||||
if (!where || !data) {
|
||||
dbLogger.warning("User update missing where or data", {
|
||||
hasWhere: !!where,
|
||||
hasData: !!data,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "where and data are required for users.update" });
|
||||
}
|
||||
|
||||
// If username is being changed, ensure it's not taken by another user
|
||||
if (data.username) {
|
||||
|
|
@ -136,6 +180,10 @@ export class DbController {
|
|||
existing.user_id !== where.id &&
|
||||
existing.user_id !== where.userId
|
||||
) {
|
||||
dbLogger.warning("Duplicate username update attempt", {
|
||||
username: data.username,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(409).json({ error: "username already exists" });
|
||||
}
|
||||
}
|
||||
|
|
@ -147,13 +195,28 @@ export class DbController {
|
|||
}
|
||||
|
||||
const result = await model.update({ where, data });
|
||||
dbLogger.info("User updated successfully", {
|
||||
userId: result.user_id,
|
||||
updatedFields: Object.keys(data),
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.json({ result });
|
||||
}
|
||||
|
||||
const result = await model[body.action](body.data || {});
|
||||
dbLogger.info("Database operation completed", {
|
||||
table: body.table,
|
||||
action: body.action,
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.json({ result });
|
||||
} catch (err) {
|
||||
console.error("DB dispatch error", err);
|
||||
dbLogger.error("Database operation error", {
|
||||
table: body.table,
|
||||
action: body.action,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
ip: req.ip,
|
||||
});
|
||||
return res.status(500).json({
|
||||
error: "internal error",
|
||||
detail: err instanceof Error ? err.message : String(err),
|
||||
|
|
|
|||
|
|
@ -184,11 +184,11 @@ export default class LogController {
|
|||
files: allFiles,
|
||||
};
|
||||
|
||||
logAccessLogger.info("Log list accessed", {
|
||||
path: safePath,
|
||||
userId: (req as any).user?.user_id,
|
||||
fileCount: result.files.length,
|
||||
});
|
||||
// logAccessLogger.info("Log list accessed", {
|
||||
// path: safePath,
|
||||
// userId: (req as any).user?.user_id,
|
||||
// fileCount: result.files.length,
|
||||
// });
|
||||
|
||||
return res.json(result);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
import { LogLevel, createLogger, getDefaultLogger } from "./logger";
|
||||
|
||||
// Example 1: Using default logger (logs to ./logs folder)
|
||||
const defaultLog = getDefaultLogger();
|
||||
|
||||
console.log("=== Default Logger Example ===");
|
||||
defaultLog.debug("a debug message");
|
||||
defaultLog.info("a info message");
|
||||
defaultLog.notice("a notice message");
|
||||
defaultLog.warning("a warning message");
|
||||
defaultLog.error("a error message");
|
||||
defaultLog.critical("a critical message");
|
||||
defaultLog.alert("a alert message");
|
||||
defaultLog.emergency("a emergency message");
|
||||
|
||||
// Example 2: Custom logger with specific folder
|
||||
const customLog = createLogger({
|
||||
logDir: "./custom-logs",
|
||||
minLevel: LogLevel.WARNING, // Only log WARNING and above
|
||||
filePrefix: "payment-api",
|
||||
});
|
||||
|
||||
console.log("\n=== Custom Logger Example ===");
|
||||
customLog.debug("this will not be logged (below min level)");
|
||||
customLog.info("this will not be logged (below min level)");
|
||||
customLog.warning("a warning message that will be logged");
|
||||
customLog.error("a error message that will be logged");
|
||||
customLog.critical("a critical message that will be logged");
|
||||
|
||||
// Example 3: Logger with additional arguments
|
||||
const apiLog = createLogger({
|
||||
logDir: "./api-logs",
|
||||
filePrefix: "mandiri-api",
|
||||
maxFileSizeMB: 5,
|
||||
});
|
||||
|
||||
console.log("\n=== API Logger Example with Objects ===");
|
||||
apiLog.info("Payment received", {
|
||||
amount: 100000,
|
||||
virtualAccount: "1234567890",
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
apiLog.error("Payment failed", {
|
||||
error: "Insufficient balance",
|
||||
virtualAccount: "1234567890",
|
||||
attemptedAmount: 500000,
|
||||
});
|
||||
|
||||
// Example 4: Different log levels with context
|
||||
const bankLog = createLogger({
|
||||
logDir: "./bank-integration",
|
||||
filePrefix: "bank-sync",
|
||||
});
|
||||
|
||||
console.log("\n=== Bank Integration Logger Example ===");
|
||||
bankLog.debug("Starting database sync");
|
||||
bankLog.info("Connected to bank API");
|
||||
bankLog.notice("Processing 1000 transactions");
|
||||
bankLog.warning("API rate limit approaching");
|
||||
bankLog.error("Connection timeout, retrying...");
|
||||
bankLog.critical("Multiple failures detected");
|
||||
bankLog.alert("System performance degraded");
|
||||
bankLog.emergency("All bank connections failed");
|
||||
|
||||
console.log(
|
||||
"\nLogger examples completed. Check the log files in respective directories."
|
||||
);
|
||||
|
|
@ -29,7 +29,7 @@ export class Logger {
|
|||
logDir: config.logDir,
|
||||
minLevel: config.minLevel ?? LogLevel.DEBUG,
|
||||
maxFileSizeMB: config.maxFileSizeMB ?? 10,
|
||||
filePrefix: config.filePrefix ?? "app",
|
||||
filePrefix: config.filePrefix ?? "",
|
||||
};
|
||||
|
||||
// Ensure log directory exists
|
||||
|
|
@ -48,10 +48,10 @@ export class Logger {
|
|||
|
||||
if (this.currentDate !== dateStr) {
|
||||
this.currentDate = dateStr;
|
||||
this.currentLogFile = path.join(
|
||||
this.config.logDir,
|
||||
`${this.config.filePrefix}-${dateStr}.log`
|
||||
);
|
||||
const fileName = this.config.filePrefix
|
||||
? `${this.config.filePrefix}-${dateStr}.log`
|
||||
: `${dateStr}.log`;
|
||||
this.currentLogFile = path.join(this.config.logDir, fileName);
|
||||
}
|
||||
|
||||
return this.currentLogFile!;
|
||||
|
|
|
|||
|
|
@ -89,12 +89,12 @@ export async function authenticateLogAccess(
|
|||
session_token: token,
|
||||
};
|
||||
|
||||
authLogger.info("Log access authenticated", {
|
||||
userId: req.user.user_id,
|
||||
username: req.user.username,
|
||||
ip: req.ip,
|
||||
path: req.path,
|
||||
});
|
||||
// authLogger.info("Log access authenticated", {
|
||||
// userId: req.user.user_id,
|
||||
// username: req.user.username,
|
||||
// ip: req.ip,
|
||||
// path: req.path,
|
||||
// });
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
|
|
@ -201,7 +201,7 @@ export function auditLogAccess() {
|
|||
},
|
||||
});
|
||||
|
||||
authLogger.info("Log access audit", auditData);
|
||||
// authLogger.info("Log access audit", auditData);
|
||||
} catch (err) {
|
||||
authLogger.error("Failed to create audit log", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
|
|
|
|||
Loading…
Reference in New Issue