canopy/src/utils/loggerUtils.js

262 lines
11 KiB
JavaScript

/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Node
const fs = require('node:fs/promises');
const crypto = require('node:crypto');
//Config
const config = require('../../config.json');
/**
* Creates and returns a custom exception, tagged as a 'custom' exception, using the 'custom' boolean property.
* This is used to denote that this error was generated on purpose, with a human readable message, that can be securely sent to the client.
* Unexpected exceptions should only be logged internally, however, as they may contain sensitive data.
*
* @param {String} msg - Error message to send the client
* @param {String} type - Error type to send back to the client
* @returns {Error} The exception to smith
*/
module.exports.exceptionSmith = function(msg, type){
//Create the new error with the given message
const exception = new Error(msg);
//Set the error type
exception.type = type;
//Mark the error as a custom error
exception.custom = true;
//Return the error
return exception;
}
/**
* Main error handling function
* @param {Express.Response} res - Response being sent out to the client who caused the issue
* @param {String} msg - Error message to send the client
* @param {String} type - Error type to send back to the client
* @param {Number} status - HTTP(s) Status Code to send back to the client
* @returns {Express.Response} If we have a usable Express Response object, return it back after it's been cashed
*/
module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){
//Some controllers do things after sending headers, for those, we should remain silent
if(!res.headersSent){
res.status(status);
return res.send({errors: [{type, msg, date: new Date()}]});
}
}
/**
* Handles local exceptions which where not directly created by user interaction
* @param {Error} err - Exception to handle
*/
module.exports.localExceptionHandler = function(err){
//If we're being verbose and this isn't just a basic bitch
if(!err.custom && config.verbose){
//Log the error
module.exports.dumpError(err);
}
}
/**
* Handles exceptions which where directly the fault of user action >:(
* @param {Express.Response} res - Express Response object to bitch at
* @param {Error} err - Error created by the jerk in question
*/
module.exports.exceptionHandler = function(res, err){
//If this is a self-made problem
if(err.custom){
module.exports.errorHandler(res, err.message, err.type);
}else{
//Locally handle the exception
module.exports.localExceptionHandler(err);
//if not yell at the browser for fucking up, and tell it what it did wrong.
module.exports.errorHandler(res, "An unexpected server crash was just prevented. You should probably report this to an admin.", "Caught Exception");
}
}
/**
* Basic error-handling for socket.io so we don't just silently swallow errors.
* @param {Socket} socket - Socket error originated from
* @param {String} msg - Error message to send the client
* @param {String} type - Error type to send back to the client
* @returns {Boolean} - Passthrough from socket.emit
*/
module.exports.socketErrorHandler = function(socket, msg, type = "Generic"){
return socket.emit("error", {errors: [{type, msg, date: new Date()}]});
}
/**
* Generates error messages for simple errors generated by socket.io interaction
* @param {Socket} socket - Socket error originated from
* @param {Error} err - Error created by the jerk in question
* @returns {Boolean} - Passthrough from socket.emit
*/
module.exports.socketExceptionHandler = function(socket, err){
//If this is a self made problem
if(err.custom){
//yell at the browser for fucking up, and tell it what it did wrong.
return module.exports.socketErrorHandler(socket, err.message, err.type);
}else{
//Locally handle the exception
module.exports.localExceptionHandler(err);
//if not yell at the browser for fucking up
return module.exports.socketErrorHandler(socket, "An unexpected server crash was just prevented. You should probably report this to an admin.", "Server");
}
}
/**
* Generates error messages and drops connection for critical errors caused by socket.io interaction
* @param {Socket} socket - Socket error originated from
* @param {Error} err - Error created by the jerk in question
* @returns {Boolean} - Passthrough from socket.disconnect
*/
module.exports.socketCriticalExceptionHandler = function(socket, err){
//If this is a self made problem
if(err.custom){
//yell at the browser for fucking up, and tell it what it did wrong.
socket.emit("kick", {type: "Disconnected", reason: `Server Error: ${err.message}`});
}else{
//Locally handle the exception
module.exports.localExceptionHandler(err);
//yell at the browser for fucking up
socket.emit("kick", {type: "Disconnected", reason: "An unexpected server crash was just prevented. You should probably report this to an admin."});
}
return socket.disconnect();
}
/**
* Prints warning text to server console
* @param {String} string - String to print to console
*/
module.exports.consoleWarn = function(string){
console.warn('\x1b[31m\x1b[4m%s\x1b[0m',string);
}
/**
* Basic error-handling middleware to ensure we're not dumping stack traces to the client, as that would be insecure
* @param {Error} err - Error to handle
* @param {Express.Request} req - Express Request
* @param {Express.Response} res - Express Response
* @param {Function} next - Next function in the Express middleware chain (Not that it's getting called XP)
*/
module.exports.errorMiddleware = function(err, req, res, next){
//Set generic error
var reason = "Server Error";
//If it's un-authorized
if(err.status == 403){
reason = "Unauthorized"
}
module.exports.errorHandler(res, err.message, reason, err.status);
}
/**
* Dumps unexpected server crashes to dedicated log files
* @param {Error} err - error to dump to file
* @param {Date} date - Date of error, defaults to now
* @param {String} subDir - subdirectory inside the log folder we want to dump to
* @param {Boolean} muzzle - Tells the function to STFU
*/
module.exports.dumpError = async function(err, date = new Date(), subDir = 'crash/', muzzle = false){
//Generate content from error
const content = `Error Date: ${date.toLocaleString()} (UTC-${date.getTimezoneOffset()/60})\nError Type: ${err.name}\nError Msg:${err.message}\nStack Trace:\n\n${err.stack}`;
//Dump text to file
module.exports.dumpLog(content, date.getTime(), subDir, muzzle);
}
module.exports.dumpSecurityLog = async function(content, date = new Date()){
module.exports.dumpLog(content, `Incident-{${crypto.randomUUID()}}-${date.getTime()}`, 'security/', true);
}
/**
* Dumps log file to log folder
* @param {String} content - Text to dump to file
* @param {String} name - file name to save to
* @param {String} subDir - subdirectory inside the log folder we want to dump to
* @param {Boolean} muzzle - Tells the function to STFU
*/
module.exports.dumpLog = async function(content, name, subDir = '/', muzzle = false){
try{
//Crash directory
const dir = `./log/${subDir}`
//Double check crash folder exists
try{
await fs.stat(dir);
//If we caught an error (most likely it's missing)
}catch(err){
if(!muzzle){
//Shout about it
module.exports.consoleWarn("Log folder missing, mking dir!")
}
//Make it if doesn't
await fs.mkdir(dir, {recursive: true});
}
//Assemble log file path
const path = `${dir}${name}.log`;
//Write content to file
fs.writeFile(path, content);
if(!muzzle){
//Whine about the error
module.exports.consoleWarn(`Warning: Unexpected Server Crash gracefully dumped to '${path}'... SOMETHING MAY BE VERY BROKEN!!!!`);
}
//If somethine went really really wrong
}catch(doubleErr){
if(!muzzle){
//Use humor to cope with the pain
module.exports.consoleWarn("Yo Dawg, I herd you like errors, so I put an error in your error dump, so you can dump while you dump:");
//Dump the original error to console
module.exports.consoleWarn(err);
//Dump the error we had saving that error to file to console
module.exports.consoleWarn(doubleErr);
}
}
}
module.exports.welcomeWagon = function(count, date, tokes){
//Inject values into ascii art
const art = `
\x1b[32m ! \x1b[0m
\x1b[32m 420 \x1b[0m \x1b[32m\x1b[40m${config.instanceName}\x1b[0m\x1b[2m, Powered By:\x1b[0m
\x1b[32m 420 \x1b[0m
\x1b[32m WEEED \x1b[0m CCCC AAA NN N OOO PPPP Y Y
\x1b[32m! WEEED !\x1b[0m C A A NN N O O P P Y Y
\x1b[32mWEE EEEEE EED\x1b[0m C A A N N N O O P P Y Y
\x1b[32m WEE EEEEE EED\x1b[0m C AAAAA N N N O O PPPP Y
\x1b[32m WEE EEE EED\x1b[0m C A A N N N O O P Y
\x1b[32m WEE EEE EED\x1b[0m C A A N NN O O P Y
\x1b[32m WEEEEED\x1b[0m CCCC A A N NN OOO P Y
\x1b[32m WEEE ! EEED\x1b[0m
\x1b[32m !\x1b[0m \x1b[34mInitialization Complete!\x1b[0m This server has booted \x1b[4m${count}\x1b[0m time${count == 1 ? '' : 's'} and taken \x1b[4m${tokes}\x1b[0m toke${tokes == 1 ? '' : 's'}.
\x1b[32m !\x1b[0m This server was first booted on \x1b[4m${date}\x1b[0m.`
//Dump art to console
console.log(art);
//Add some extra padding for the port printout from server.js
process.stdout.write(' ');
}