highcharts-export-server
Version:
Convert Highcharts.JS charts into static image files.
266 lines (226 loc) • 6.84 kB
JavaScript
/*******************************************************************************
Highcharts Export Server
Copyright (c) 2016-2024, Highsoft
Licenced under the MIT licence.
Additionally a valid Highcharts license is required for use.
See LICENSE file in root for details.
*******************************************************************************/
import { appendFile, existsSync, mkdirSync } from 'fs';
// The available colors
const colors = ['red', 'yellow', 'blue', 'gray', 'green'];
// The default logging config
let logging = {
// Flags for logging status
toConsole: true,
toFile: false,
pathCreated: false,
// Log levels
levelsDesc: [
{
title: 'error',
color: colors[0]
},
{
title: 'warning',
color: colors[1]
},
{
title: 'notice',
color: colors[2]
},
{
title: 'verbose',
color: colors[3]
},
{
title: 'benchmark',
color: colors[4]
}
],
// Log listeners
listeners: []
};
/**
* Logs the provided texts to a file, if file logging is enabled. It creates
* the necessary directory structure if not already created and appends the
* content, including an optional prefix, to the specified log file.
*
* @param {string[]} texts - An array of texts to be logged.
* @param {string} prefix - An optional prefix to be added to each log entry.
*/
const logToFile = (texts, prefix) => {
if (!logging.pathCreated) {
// Create if does not exist
!existsSync(logging.dest) && mkdirSync(logging.dest);
// We now assume the path is available, e.g. it's the responsibility
// of the user to create the path with the correct access rights.
logging.pathCreated = true;
}
// Add the content to a file
appendFile(
`${logging.dest}${logging.file}`,
[prefix].concat(texts).join(' ') + '\n',
(error) => {
if (error) {
console.log(`[logger] Unable to write to log file: ${error}`);
logging.toFile = false;
}
}
);
};
/**
* Logs a message. Accepts a variable amount of arguments. Arguments after
* `level` will be passed directly to console.log, and/or will be joined
* and appended to the log file.
*
* @param {any} args - An array of arguments where the first is the log level
* and the rest are strings to build a message with.
*/
export const log = (...args) => {
const [newLevel, ...texts] = args;
// Current logging options
const { levelsDesc, level } = logging;
// Check if log level is within a correct range or is a benchmark log
if (
newLevel !== 5 &&
(newLevel === 0 || newLevel > level || level > levelsDesc.length)
) {
return;
}
// Get rid of the GMT text information
const newDate = new Date().toString().split('(')[0].trim();
// Create a message's prefix
const prefix = `${newDate} [${levelsDesc[newLevel - 1].title}] -`;
// Call available log listeners
logging.listeners.forEach((fn) => {
fn(prefix, texts.join(' '));
});
// Log to console
if (logging.toConsole) {
console.log.apply(
undefined,
[prefix.toString()[logging.levelsDesc[newLevel - 1].color]].concat(texts)
);
}
// Log to file
if (logging.toFile) {
logToFile(texts, prefix);
}
};
/**
* Logs an error message with its stack trace. Optionally, a custom message
* can be provided.
*
* @param {number} level - The log level.
* @param {Error} error - The error object.
* @param {string} customMessage - An optional custom message to be logged along
* with the error.
*/
export const logWithStack = (newLevel, error, customMessage) => {
// Get the main message
const mainMessage = customMessage || error.message;
// Current logging options
const { level, levelsDesc } = logging;
// Check if log level is within a correct range
if (newLevel === 0 || newLevel > level || level > levelsDesc.length) {
return;
}
// Get rid of the GMT text information
const newDate = new Date().toString().split('(')[0].trim();
// Create a message's prefix
const prefix = `${newDate} [${levelsDesc[newLevel - 1].title}] -`;
// If the customMessage exists, we want to display the whole stack message
const stackMessage =
error.message !== error.stackMessage || error.stackMessage === undefined
? error.stack
: error.stack.split('\n').slice(1).join('\n');
// Combine custom message or error message with error stack message
const texts = [mainMessage, '\n', stackMessage];
// Log to console
if (logging.toConsole) {
console.log.apply(
undefined,
[prefix.toString()[logging.levelsDesc[newLevel - 1].color]].concat([
mainMessage[colors[newLevel - 1]],
'\n',
stackMessage
])
);
}
// Call available log listeners
logging.listeners.forEach((fn) => {
fn(prefix, texts.join(' '));
});
// Log to file
if (logging.toFile) {
logToFile(texts, prefix);
}
};
/**
* Sets the log level to the specified value. Log levels are (0 = no logging,
* 1 = error, 2 = warning, 3 = notice, 4 = verbose or 5 = benchmark)
*
* @param {number} newLevel - The new log level to be set.
*/
export const setLogLevel = (newLevel) => {
if (newLevel >= 0 && newLevel <= logging.levelsDesc.length) {
logging.level = newLevel;
}
};
/**
* Enables file logging with the specified destination and log file.
*
* @param {string} logDest - The destination path for log files.
* @param {string} logFile - The log file name.
*/
export const enableFileLogging = (logDest, logFile) => {
// Update logging options
logging = {
...logging,
dest: logDest || logging.dest,
file: logFile || logging.file,
toFile: true
};
if (logging.dest.length === 0) {
return log(1, '[logger] File logging initialization: no path supplied.');
}
if (!logging.dest.endsWith('/')) {
logging.dest += '/';
}
};
/**
* Initializes logging with the specified logging configuration.
*
* @param {Object} loggingOptions - The logging configuration object.
*/
export const initLogging = (loggingOptions) => {
// Set all the logging options on our logging module object
for (const [key, value] of Object.entries(loggingOptions)) {
logging[key] = value;
}
// Set the log level
setLogLevel(loggingOptions && parseInt(loggingOptions.level));
// Set the log file path and name
if (loggingOptions && loggingOptions.dest && loggingOptions.toFile) {
enableFileLogging(
loggingOptions.dest,
loggingOptions.file || 'highcharts-export-server.log'
);
}
};
/**
* Adds a listener function to the logging system.
*
* @param {function} fn - The listener function to be added.
*/
export const listen = (fn) => {
logging.listeners.push(fn);
};
export default {
log,
logWithStack,
setLogLevel,
enableFileLogging,
initLogging,
listen
};