| 'use strict';
 
const log4js = require('../log4js');
const net = require('net');
 
const END_MSG = '__LOG4JS__';
const servers = [];
 
/**
 * Creates a server, listening on config.loggerPort, config.loggerHost.
 * Output goes to config.actualAppender (config.appender is used to
 * set up that appender).
 */
function logServer(config) {
  /**
   * Takes a utf-8 string, returns an object with
   * the correct log properties.
   */
  function deserializeLoggingEvent(clientSocket, msg) {
    let loggingEvent;
    try {
      loggingEvent = JSON.parse(msg);
      loggingEvent.startTime = new Date(loggingEvent.startTime);
      loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
    } catch (e) {
      // JSON.parse failed, just log the contents probably a naughty.
      loggingEvent = {
        startTime: new Date(),
        categoryName: 'log4js',
        level: log4js.levels.ERROR,
        data: ['Unable to parse log:', msg]
      };
    }
 
    loggingEvent.remoteAddress = clientSocket.remoteAddress;
    loggingEvent.remotePort = clientSocket.remotePort;
 
    return loggingEvent;
  }
 
  const actualAppender = config.actualAppender;
 
  /* eslint prefer-arrow-callback:0 */
  const server = net.createServer(function serverCreated(clientSocket) {
    clientSocket.setEncoding('utf8');
    let logMessage = '';
 
    function logTheMessage(msg) {
      if (logMessage.length > 0) {
        actualAppender(deserializeLoggingEvent(clientSocket, msg));
      }
    }
 
    function chunkReceived(chunk) {
      let event;
      logMessage += chunk || '';
      if (logMessage.indexOf(END_MSG) > -1) {
        event = logMessage.substring(0, logMessage.indexOf(END_MSG));
        logTheMessage(event);
        logMessage = logMessage.substring(event.length + END_MSG.length) || '';
        // check for more, maybe it was a big chunk
        chunkReceived();
      }
    }
 
    clientSocket.on('data', chunkReceived);
    clientSocket.on('end', chunkReceived);
  });
 
  server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost', function () {
    servers.push(server);
    // allow the process to exit, if this is the only socket active
    server.unref();
  });
 
  return actualAppender;
}
 
function workerAppender(config) {
  let canWrite = false;
  const buffer = [];
  let socket;
 
  function write(loggingEvent) {
    // JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
    // The following allows us to serialize errors correctly.
    // Validate that we really are in this case
    if (loggingEvent && loggingEvent.stack && JSON.stringify(loggingEvent) === '{}') {
      loggingEvent = { stack: loggingEvent.stack };
    }
    socket.write(JSON.stringify(loggingEvent), 'utf8');
    socket.write(END_MSG, 'utf8');
  }
 
  function emptyBuffer() {
    let evt;
 
    /* eslint no-cond-assign:0 */
    while ((evt = buffer.shift())) {
      write(evt);
    }
  }
 
  function createSocket() {
    socket = net.createConnection(config.loggerPort || 5000, config.loggerHost || 'localhost');
    socket.on('connect', () => {
      emptyBuffer();
      canWrite = true;
    });
    socket.on('timeout', socket.end.bind(socket));
    // don't bother listening for 'error', 'close' gets called after that anyway
    socket.on('close', createSocket);
  }
 
  createSocket();
 
  return function log(loggingEvent) {
    if (canWrite) {
      write(loggingEvent);
    } else {
      buffer.push(loggingEvent);
    }
  };
}
 
function createAppender(config) {
  Eif (config.mode === 'master') {
    return logServer(config);
  }
 
  return workerAppender(config);
}
 
function configure(config, options) {
  let actualAppender;
  Eif (config.appender && config.mode === 'master') {
    log4js.loadAppender(config.appender.type);
    actualAppender = log4js.appenderMakers[config.appender.type](config.appender, options);
    config.actualAppender = actualAppender;
  }
  return createAppender(config);
}
 
function shutdown(done) {
  let toBeClosed = servers.length;
  servers.forEach(function (server) {
    server.close(function () {
      toBeClosed -= 1;
      Eif (toBeClosed < 1) {
        done();
      }
    });
  });
}
 
module.exports.appender = createAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;
  |