254 lines
7.8 KiB
JavaScript
254 lines
7.8 KiB
JavaScript
// Load environment variables THIS MUST BE AT THE TOP
|
|
const InstanceManager = require("../utils/instanceMgr").default;
|
|
const winston = require("winston");
|
|
const WinstonCloudWatch = require("winston-cloudwatch");
|
|
const { isString, isEmpty } = require("lodash");
|
|
const { uploadFileToS3 } = require("./s3");
|
|
const { v4 } = require("uuid");
|
|
const { InstanceRegion } = require("./instanceMgr");
|
|
const getHostNameOrIP = require("./getHostNameOrIP");
|
|
const client = require("../graphql-client/graphql-client").client;
|
|
const queries = require("../graphql-client/queries");
|
|
|
|
const LOG_LEVELS = {
|
|
error: { level: 0, name: "error" },
|
|
warn: { level: 1, name: "warn" },
|
|
info: { level: 2, name: "info" },
|
|
http: { level: 3, name: "http" },
|
|
verbose: { level: 4, name: "verbose" },
|
|
debug: { level: 5, name: "debug" },
|
|
silly: { level: 6, name: "silly" }
|
|
};
|
|
|
|
const LOG_LENGTH_LIMIT = 256 * 1024; // 256KB
|
|
|
|
const S3_BUCKET_NAME = InstanceManager({
|
|
imex: "imex-large-log",
|
|
rome: "rome-large-log"
|
|
});
|
|
|
|
const region = InstanceRegion();
|
|
|
|
const estimateLogSize = (logEntry) => {
|
|
let estimatedSize = 0;
|
|
for (const key in logEntry) {
|
|
if (Object.prototype.hasOwnProperty.call(logEntry, key)) {
|
|
const value = logEntry[key];
|
|
if (value === undefined || value === null) {
|
|
estimatedSize += key.length; // Only count the key length if value is undefined or null
|
|
} else {
|
|
estimatedSize += key.length + (typeof value === "string" ? value.length : JSON.stringify(value).length);
|
|
}
|
|
}
|
|
}
|
|
return estimatedSize;
|
|
};
|
|
|
|
const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debug.name);
|
|
|
|
const createLogger = () => {
|
|
try {
|
|
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
|
const logGroupName = isLocal ? "development" : process.env.CLOUDWATCH_LOG_GROUP;
|
|
|
|
const winstonCloudwatchTransportDefaults = {
|
|
logGroupName: logGroupName,
|
|
awsOptions: {
|
|
region
|
|
},
|
|
jsonMessage: true
|
|
};
|
|
|
|
if (isLocal) {
|
|
winstonCloudwatchTransportDefaults.awsOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
|
}
|
|
|
|
const levelFilter = (levels) => {
|
|
return winston.format((info) => {
|
|
if (Array.isArray(levels)) {
|
|
return levels.includes(info.level) ? info : false;
|
|
} else {
|
|
return info.level === levels ? info : false;
|
|
}
|
|
})();
|
|
};
|
|
|
|
const createProductionTransport = (level, logStreamName, filters) => {
|
|
return new WinstonCloudWatch({
|
|
level,
|
|
logStreamName: logStreamName || level,
|
|
format: levelFilter(filters || level),
|
|
...winstonCloudwatchTransportDefaults
|
|
});
|
|
};
|
|
|
|
const internalHostname = process.env.HOSTNAME || getHostNameOrIP();
|
|
|
|
const getDevelopmentTransports = () => [
|
|
new winston.transports.Console({
|
|
level: "silly",
|
|
format: winston.format.combine(
|
|
winston.format.colorize(),
|
|
winston.format.timestamp(),
|
|
winston.format.printf(({ level, message, timestamp, user, record, meta }) => {
|
|
const hostnameColor = `\x1b[34m${internalHostname}\x1b[0m`; // Blue
|
|
const timestampColor = `\x1b[36m${timestamp}\x1b[0m`; // Cyan
|
|
const labelColor = "\x1b[33m"; // Yellow
|
|
const separatorColor = "\x1b[35m|\x1b[0m"; // Magenta for separators
|
|
|
|
return `${timestampColor} [${hostnameColor}] [${level}]: ${message} ${
|
|
user ? `${separatorColor} ${labelColor}user:\x1b[0m ${JSON.stringify(user)}` : ""
|
|
} ${record ? `${separatorColor} ${labelColor}record:\x1b[0m ${JSON.stringify(record)}` : ""}${
|
|
meta
|
|
? `\n${separatorColor} ${labelColor}meta:\x1b[0m ${JSON.stringify(meta, null, 2)} ${separatorColor}`
|
|
: ""
|
|
}`;
|
|
})
|
|
)
|
|
})
|
|
];
|
|
|
|
const getProductionTransports = () => [
|
|
createProductionTransport("error"),
|
|
createProductionTransport("warn"),
|
|
createProductionTransport("info"),
|
|
createProductionTransport("silly", "debug", ["http", "verbose", "debug", "silly"])
|
|
];
|
|
|
|
const winstonLogger = winston.createLogger({
|
|
format: winston.format.json(),
|
|
transports:
|
|
process.env.NODE_ENV === "production"
|
|
? getProductionTransports()
|
|
: [...getDevelopmentTransports(), ...getProductionTransports()]
|
|
});
|
|
|
|
if (isLocal) {
|
|
winstonLogger.debug(
|
|
`CloudWatch set to LocalStack end point: ${winstonCloudwatchTransportDefaults.awsOptions.endpoint}`
|
|
);
|
|
}
|
|
|
|
const log = (message, type, user, record, meta, upload) => {
|
|
const logEntry = {
|
|
level: normalizeLevel(type),
|
|
message,
|
|
user,
|
|
record,
|
|
hostname: internalHostname,
|
|
meta
|
|
};
|
|
|
|
const uploadLogToS3 = (logEntry, message, type, user) => {
|
|
const uniqueId = v4();
|
|
const dateTimeString = new Date().toISOString().replace(/:/g, "-");
|
|
const envName = process.env?.NODE_ENV ? process.env.NODE_ENV : "";
|
|
const logStreamName = `${envName}-${internalHostname}-${dateTimeString}-${uniqueId}.json`;
|
|
const logString = JSON.stringify(logEntry);
|
|
const webPath = isLocal
|
|
? `https://${S3_BUCKET_NAME}.s3.localhost.localstack.cloud:4566/${logStreamName}`
|
|
: `https://${S3_BUCKET_NAME}.s3.${region}.amazonaws.com/${logStreamName}`;
|
|
|
|
uploadFileToS3({ bucketName: S3_BUCKET_NAME, key: logStreamName, content: logString })
|
|
.then(() => {
|
|
log("A log file has been uploaded to S3", "info", "S3", null, {
|
|
logStreamName,
|
|
webPath,
|
|
message: message?.slice(0, 200),
|
|
type,
|
|
user
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
log("Error in S3 Upload", "error", "S3", null, {
|
|
logStreamName,
|
|
webPath,
|
|
message: message?.slice(0, 100),
|
|
type,
|
|
user,
|
|
errorMessage: err?.message?.slice(0, 100)
|
|
});
|
|
});
|
|
};
|
|
|
|
const checkAndUploadLog = () => {
|
|
const estimatedSize = estimateLogSize(logEntry);
|
|
|
|
if (estimatedSize > LOG_LENGTH_LIMIT * 0.9 || estimatedSize > LOG_LENGTH_LIMIT) {
|
|
uploadLogToS3(logEntry, message, type, user);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
// Upload log immediately if upload is true, otherwise check the log size.
|
|
if (upload) {
|
|
uploadLogToS3(logEntry, message, type, user);
|
|
return;
|
|
}
|
|
|
|
if (checkAndUploadLog()) return;
|
|
|
|
winstonLogger.log(logEntry);
|
|
};
|
|
|
|
const LogIntegrationCall = async ({
|
|
platform,
|
|
method,
|
|
name,
|
|
jobid,
|
|
paymentid,
|
|
billid,
|
|
status,
|
|
bodyshopid,
|
|
email
|
|
}) => {
|
|
try {
|
|
//Insert the record.
|
|
await client.request(queries.INSERT_INTEGRATION_LOG, {
|
|
log: {
|
|
platform,
|
|
method,
|
|
name,
|
|
jobid,
|
|
paymentid,
|
|
billid,
|
|
status: status?.toString() ?? "0",
|
|
bodyshopid,
|
|
email
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.trace("Stack", error?.stack);
|
|
log("integration-log-error", "ERROR", email, null, {
|
|
message: error?.message,
|
|
stack: error?.stack,
|
|
platform,
|
|
method,
|
|
name,
|
|
jobid,
|
|
paymentid,
|
|
billid,
|
|
status,
|
|
bodyshopid,
|
|
email
|
|
});
|
|
}
|
|
};
|
|
|
|
return {
|
|
log,
|
|
logger: winstonLogger,
|
|
LogIntegrationCall
|
|
};
|
|
} catch (e) {
|
|
console.error("Error setting up enhanced Logger, defaulting to console.: " + e?.message || "");
|
|
return {
|
|
log: console.log,
|
|
logger: console.log,
|
|
LOG_LEVELS
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports = createLogger();
|