@kingdiablo/auditor
Version:
A lightweight and customizable audit logger for Node.js apps. Tracks database changes, errors, and user actions with support for external loggers like Winston or Pino.
1,001 lines (991 loc) • 32.7 kB
JavaScript
import "./chunk-ZD7AOCMD.mjs";
// src/core/Auditor.ts
import chalk4 from "chalk";
// src/database/mongodb/config.ts
import chalk2 from "chalk";
// src/core/AppConfigs.ts
var AppConfig = /* @__PURE__ */ (() => {
let logFilePath = "";
let fileConfig;
let auditOption;
let isInitialized = false;
let captureSystemErrors = true;
let useUI = false;
let defaultFileConfigs;
let framework = "express";
return {
setAuditOption(options) {
auditOption = options;
},
getAuditOption() {
return auditOption;
},
setLogFilePath(value) {
logFilePath = value;
},
getLogFilePath() {
return logFilePath;
},
setFileConfig(config) {
fileConfig = config;
},
getFileConfig() {
return fileConfig;
},
setDefaultFileConfig(config) {
defaultFileConfigs = config;
},
getDefaultFileConfig() {
return defaultFileConfigs;
},
setIsInitialized(value) {
isInitialized = value;
},
getIsInitialized() {
return isInitialized;
},
setCaptureSystemErrors(value) {
captureSystemErrors = value;
},
getCaptureSystemErrors() {
return captureSystemErrors;
},
setFrameWork(value) {
framework = value;
},
getFrameWork() {
return framework;
},
setUseUI(value) {
useUI = value;
},
getUseUI() {
return useUI;
}
};
})();
// src/utils/helper.ts
import chalk from "chalk";
import fs from "fs";
import path from "path";
import crypto from "crypto";
import { createRequire } from "module";
var getTimeStamp = () => (/* @__PURE__ */ new Date()).toISOString();
var getUserId = (req) => {
try {
if ("user" in req) {
const user = req?.user;
if (!user) return "unknown";
if ("id" in user) {
return user;
} else return "unknown";
} else "unknown";
return "unknown";
} catch (error) {
return "unknown";
}
};
var handleLog = (fileConfig, content) => {
const config = AppConfig.getAuditOption();
if (config.destinations?.includes("console"))
logAuditEvent(content);
if (config.destinations?.includes("file"))
logAuditEvent(content, fileConfig);
};
var saveToFile = (file, content) => {
const config = AppConfig.getAuditOption();
try {
fs.appendFileSync(file.fullPath, `${JSON.stringify(content)}
`, { encoding: "utf-8" });
} catch (error) {
config?.logger?.error(chalk.red("Failed to save log to file"));
}
};
var createFile = (config) => {
const fullPath = path.join(process.cwd(), config.folderName);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
const dir = path.join(fullPath, config.fileName);
return dir;
};
var logAuditEvent = (content, file) => {
const config = AppConfig.getAuditOption();
if (file) {
saveToFile(file, content);
return;
}
const cleanContent = { ...content };
delete cleanContent.fullStack;
config?.logger?.info(cleanContent);
};
var getFileLocation = (location) => {
const config = AppConfig.getAuditOption();
const defaultFileConfigs = AppConfig.getDefaultFileConfig();
if (config.splitFiles) {
const dbFile = defaultFileConfigs.find((x) => x.fileName === location);
return dbFile;
}
return AppConfig.getFileConfig();
};
var generateAuditContent = ({
type,
action,
message,
...rest
}) => {
return {
id: generateId(),
type,
action,
message,
...rest,
...AppConfig.getAuditOption()?.useTimeStamp ? { timeStamp: getTimeStamp() } : {}
};
};
var generateId = () => {
const time = Date.now().toString(36);
const rand = crypto.randomBytes(3).toString("hex");
return `${time}-${rand}`;
};
var checkForModule = (item) => {
const requireFromUserProject = createRequire(path.join(process.cwd(), "index.js"));
try {
AppConfig.getAuditOption()?.logger?.info(
chalk.blueBright(`Checking for ${item}`)
);
requireFromUserProject.resolve(item);
AppConfig.getAuditOption()?.logger?.info(
chalk.greenBright(`${item} found.`)
);
return true;
} catch (error) {
AppConfig.getAuditOption()?.logger?.info(
chalk.redBright(`Failed to find "${item}"`)
);
return false;
}
};
// src/utils/user.ts
var UserProfile = class {
constructor() {
this.userId = "";
this.endPoint = "";
this.ip = "";
this.userAgent = "";
}
BuildProfile(id, url, ip, userAgent) {
this.userId = id;
this.endPoint = url;
this.ip = ip;
this.userAgent = userAgent;
}
getUserId() {
return this.userId;
}
getEndPoint() {
return this.endPoint;
}
getIp() {
return this.ip;
}
getUserAgent() {
return this.userAgent;
}
};
var userProfile = new UserProfile();
// src/database/mongodb/config.ts
var hasMongoose = false;
var checkForMongodb = () => {
hasMongoose = checkForModule("mongoose");
return hasMongoose;
};
var auditModel = (schema) => {
const config = AppConfig.getAuditOption();
if (config.dbType === "none") {
config.logger?.error(chalk2.yellow("Cannot audit db while DB type is set to none"));
return;
}
if (!hasMongoose) {
config.logger?.error(chalk2.red("Please install mongoose to use audit"));
return;
}
handleSaveSchema(schema);
handleFindSchema(schema);
handleUpdateSchema(schema);
handleDeletingSchema(schema);
};
var handleSaveSchema = (schema) => {
const log = generateLog();
schema.pre("save", function(next) {
this._wasNew = this.isNew;
next();
});
schema.post("save", (doc) => {
const { modelName } = doc.constructor;
const message = `doc was ${doc._wasNew ? "created" : "updated"}`;
log("save", modelName, {}, message);
});
};
var handleFindSchema = (schema) => {
const log = generateLog();
schema.post("find", function(docs) {
const modelName = this.model.collection.name;
const message = `looking for ${modelName}`;
log("read", modelName, this.getFilter(), message, docs.length);
});
schema.post("findOne", function(doc) {
const modelName = this.model.collection.name;
const message = `looking for a single ${modelName}`;
log("read", modelName, this.getFilter(), message);
});
};
var handleUpdateSchema = (schema) => {
const log = generateLog();
schema.post("updateOne", function(doc) {
const modelName = this.model.collection.name;
const message = `updating a single ${modelName} doc`;
log("update", modelName, this.getFilter(), message);
});
schema.post("findOneAndUpdate", function(doc) {
const modelName = this.model.collection.name;
const message = `finding & updating a single ${modelName} doc`;
log("update", modelName, this.getFilter(), message);
});
schema.post("updateMany", function(docs) {
const modelName = this.model.collection.name;
const message = `updating multiple ${modelName} docs`;
log("update", modelName, this.getFilter(), message, docs.modifiedCount);
});
};
var handleDeletingSchema = (schema) => {
const log = generateLog();
schema.post("findOneAndDelete", function() {
const modelName = this.model.collection.name;
const message = `finding & deleting a single ${modelName} doc`;
log("delete", modelName, this.getFilter(), message);
});
schema.post("deleteOne", function() {
const modelName = this.model.collection.name;
const message = `deleting a single ${modelName} doc`;
log("delete", modelName, this.getFilter(), message);
});
schema.post("deleteMany", function(docs) {
const modelName = this.model.collection.name;
const message = `deleting multiple ${modelName} docs`;
log("delete", modelName, this.getFilter(), message, docs.deletedCount);
});
};
var generateLog = () => {
const config = AppConfig.getAuditOption();
return (action, modelName, filter, message, length) => {
const content = generateAuditContent({
type: "db",
action,
collection: modelName,
criteria: filter,
...length !== void 0 ? { resultCount: length } : {},
message,
userId: userProfile.getUserId(),
endPoint: userProfile.getEndPoint(),
ip: userProfile.getIp(),
userAgent: userProfile.getUserAgent(),
...config.useTimeStamp ? { timeStamp: getTimeStamp() } : {}
});
if (config.destinations?.includes("console"))
logAuditEvent(content);
const dbFile = getFileLocation("db.log");
if (!dbFile) return;
if (config.destinations?.includes("file"))
logAuditEvent(content, dbFile);
};
};
// src/middleware/requestLogger.ts
var expressLogger = (req, res, next) => {
const file = getFileLocation("request.log");
const start = Date.now();
const route = req.originalUrl;
const ip = req.ip ?? "unknown";
const userAgent = req.headers["user-agent"] || "";
const user = getUserId(req);
const id = typeof user === "string" ? user : user ? user?.id : "unknown";
userProfile.BuildProfile(id, route, ip, userAgent);
res.on("finish", () => {
const duration = Date.now() - start;
if (res._suppressAudit) {
const content2 = generateAuditContent({
type: "request",
action: "incoming request",
message: req.statusMessage ?? res.statusMessage ?? `[${route}]||[${req.method}]||[${[res.statusCode]}]`,
outcome: "failure",
duration,
method: req.method,
statusCode: res.statusCode || 500,
ip,
route,
userAgent,
userId: id
});
handleLog(file, content2);
return;
}
const content = generateAuditContent({
type: "request",
action: "incoming request",
outcome: "success",
duration,
method: req.method,
statusCode: res.statusCode || 200,
message: req.statusMessage ?? res.statusMessage ?? `[${route}]||[${req.method}]||[${[res.statusCode]}]`,
route,
statusMessage: res.statusMessage || "success",
ip,
userAgent,
userId: id
});
handleLog(file, content);
});
next();
};
var fastifyLogger = {
onRequest: (request, reply, done) => {
request.startTime = Date.now();
const id = request.userId ?? "unknown";
const route = request.url;
const { ip } = request;
const userAgent = request.headers["user-agent"] || "";
userProfile.BuildProfile(id, route, ip, userAgent);
done();
},
onResponse: (request, reply, done) => {
const file = getFileLocation("request.log");
const duration = Date.now() - (request.startTime || Date.now());
const route = userProfile.getEndPoint() ?? "unknown";
const content = generateAuditContent({
type: "request",
action: "incoming request",
duration,
method: request.method,
statusCode: reply.statusCode || 500,
message: `[${route}]||[${request.method}]||[${[reply.statusCode]}]`,
ip: userProfile.getIp() ?? "unknown",
route,
userAgent: userProfile.getUserAgent() ?? "unknown",
userId: request.userId ?? "unknown"
});
handleLog(file, content);
done();
}
};
var koaLogger = async (ctx, next) => {
const file = getFileLocation("request.log");
const start = Date.now();
const userId = ctx.state.userId ?? "unknown";
const route = ctx.request.url;
const ip = ctx.request.ip ?? "unknown";
const userAgent = ctx.request.headers["user-agent"] || "";
userProfile.BuildProfile(userId, route, ip, userAgent);
try {
await next();
} finally {
const duration = Date.now() - start;
const content = generateAuditContent({
type: "request",
action: "incoming request",
duration,
method: ctx.request.method,
statusCode: ctx.res.statusCode || 500,
message: ctx.message ?? `[${route}]||[${ctx.request.method}]||[${[ctx.status]}]`,
ip,
route,
userAgent,
userId
});
handleLog(file, content);
}
};
var requestLogger = {
express: expressLogger,
fastify: fastifyLogger,
koa: koaLogger
};
// src/middleware/errorLogger.ts
var expressErrorLogger = (err, req, res, next) => {
const file = getFileLocation("error.log");
res._suppressAudit = true;
const user = getUserId(req);
let stackLine = "";
if (err.stack) {
const lines = err.stack.split("\n");
stackLine = lines.length > 1 ? lines[1].trim() : lines[0]?.trim() ?? "";
}
const content = generateAuditContent({
type: "error",
action: "request failed",
method: req.method,
statusCode: res.statusCode >= 400 ? res.statusCode : 500,
route: req.originalUrl,
statusMessage: res.statusMessage || "Internal Server Error",
ip: req.ip ?? "unknown",
userAgent: req.headers["user-agent"],
message: err.message,
stack: stackLine,
fullStack: err.stack ?? "Invalid",
userId: typeof user === "string" ? user : user ? user?.id : "unknown"
});
handleLog(file, content);
next(err);
};
var fastifyErrorLogger = (error, request, reply) => {
const file = getFileLocation("error.log");
let stackLine = "";
if (error.stack) {
const lines = error.stack?.split("\n");
stackLine = lines ? lines.length > 1 ? lines[1].trim() : lines[0]?.trim() ?? "" : error.message;
}
const content = generateAuditContent({
type: "error",
action: "request failed",
method: request.method,
statusCode: reply.statusCode || 500,
route: request.url,
ip: request.ip ?? "unknown",
userAgent: request.headers["user-agent"],
message: error.message,
stack: stackLine,
fullStack: error.stack ?? "Invalid",
userId: request.userId ?? "unknown"
});
handleLog(file, content);
return reply.send(error);
};
var koaErrorLogger = async (ctx, next) => {
const file = getFileLocation("error.log");
const userId = ctx.state.userId ?? "unknown";
try {
await next();
} catch (error) {
if (ctx.status >= 400) {
let stackLine = "";
if (error.stack) {
const lines = error.stack.split("\n");
stackLine = lines ? lines.length > 1 ? lines[1].trim() : lines[0]?.trim() ?? null : error.message;
}
const content = generateAuditContent({
type: "error",
action: "request failed",
method: ctx.method,
statusCode: ctx.statusCode || 500,
message: error.message,
route: ctx.url,
ip: ctx.ip ?? "unknown",
userAgent: ctx.headers["user-agent"],
stack: stackLine,
fullStack: error.stack ?? "Invalid",
userId
});
handleLog(file, content);
}
}
};
var errorLogger = {
express: expressErrorLogger,
fastify: fastifyErrorLogger,
koa: koaErrorLogger
};
// src/router/router.ts
import chalk3 from "chalk";
import fs2, { createWriteStream, existsSync } from "fs";
import path2 from "path";
import { Readable } from "stream";
import { pipeline } from "stream/promises";
import { fileURLToPath } from "url";
var __filename = fileURLToPath(import.meta.url);
var __dirname = path2.dirname(__filename);
var uiPath = path2.join(__dirname, "ui");
var getLogs = () => {
const hasSplitFiles = AppConfig.getAuditOption()?.splitFiles;
const file = AppConfig.getFileConfig();
if (hasSplitFiles) {
const files = AppConfig.getDefaultFileConfig();
if (!files) return [];
const data = files.flatMap((item2) => {
const logData2 = fs2.readFileSync(item2.fullPath, "utf-8");
return logData2.trim().split("\n").filter(Boolean).map((line, i) => ({
id: i,
...JSON.parse(line)
}));
});
return data.sort((a, b) => b.timeStamp.localeCompare(a.timeStamp));
}
if (!file) return [];
const logData = fs2.readFileSync(file.fullPath, "utf-8");
const item = logData.trim().split("\n").filter(Boolean).map((line, i) => ({
id: i,
...JSON.parse(line)
}));
return item.sort((a, b) => b.timeStamp.localeCompare(a.timeStamp));
};
var expressRouter = async () => {
let express;
try {
express = await import("express");
} catch (error) {
AppConfig.getAuditOption()?.logger?.info(chalk3.redBright("Please install express in order to use this module"));
return (req, res, next) => next();
}
const router = express.Router();
router.use(express.static(uiPath));
router.get("/audit-ui", (_req, res) => {
res.sendFile(path2.join(uiPath, "index.html"));
});
router.get("/audit-log", (_req, res) => {
const logs = getLogs();
res.status(200).json({ logs });
});
return router;
};
var fastifyRouter = async () => {
let fastifyStatic;
try {
fastifyStatic = await import("@fastify/static");
} catch {
return async () => {
};
}
return async function(fastify, opts) {
await fastify.register(fastifyStatic.default, {
root: uiPath,
prefix: "/"
});
fastify.get("/audit-ui", (_, reply) => {
reply.type("text/html").sendFile("index.html");
});
fastify.get("/audit-log", (_, reply) => {
reply.send({ logs: getLogs() });
});
};
};
var koaRouter = async () => {
let Router, serve, compose;
try {
Router = (await import("@koa/router")).default;
serve = (await import("koa-static")).default;
compose = (await import("./koa-compose-JHK7V4NJ.mjs")).default;
} catch {
AppConfig.getAuditOption()?.logger?.info(
chalk3.redBright("Please install koa, @koa/router, koa-static, and koa-compose to use the audit UI.")
);
return async (ctx, next) => await next();
}
const router = new Router();
router.get("/audit-ui", (ctx) => {
ctx.type = "html";
ctx.body = fs2.createReadStream(path2.join(uiPath, "index.html"));
});
router.get("/audit-log", (ctx) => {
ctx.body = { logs: getLogs() };
});
return compose([
serve(uiPath),
router.routes(),
router.allowedMethods()
]);
};
var checkForFramework = () => {
const activeFramework = AppConfig.getFrameWork();
return checkForModule(activeFramework);
};
var UIRouter = {
express: expressRouter,
fastify: fastifyRouter,
koa: koaRouter
};
var downloadDependency = async () => {
const logger = AppConfig.getAuditOption()?.logger;
await downloadFile("https://cdn.jsdelivr.net/npm/chart.js", "chart.min.js");
await downloadFile("https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js", "html2canvas.min.js");
logger?.info(chalk3.green("Files downloaded successfully"));
};
async function downloadFile(url, fileName) {
const location = path2.join(uiPath, fileName);
const logger = AppConfig.getAuditOption()?.logger;
if (existsSync(location)) {
logger?.info(chalk3.gray(`Skipping ${fileName}, already exists.`));
return;
}
logger?.info(chalk3.yellow(`Downloading ${fileName}`));
const res = await fetch(url);
if (!res.ok || !res.body) {
logger?.error(chalk3.red(`Failed to fetch ${url}: ${res.status}`));
return;
}
const nodeStream = Readable.fromWeb(res.body);
const fileStream = createWriteStream(location);
try {
await pipeline(nodeStream, fileStream);
logger?.info(chalk3.green(`Downloaded ${fileName} successfully.`));
} catch (err) {
logger?.error(chalk3.red(`Error writing ${fileName}: ${err}`));
}
}
// src/core/Auditor.ts
var Audit = class {
/**
* Creates an instance of the Auditor class with the provided options.
*
* @param options - Optional configuration object for the auditor.
* - `logger`: Custom logger to use; defaults to `console` if not provided.
* - `dbType`: Type of database to use; defaults to `"none"`.
* - `destinations`: Array of destinations for audit logs; defaults to `["console"]`.
* - `framework`: The framework being used (e.g., "express"); defaults to `"express"`.
* - `useTimeStamp`: Whether to include timestamps in logs; defaults to `true`.
* - `splitFiles`: Whether to split logs into multiple files; defaults to `false`.
* - `captureSystemErrors`: Whether to capture system errors; defaults to `false`.
*
* Initializes the `auditOptions` property by merging the provided options with sensible defaults.
*/
constructor(options) {
this.options = options;
this.logFilePath = "";
this.defaultFileConfigs = [
{
fileName: "error.log",
folderName: "audits",
fullPath: ""
},
{
fileName: "request.log",
folderName: "audits",
fullPath: ""
},
{
fileName: "db.log",
folderName: "audits",
fullPath: ""
},
{
fileName: "action.log",
folderName: "audits",
fullPath: ""
}
];
this.fileConfig = {
fileName: "audit.log",
folderName: "audit",
fullPath: ""
};
this.isInitialized = false;
this.auditOptions = {
dbType: "none",
destinations: ["console"],
framework: "express",
logger: console,
useTimeStamp: true,
splitFiles: false,
captureSystemErrors: false,
useUI: false
};
this.CreateFileLocation = (config) => {
if (!this.auditOptions.destinations?.includes("file")) return;
if (this.auditOptions.splitFiles) {
this.defaultFileConfigs.forEach((item) => {
this.GenerateFile(item);
});
return;
}
this.GenerateFile(config);
};
this.GenerateFile = (config) => {
const dir = createFile(config);
config.fullPath = dir;
this.logFilePath = dir;
this.defaultFileConfigs = this.defaultFileConfigs.map((item) => {
if (item.fileName === config.fileName) {
return { ...item, fullPath: dir };
}
return item;
});
};
/**
* Sets up system error handling and logging for audit purposes.
* @example
* handleSystemErrors()
* Initializes processes to log exceptions, rejections, signals, and exits.
* @description
* - Listens for Node.js process events such as uncaught exceptions, unhandled rejections, SIGTERM, SIGINT, and exit.
* - Utilizes the `generateAuditContent` utility to format the logs.
* - Logs generated content using `handleLog` with the error file configuration.
* - Only activates if `captureSystemErrors` is true within audit options.
*/
this.HandleSystemErrors = () => {
if (!this.auditOptions.captureSystemErrors) return;
const file = getFileLocation("error.log");
process.on("uncaughtException", (error, origin) => {
const fullStack = error instanceof Error ? error.stack : void 0;
const content = generateAuditContent({
type: "error",
action: "unknown",
message: error.message ?? "an uncaughtException",
outcome: "uncaughtException",
error,
origin,
fullStack
});
handleLog(file, content);
});
process.on("unhandledRejection", (reason) => {
const content = generateAuditContent({
type: "error",
action: "unknown",
message: "an unhandledRejection",
outcome: "unhandledRejection",
reason
});
handleLog(file, content);
});
process.on("SIGTERM", () => {
const content = generateAuditContent({
type: "signal",
action: "terminated",
message: "was terminated",
outcome: "SIGTERM",
signal: "SIGTERM"
});
handleLog(file, content);
process.exit(0);
});
process.on("SIGINT", () => {
const content = generateAuditContent({
type: "signal",
action: "terminated",
message: "app was terminated",
outcome: "SIGINT",
signal: "SIGINT"
});
handleLog(file, content);
process.exit(0);
});
process.on("exit", (code) => {
const content = generateAuditContent({
type: "system",
action: "exit",
message: "app was exited",
outcome: "exit",
code
});
handleLog(file, content);
});
};
this.auditOptions = {
...options,
logger: options?.logger || console,
dbType: options?.dbType || "none",
destinations: options?.destinations || ["console"],
framework: options?.framework ?? "express",
useTimeStamp: options?.useTimeStamp ?? true,
splitFiles: options?.splitFiles ?? false,
captureSystemErrors: options?.captureSystemErrors ?? false,
useUI: options?.useUI ?? false
};
}
/**
* Initializes the auditor by creating the necessary file location and logging a success message.
*
* This method sets up the audit configuration by invoking `CreateFileLocation` with the current file configuration,
* and logs a confirmation message using the configured logger.
*
* @remarks
* Should be called before performing any audit operations to ensure the environment is properly configured.
*/
async Setup() {
if (this.isInitialized) {
this.auditOptions.logger?.warn(chalk4.yellow("Audit already initialized."));
return;
}
this.CreateFileLocation(this.fileConfig);
AppConfig.setAuditOption(this.auditOptions);
AppConfig.setDefaultFileConfig(this.defaultFileConfigs);
AppConfig.setFileConfig(this.fileConfig);
AppConfig.setLogFilePath(this.logFilePath);
AppConfig.setCaptureSystemErrors(this.auditOptions.captureSystemErrors ?? false);
AppConfig.setFrameWork(this.auditOptions.framework);
AppConfig.setUseUI(this.auditOptions.useUI ?? false);
if (this.auditOptions.dbType === "mongoose") {
const result = checkForMongodb();
if (!result) return;
}
if (this.auditOptions.useUI) {
const hasFramework = checkForFramework();
if (hasFramework) {
AppConfig.getAuditOption()?.logger?.info(chalk4.yellow("In order to use this module some dependency will be downloaded"));
await downloadDependency();
}
}
this.isInitialized = true;
AppConfig.setIsInitialized(this.isInitialized);
this.HandleSystemErrors();
this.auditOptions.logger?.info(chalk4.green("Default Audit config set successfully"));
}
/**
* Logs an event to the configured destinations (console and/or file).
*
* Depending on the configuration, this method will:
* - Add a timestamp to the event if `useTimeStamp` is enabled.
* - Log the event to the console if "console" is included in `destinations`.
* - Log the event to a file if "file" is included in `destinations`.
* - If `splitFiles` is enabled, logs to a specific action log file.
* - Otherwise, logs to the default file configuration.
* - If the file path is not set when logging to a file, logs an error.
*
* @param event - The event object to be logged.
*/
Log(event) {
const item = generateAuditContent({ ...event });
if (!this.isInitialized) {
this.auditOptions?.logger?.info(chalk4.red("Not Initialized. Setup Is Required"));
return;
}
if (this.auditOptions.destinations?.includes("console")) {
logAuditEvent(item);
}
if (this.auditOptions.destinations?.includes("file")) {
const actionFile = this.defaultFileConfigs.find((x) => x.fileName === "action.log");
const file = this.auditOptions.splitFiles ? actionFile : this.fileConfig;
if (!file.fullPath) {
this.auditOptions.logger?.error(chalk4.red("Unable to locate file path"));
return;
}
logAuditEvent(item, file);
}
}
/**
* Logs an error event to the configured destinations (console and/or file).
* @example
* LogError(new Error("Unexpected failure"))
* Logs error details to the console and file depending on configuration.
* @param {any} error - The error object to be logged.
* @description
* - Extracts the error stack trace to capture informative details.
* - Compiles additional user context such as IP and user agent when available.
* - Handles uninitialized state by printing a warning message.
* - Utilizes timestamp inclusion and log destination logic based on audit options.
*/
LogError(error) {
if (!this.isInitialized) {
this.auditOptions?.logger?.info(chalk4.red("Not Initialized. Setup Is Required"));
return;
}
let stackLine = "";
if (error.stack) {
const lines = error.stack.split("\n");
stackLine = lines.length > 1 ? lines[1].trim() : lines[0]?.trim() ?? "";
}
const item = generateAuditContent({
type: "error",
action: "unknown",
outcome: "error",
method: "user called",
statusCode: error.statusCode ?? 500,
userId: userProfile.getUserId() ?? "unknown",
ip: userProfile.getIp() ?? "unknown",
userAgent: userProfile.getUserAgent() ?? "unknown",
message: error.message ?? "an error occurred",
stack: stackLine ?? "no stack available"
});
if (this.auditOptions.destinations?.includes("console")) {
logAuditEvent(item);
}
if (this.auditOptions.destinations?.includes("file")) {
const actionFile = this.defaultFileConfigs.find((x) => x.fileName === "error.log");
const file = this.auditOptions.splitFiles ? actionFile : this.fileConfig;
if (!file.fullPath) {
this.auditOptions.logger?.error(chalk4.red("Unable to locate file path"));
return;
}
logAuditEvent(item, file);
}
}
/**
* Audits a given schema model by invoking the `auditModel` function with the current configuration,
* a generated timestamp, and the provided schema.
*
* @template T - The type of the schema being audited.
* @param schema - The schema object to be audited.
*/
AuditModel(schema) {
auditModel(schema);
}
/**
* Configures the file logging settings for the auditor.
*
* This method sets up the file configuration used for logging audit events to a file.
* It checks if "file" is included in the destinations and if split file logging is disabled.
* If these conditions are not met, it logs appropriate warnings and returns early.
* Otherwise, it sets up the file configuration with the provided options or defaults.
*
* @param config - Optional configuration object for the file logger, excluding the `fullPath` property.
* - `folderName` (optional): The folder where the log file will be stored. Defaults to `"audit"`.
* - `fileName` (optional): The name of the log file. Defaults to `"audit.log"`.
*/
SetFileConfig(config) {
if (!this.auditOptions.destinations?.includes("file")) {
this.auditOptions.logger?.warn(chalk4.yellowBright("You need to add file to destinations for this to work properly"));
return;
}
if (this.auditOptions.splitFiles) {
this.auditOptions.logger?.info(chalk4.yellow("Cannot configure file as it is not supported when using splitfile"));
return;
}
const folder = config?.folderName ?? "audit";
const baseFileName = config?.fileName ?? "audit";
const fileName = baseFileName.endsWith(".log") ? baseFileName : `${baseFileName}.log`;
this.fileConfig = {
...config,
folderName: folder,
fileName,
fullPath: ""
};
}
/**
* Logs all incoming requests using the configured logger.
*
* Depending on the `splitFiles` configuration, this method will either:
* - Log requests to a dedicated "request.log" file if `splitFiles` is enabled.
* - Log requests to the default log file otherwise.
*
* @returns The result of the `requestLogger` function, which handles the actual logging process.
*/
RequestLogger() {
return requestLogger[this.auditOptions.framework];
}
/**
* Logs all errors using the configured error logger.
*
* If the `splitFiles` option is enabled in the configuration, errors are logged to a separate
* "error.log" file as specified in the default file configurations. Otherwise, errors are logged
* to the main file configuration.
*
* @returns The result of the error logger function, which handles error logging based on the current configuration.
*/
ErrorLogger() {
return errorLogger[this.auditOptions.framework];
}
/**
* Asynchronously creates and returns the UI component or handler based on the specified framework
* in the audit options.
*
* @returns {Promise<any>} A promise that resolves to the UI component or handler corresponding to the selected framework.
*/
CreateUI() {
if (!AppConfig.getUseUI()) {
AppConfig.getAuditOption()?.logger?.info(chalk4.yellow("Add the useUI option in the constructor to download the dependency"));
return UIRouter[this.auditOptions.framework];
}
return UIRouter[this.auditOptions.framework];
}
};
export {
Audit as Auditor
};
//# sourceMappingURL=index.mjs.map