Source: fs-error-logger.js

const serializeError = require( "serialize-error" );
const isFunction = require( "lodash.isfunction" );
const isString = require( "lodash.isstring" );
const stringify = require( "json-stringify-pretty-compact" );
const toXML = require( "to-xml" ).toXML;

const promisify = require( "util" ).promisify;

const error = require( "./errors" );
const idFnNotAFunction = error.idFnNotAFunction;
const pathNotAString = error.pathNotAString;

const DEFAULT_OUTPUT_FOLDER = ".";

/**
 * @public
 * @author  Pedro Miguel P. S. Martins
 * @version 1.0.2
 * @module  fs-error-logger
 *
 * @desc    Writes errors into files in both JSON and XML.
 */

/**
 * @typedef   options
 * @type      {Object}
 * @property  {string}    [outputFolder="."] The default output folder.
 * @property  {function}  [idFn=Date.now]    The default id generator
 *                                                function.
 *
 * @desc      Options object determining output folder and id generator function.
 */

/**
 * @alias   module:fs-error-logger.logger
 * @param   {Object}  deps
 * @param   {Object}  deps.fs File system object that allows the logger to
 *                            write the files. Must have an async
 *                            <code>writeFile</code> function and an async
 *                            <code>mkdir</code> function.
 * @param   {module:fs-error-logger~options} [opts]  Options object determining output folder and
 *                            id generator function.
 * @returns {Object}
 *
 * @desc    Returns a logger object, with the API that allows you to write errors to files.
 */
const logger = ( { fs }, { outputFolder = DEFAULT_OUTPUT_FOLDER, idFn = Date.now } ) => {

    if ( !isString( outputFolder ) )
        throw pathNotAString( outputFolder );

    if ( !isFunction( idFn ) )
        throw idFnNotAFunction( idFn );

    if ( outputFolder[ outputFolder.length - 1 ] === "/" )
        outputFolder = outputFolder.substring( 0, outputFolder.length - 1 );

    const writeFile = promisify( fs.writeFile );
    const mkdir = promisify( fs.mkdir );

    /**
     * @public
     * @function  logJSON
     * @param     {Error}   error The error object we want to write.
     * @returns   {Promise}
     *
     * @desc      Resolves if the error was successfully written to a JSON file or rejects otherwise. The format of the file will be: <code>${error.name}_${idFn()}.json</code>.
     */
    const logJSON = error =>
        write(
            `${outputFolder}/${error.name}_${idFn()}.json`,
            stringify( serializeError( error ), { indent: 4 } )
        );

    /**
     * @public
     * @function  logXML
     * @param     {Error}   error   The error object we want to write.
     * @returns   {Promise}
     *
     * @desc      Resolves if the error was successfully written to a XML file or rejects otherwise. The format of the file will be: <code>${error.name}_${idFn()}.xml</code>.
     */
    const logXML = error =>
        write(
            `${outputFolder}/${error.name}_${idFn()}.xml`,
            toXML( { error: serializeError( error ) } )
        );


    /**
     * @private
     * @function write
     * @param   {string}  fileName      The name of the file to be created.
     * @param   {string}  fileContent   The content of the file.
     * @returns {Promise}
     *
     * @desc    If successful, resolves after creates a file with given name
     *          and content.Otherwise rejects with error.
     */
    const write = ( fileName, fileContent ) => {
        if ( !fs.existsSync( outputFolder ) )
            return mkdir( outputFolder )
                .then( () => writeFile( fileName, fileContent ) );

        return writeFile( fileName, fileContent );
    };

    /**
     * @public
     * @function  setOutputFolder
     * @param     {string}  newFolder The path of the output folder.
     * @throws    {PathNotAString}    If <code>newFolder</code> is not a string.
     *
     * @desc      Sets the output folder path to the one passed.
     */
    const setOutputFolder = newFolder => {
        if ( !isString( newFolder ) )
            throw pathNotAString( newFolder );

        if ( newFolder === "" ) {
            outputFolder = DEFAULT_OUTPUT_FOLDER;
        } else {
            outputFolder = newFolder;
        }
    };

    /**
     * @public
     * @function  getOutputFolder
     * @returns   {string}
     *
     * @desc      Returns the current output folder path.
     */
    const getOutputFolder = () => outputFolder;

    /**
     * @public
     * @function  setIdFn
     * @param     {function}  newFn   A new Id generator function.
     * @throws    {IdFnNotAFunction}  If <code>newFn</code> is not a function.
     *
     * @desc      Sets the current Id generator function to the one given.
     */
    const setIdFn = newFn => {
        if ( !isFunction( newFn ) )
            throw idFnNotAFunction( newFn );
        idFn = newFn;
    };

    /**
     * @public
     * @function  getIdFn
     * @returns   {function}
     *
     * @desc      Returns the current id generator function.
     */
    const getIdFn = () => idFn;

    return {
        logJSON,
        logXML,
        setOutputFolder,
        getOutputFolder,
        setIdFn,
        getIdFn
    };
};

module.exports.logger = logger;

const fs = require( "fs" );

/**
 * @private
 * @param     {module:fs-error-logger~options}    [opts] Options object determining output folder and
 *                                id generator function.
 * @returns   {Object}
 *
 * @desc      Returns a logger object with all the dependencies pre-injected and with the given options, ready to use.
 *
 * @example
 * const loggerFactory = require("fs-error-logger");
 * const logger = loggerFactory({ outputFolder: ".", idFn: Date.now });
 */
module.exports = opts => {
    if ( opts === undefined || opts === null )
        return logger( { fs }, {} );
    return logger( { fs }, opts );
};