Better logging

Separated log streams for access log (defaults to stdout) and general
log (defaults to stderr). Added support for writing logs to a file.
Improved formatting and added support for JSON formatted logs.
This commit is contained in:
Zaid Abdulla committed 2017-02-19 10:22:50 -08:00
1 parent 8a33d6934a
commit 0097a75cab
7 files changed
+543 -96

No files matched your search

-58
View File
@@ -1,58 +0,0 @@
###
# Copyright 2013, 2014 Zaid Abdulla
#
# GenieACS 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.
#
# GenieACS 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 GenieACS. If not, see <http://www.gnu.org/licenses/>.
###
config = require './config'
cluster = require 'cluster'
util = require 'util'
cluster.on('listening', (worker, address) ->
util.log("Worker #{worker.process.pid} listening to #{address.address}:#{address.port}")
)
restartWorker = (worker, code, signal) ->
util.log("Worker #{worker.process.pid} died (#{worker.process.exitCode}) #{signal}")
cluster.fork()
cluster.on('exit', restartWorker)
start = (service) ->
console.error('WARNING: This is a development branch of GenieACS. DO NOT use in production.')
workerProcesses = config.get("#{service.toUpperCase()}_WORKER_PROCESSES")
cluster.setupMaster({
exec : 'lib/server',
args : [service]
})
for [0 ... workerProcesses]
cluster.fork()
process.on('SIGINT', () ->
cluster.removeListener('exit', restartWorker)
)
process.on('SIGTERM', () ->
cluster.removeListener('exit', restartWorker)
for id of cluster.workers
cluster.workers[id].kill()
)
exports.start = start
+138
View File
@@ -0,0 +1,138 @@
/**
* Copyright 2013-2017 Zaid Abdulla
*
* GenieACS 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.
*
* GenieACS 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 GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const cluster = require("cluster");
const logger = require("./logger");
const VERSION = require("../package.json").version;
function getDependencyVersions() {
let dependencies = [`node@${process.versions.node}`];
try {
let npmls = JSON.parse(require("child_process").execSync("npm ls --prod --json"));
for (let k in npmls.dependencies)
dependencies.push(`${k}@${npmls.dependencies[k].version}`);
} catch (err) {}
return dependencies.join(",");
}
function getConfig() {
let conf = [];
const keys = Object.keys(process.env);
for (let k of keys)
if (k.startsWith("GENIEACS_"))
conf.push(`${k.slice(9)}=${process.env[k]}`);
return conf.join(",");
}
function restartWorker(worker, code, signal) {
let msg = {
message: "Worker died",
pid: worker.process.pid
};
if (code != null)
msg.exitCode = code;
if (signal != null)
msg.signal = signal;
logger.error(msg);
cluster.fork();
}
function start(service) {
logger.init(service, VERSION);
const config = require("./config");
logger.info({
message: `GenieACS (genieacs-${service}) starting`,
pid: process.pid,
version: VERSION,
dependencies: getDependencyVersions(),
config: getConfig()
});
cluster.on("listening", function(worker, address) {
logger.info({
message: "Worker listening",
pid: worker.process.pid,
address: address.address,
port: address.port
});
});
cluster.on("exit", restartWorker);
process.on("SIGINT", function() {
logger.info({
message: "Received signal SIGINT, exiting",
pid: process.pid
});
cluster.removeListener("exit", restartWorker);
});
process.on("SIGTERM", function() {
logger.info({
message: "Received signal SIGTERM, exiting",
pid: process.pid
});
cluster.removeListener("exit", restartWorker);
for (let pid in cluster.workers)
cluster.workers[pid].kill();
});
logger.warn({
message: "This is a development branch of GenieACS. DO NOT use in production."
});
cluster.setupMaster({
exec: "lib/server",
args: [service]
});
let workerCount = config.get(`${service.toUpperCase()}_WORKER_PROCESSES`);
for (let i = 0; i < workerCount; ++ i)
cluster.fork();
}
process.on("uncaughtException", function(err) {
logger.error({
message: "Uncaught exception in master process, exiting",
exception: err
});
cluster.removeListener("exit", restartWorker);
for (let pid in cluster.workers)
cluster.workers[pid].kill();
logger.close();
});
exports.start = start;
+9 -6
View File
@@ -29,22 +29,27 @@ options = {
CWMP_PORT : {type : 'int', default : 7547}, CWMP_PORT : {type : 'int', default : 7547},
CWMP_INTERFACE : {type : 'string', default : '0.0.0.0'}, CWMP_INTERFACE : {type : 'string', default : '0.0.0.0'},
CWMP_SSL : {type : 'bool', default : false}, CWMP_SSL : {type : 'bool', default : false},
CWMP_LOG_FILE : {type: 'path', default : ''},
CWMP_ACCESS_LOG_FILE : {type : 'path', default : ''},
NBI_WORKER_PROCESSES : {type : 'int', default : 2}, NBI_WORKER_PROCESSES : {type : 'int', default : 2},
NBI_PORT : {type : 'int', default : 7557}, NBI_PORT : {type : 'int', default : 7557},
NBI_INTERFACE : {type : 'string', default : '0.0.0.0'}, NBI_INTERFACE : {type : 'string', default : '0.0.0.0'},
NBI_SSL : {type : 'bool', default : false}, NBI_SSL : {type : 'bool', default : false},
NBI_LOG_FILE : {type: 'path', default : ''},
NBI_ACCESS_LOG_FILE : {type : 'path', default : ''},
FS_WORKER_PROCESSES : {type : 'int', default : 2}, FS_WORKER_PROCESSES : {type : 'int', default : 2},
FS_PORT : {type : 'int', default : 7567}, FS_PORT : {type : 'int', default : 7567},
FS_INTERFACE : {type : 'string', default : '0.0.0.0'}, FS_INTERFACE : {type : 'string', default : '0.0.0.0'},
FS_SSL : {type : 'bool', default : false}, FS_SSL : {type : 'bool', default : false},
FS_IP : {type : 'string', default : '192.168.0.1'}, FS_IP : {type : 'string', default : '192.168.0.1'},
FS_LOG_FILE : {type: 'path', default : ''},
FS_ACCESS_LOG_FILE : {type : 'path', default : ''},
DOWNLOAD_TIMEOUT: {type : 'int', default : 3600}, DOWNLOAD_TIMEOUT: {type : 'int', default : 3600},
EXT_TIMEOUT: {type: 'int', default: 3000}, EXT_TIMEOUT: {type: 'int', default: 3000},
PRESETS_CACHE_DURATION : {type : 'int', default : 86400}, PRESETS_CACHE_DURATION : {type : 'int', default : 86400},
LOG_INFORMS : {type : 'bool', default : true},
DEBUG : {type : 'bool', default : false}, DEBUG : {type : 'bool', default : false},
RETRY_DELAY : {type : 'int', default : 300}, RETRY_DELAY : {type : 'int', default : 300},
SESSION_TIMEOUT : {type : 'int', default : 30}, SESSION_TIMEOUT : {type : 'int', default : 30},
@@ -53,6 +58,8 @@ options = {
TASK_PARAMETERS_BATCH_SIZE : {type : 'int', default : 32}, TASK_PARAMETERS_BATCH_SIZE : {type : 'int', default : 32},
MAX_DEPTH : {type: 'int', default : 16}, MAX_DEPTH : {type: 'int', default : 16},
COOKIES_PATH : {type : 'string'}, COOKIES_PATH : {type : 'string'},
LOG_FORMAT : {type : 'string', default : 'simple'},
ACCESS_LOG_FORMAT : {type : 'string', default : ''},
# XML configuration # XML configuration
XML_RECOVER : {type : 'bool', default : false}, XML_RECOVER : {type : 'bool', default : false},
@@ -81,7 +88,7 @@ setConfig = (name, value, commandLineArgument) ->
when 'string' when 'string'
String(val) String(val)
when 'path' when 'path'
path.resolve(val) if val then path.resolve(val) else ''
else else
null null
@@ -109,16 +116,12 @@ setConfig = (name, value, commandLineArgument) ->
# Command line arguments # Command line arguments
exports.argv = []
argv = process.argv[2..] argv = process.argv[2..]
while argv.length while argv.length
arg = argv.shift() arg = argv.shift()
if arg[0] == '-' if arg[0] == '-'
v = argv.shift() v = argv.shift()
exports.argv[arg] = v
setConfig(arg[2..], v, true) setConfig(arg[2..], v, true)
else
exports.argv.push(arg)
# Environment variable # Environment variable
+67 -19
View File
@@ -50,6 +50,7 @@ query = require './query'
device = require './device' device = require './device'
cache = require './cache' cache = require './cache'
db = require './db' db = require './db'
logger = require './logger'
MAX_CYCLES = 4 MAX_CYCLES = 4
@@ -92,7 +93,8 @@ writeResponse = (currentRequest, res) ->
currentRequest.httpResponse.end(res.data) currentRequest.httpResponse.end(res.data)
recordFault = (sessionData, fault, provisions, channels) -> recordFault = (currentRequest, fault, provisions, channels) ->
sessionData = currentRequest.sessionData
faults = sessionData.faults faults = sessionData.faults
for channel of channels for channel of channels
@@ -111,7 +113,14 @@ recordFault = (sessionData, fault, provisions, channels) ->
sessionData.faultsTouched ?= {} sessionData.faultsTouched ?= {}
sessionData.faultsTouched[channel] = true sessionData.faultsTouched[channel] = true
util.log("#{sessionData.deviceId}: Fault occurred in channel #{channel} (retries: #{sessionData.retries[channel]})")
logger.accessError({
currentRequest: currentRequest
message: 'Channel has faulted'
fault: fault
channel: channel
retries: sessionData.retries[channel]
})
for provision, i in provisions for provision, i in provisions
for channel of channels for channel of channels
@@ -128,9 +137,6 @@ recordFault = (sessionData, fault, provisions, channels) ->
inform = (currentRequest, cwmpRequest) -> inform = (currentRequest, cwmpRequest) ->
if config.get('LOG_INFORMS', currentRequest.sessionData.deviceId)
util.log("#{currentRequest.sessionData.deviceId}: Inform (#{cwmpRequest.methodRequest.event}); retry count #{cwmpRequest.methodRequest.retryCount}")
session.inform(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse) -> session.inform(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
res = soap.response({ res = soap.response({
@@ -149,14 +155,21 @@ inform = (currentRequest, cwmpRequest) ->
transferComplete = (currentRequest, cwmpRequest) -> transferComplete = (currentRequest, cwmpRequest) ->
session.transferComplete(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse, fault, operation) -> session.transferComplete(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse, operation, fault) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
if not operation?
logger.accessWarn({
currentRequest: currentRequest
message: 'Unrecognized command key'
cpeRequest: cwmpRequest.methodRequest
})
if fault if fault
for k, v of operation.retries for k, v of operation.retries
currentRequest.sessionData.retries[k] = v currentRequest.sessionData.retries[k] = v
recordFault(currentRequest.sessionData, fault, operation.provisions, operation.channels) recordFault(currentRequest, fault, operation.provisions, operation.channels)
res = soap.response({ res = soap.response({
id : cwmpRequest.id, id : cwmpRequest.id,
@@ -280,7 +293,7 @@ applyPresets = (currentRequest) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
if fault if fault
recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
session.clearProvisions(sessionData) session.clearProvisions(sessionData)
return applyPresets(currentRequest) return applyPresets(currentRequest)
@@ -321,7 +334,7 @@ applyPresets = (currentRequest) ->
message: 'The provision seems to be repeating indefinitely' message: 'The provision seems to be repeating indefinitely'
timestamp: sessionData.timestamp timestamp: sessionData.timestamp
} }
recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
session.clearProvisions(sessionData) session.clearProvisions(sessionData)
return sendRpcRequest(currentRequest) return sendRpcRequest(currentRequest)
@@ -329,7 +342,7 @@ applyPresets = (currentRequest) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
if fault if fault
recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
session.clearProvisions(sessionData) session.clearProvisions(sessionData)
return applyPresets(currentRequest) return applyPresets(currentRequest)
@@ -354,7 +367,7 @@ nextRpc = (currentRequest) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
if fault if fault
recordFault(currentRequest.sessionData, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels) recordFault(currentRequest, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels)
session.clearProvisions(currentRequest.sessionData) session.clearProvisions(currentRequest.sessionData)
return nextRpc(currentRequest) return nextRpc(currentRequest)
@@ -383,7 +396,11 @@ nextRpc = (currentRequest) ->
# Delete if expired # Delete if expired
if task.expiry <= currentRequest.sessionData.timestamp if task.expiry <= currentRequest.sessionData.timestamp
util.log("#{currentRequest.sessionData.deviceId}: Task is expired #{task.name}(#{task._id})") logger.accessInfo({
currentRequest: currentRequest
message: 'Task expired'
task: task
})
currentRequest.sessionData.doneTasks ?= [] currentRequest.sessionData.doneTasks ?= []
currentRequest.sessionData.doneTasks.push(String(task._id)) currentRequest.sessionData.doneTasks.push(String(task._id))
if channel of currentRequest.sessionData.faults if channel of currentRequest.sessionData.faults
@@ -443,7 +460,10 @@ sendRpcRequest = (currentRequest, id, rpcRequest) ->
delete currentSessions.get(currentRequest.httpRequest.connection)[currentRequest.sessionData.sessionId] delete currentSessions.get(currentRequest.httpRequest.connection)[currentRequest.sessionData.sessionId]
if isNew if isNew
util.log("#{currentRequest.sessionData.deviceId}: New device registered") logger.accessInfo({
currentRequest: currentRequest
message: 'New device registered'
})
db.clearTasks(currentRequest.sessionData.deviceId, currentRequest.sessionData.doneTasks, (err) -> db.clearTasks(currentRequest.sessionData.deviceId, currentRequest.sessionData.doneTasks, (err) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
@@ -500,7 +520,12 @@ sendRpcRequest = (currentRequest, id, rpcRequest) ->
return sendRpcRequest(currentRequest, id, rpcRequest) return sendRpcRequest(currentRequest, id, rpcRequest)
) )
util.log("#{currentRequest.sessionData.deviceId}: #{rpcRequest.type} (#{id})") logger.accessInfo({
currentRequest: currentRequest
message: 'ACS request'
acsRequest: rpcRequest
acsRequestId: id
})
res = soap.response({ res = soap.response({
id : id, id : id,
@@ -647,30 +672,53 @@ listener = (httpRequest, httpResponse) ->
if cwmpRequest.methodRequest? if cwmpRequest.methodRequest?
if cwmpRequest.methodRequest.type is 'Inform' if cwmpRequest.methodRequest.type is 'Inform'
logger.accessInfo({
currentRequest: currentRequest
message: 'Inform'
cpeRequest: cwmpRequest.methodRequest
cpeRequestId: cwmpRequest.id
});
inform(currentRequest, cwmpRequest) inform(currentRequest, cwmpRequest)
else if cwmpRequest.methodRequest.type is 'TransferComplete' else if cwmpRequest.methodRequest.type is 'TransferComplete'
logger.accessInfo({
currentRequest: currentRequest
message: 'CPE request'
cpeRequest: cwmpRequest.methodRequest
cpeRequestId: cwmpRequest.id
});
transferComplete(currentRequest, cwmpRequest) transferComplete(currentRequest, cwmpRequest)
else if cwmpRequest.methodRequest.type is 'GetRPCMethods' else if cwmpRequest.methodRequest.type is 'GetRPCMethods'
util.log("#{currentRequest.sessionData.deviceId}: GetRPCMethods") logger.accessInfo({
currentRequest: currentRequest
message: 'CPE request'
cpeRequest: cwmpRequest.methodRequest
cpeRequestId: cwmpRequest.id
});
res = soap.response({ res = soap.response({
id : cwmpRequest.id, id : cwmpRequest.id,
methodResponse : {type : 'GetRPCMethodsResponse', methodList : ['Inform', 'GetRPCMethods', 'TransferComplete', 'RequestDownload']}, methodResponse : {type : 'GetRPCMethodsResponse', methodList : ['Inform', 'GetRPCMethods', 'TransferComplete', 'RequestDownload']},
cwmpVersion : currentRequest.sessionData.cwmpVersion cwmpVersion : currentRequest.sessionData.cwmpVersion
}) })
writeResponse(currentRequest, res) writeResponse(currentRequest, res)
else if cwmpRequest.methodRequest.type is 'TransferComplete' else
return throwError(new Error('ACS method not supported'), currentRequest.httpResponse) if err return throwError(new Error('ACS method not supported'), currentRequest.httpResponse)
else if cwmpRequest.methodResponse? else if cwmpRequest.methodResponse?
session.rpcResponse(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.methodResponse, (err) -> session.rpcResponse(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.methodResponse, (err) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
nextRpc(currentRequest) nextRpc(currentRequest)
) )
else if cwmpRequest.fault? else if cwmpRequest.fault?
logger.accessWarn({
currentRequest: currentRequest
message: 'CPE fault'
cpeFault: cwmpRequest.fault.detail
cpeRequestId: cwmpRequest.id
});
session.rpcFault(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.fault, (err, fault) -> session.rpcFault(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.fault, (err, fault) ->
return throwError(err, currentRequest.httpResponse) if err return throwError(err, currentRequest.httpResponse) if err
if fault if fault
recordFault(currentRequest.sessionData, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels) recordFault(currentRequest, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels)
session.clearProvisions(currentRequest.sessionData) session.clearProvisions(currentRequest.sessionData)
nextRpc(currentRequest) nextRpc(currentRequest)
) )
@@ -682,7 +730,7 @@ listener = (httpRequest, httpResponse) ->
for k, v of operations[i].retries for k, v of operations[i].retries
sessionData.retries[k] = v sessionData.retries[k] = v
recordFault(currentRequest.sessionData, fault, operations[i].provisions, operations[i].channels) recordFault(currentRequest, fault, operations[i].provisions, operations[i].channels)
nextRpc(currentRequest) nextRpc(currentRequest)
) )
+315
View File
@@ -0,0 +1,315 @@
/**
* Copyright 2013-2017 Zaid Abdulla
*
* GenieACS 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.
*
* GenieACS 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 GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const fs = require("fs");
const os = require("os");
const config = require("./config");
const REOPEN_EVERY = 60000;
const LOG_FORMAT = config.get("LOG_FORMAT");
const ACCESS_LOG_FORMAT = config.get("ACCESS_LOG_FORMAT") || LOG_FORMAT;
const defaultMeta = {};
let LOG_SYSTEMD = false;
let ACCESS_LOG_SYSTEMD = false;
let LOG_FILE, ACCESS_LOG_FILE;
let logStream = fs.createWriteStream(null, {fd: process.stderr.fd});
let logStat = fs.fstatSync(logStream.fd);
let accessLogStream = fs.createWriteStream(null, {fd: process.stdout.fd});
let accessLogStat = fs.fstatSync(accessLogStream.fd);
// Reopen if original files have been moved (e.g. logrotate)
function reopen() {
let counter = 1;
if (LOG_FILE) {
++ counter;
fs.stat(LOG_FILE, function(err, stat) {
if (err && !err.message.startsWith("ENOENT:"))
throw err;
if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) {
logStream.end();
logStream = fs.createWriteStream(null,
{fd: fs.openSync(LOG_FILE, "a")});
logStat = fs.fstatSync(logStream.fd);
}
if (-- counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
});
}
if (ACCESS_LOG_FILE) {
++ counter;
fs.stat(ACCESS_LOG_FILE, function(err, stat) {
if (err && !err.message.startsWith("ENOENT:"))
throw err;
if (!(stat && stat.dev === accessLogStat.dev && stat.ino === accessLogStat.ino)) {
accessLogStream.end();
accessLogStream = fs.createWriteStream(null,
{fd: fs.openSync(ACCESS_LOG_FILE, "a")});
accessLogStat = fs.fstatSync(accessLogStream.fd);
}
if (-- counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
});
}
if (-- counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
}
function init(service, version) {
defaultMeta.hostname = os.hostname();
defaultMeta.pid = process.pid;
defaultMeta.name = `genieacs-${service}`;
defaultMeta.version = version;
LOG_FILE = config.get(`${service.toUpperCase()}_LOG_FILE`);
ACCESS_LOG_FILE = config.get(`${service.toUpperCase()}_ACCESS_LOG_FILE`);
if (LOG_FILE) {
logStream = fs.createWriteStream(null,
{fd: fs.openSync(LOG_FILE, "a")});
logStat = fs.fstatSync(logStream.fd);
}
if (ACCESS_LOG_FILE) {
accessLogStream = fs.createWriteStream(null,
{fd: fs.openSync(ACCESS_LOG_FILE, "a")});
accessLogStat = fs.fstatSync(accessLogStream.fd);
}
// Determine if logs are going to journald
const JOURNAL_STREAM = process.env["JOURNAL_STREAM"];
if (JOURNAL_STREAM) {
let [dev, inode] = JOURNAL_STREAM.split(":");
dev = parseInt(dev);
inode = parseInt(inode);
LOG_SYSTEMD = (logStat.dev === dev && logStat.ino === inode);
ACCESS_LOG_SYSTEMD = (accessLogStat.dev === dev && accessLogStat.ino === inode);
}
if (LOG_FILE || ACCESS_LOG_FILE) {
// Can't use setInterval as we need all workers to cehck at the same time
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
}
}
function close() {
accessLogStream.end();
logStream.end();
}
function flatten(details) {
if (details.currentRequest) {
details.deviceId = details.currentRequest.sessionData.deviceId;
details.remoteAddress = details.currentRequest.httpRequest.connection.remoteAddress;
delete details.currentRequest;
}
if (details.exception) {
details.exceptionName = details.exception.name;
details.exceptionMessage = details.exception.message;
details.exceptionStack = details.exception.stack;
delete details.exception;
}
if (details.task) {
details.taskId = details.task._id;
delete details.task;
}
if (details.acsRequest) {
details.acsRequestName = details.acsRequest.type;
if (details.acsRequest.commandKey)
details.acsRequestCommandKey = details.acsRequest.commandKey;
delete details.acsRequest;
}
if (details.cpeRequest) {
if (details.cpeRequest.type === "Inform") {
details.informEvent = details.cpeRequest.event.join(",");
details.informRetryCount = details.cpeRequest.retryCount;
}
else {
details.cpeRequestName = details.cpeRequest.type;
if (details.cpeRequest.commandKey)
details.cpeRequestCommandKey = details.cpeRequest.commandKey;
}
delete details.cpeRequest;
}
if (details.cpeFault) {
details.cpeFaultCode = details.cpeFault.faultCode;
details.cpeFaultString = details.cpeFault.faultString;
delete details.cpeFault;
}
if (details.fault) {
details.faultCode = details.fault.code;
details.faultMessage = details.fault.message;
delete details.fault;
}
return details;
}
function formatJson(details, systemd) {
if (systemd) {
let severity = "";
if (details.severity === "info")
severity = "<6>";
else if (details.severity === "warn")
severity = "<4>";
else if (details.severity === "error")
severity = "<3>";
return `${severity}${JSON.stringify(flatten(details))}${os.EOL}`;
}
return `${JSON.stringify(flatten(details))}${os.EOL}`;
}
function formatSimple(details, systemd) {
flatten(details);
let remote = "";
if (details.remoteAddress) {
if (details.deviceId)
remote = `${details.remoteAddress} ${details.deviceId}: `;
else
remote = `${details.remoteAddress}: `;
}
const keys = Object.keys(details);
const skip = {
deviceId: 1,
remoteAddress: 1,
severity: 1,
timestamp: 1,
message: 1
};
let meta = "";
let kv = [];
for (let k of keys)
if (!skip[k])
kv.push(`${k}=${JSON.stringify(details[k])}`);
if (kv.length)
meta = `; ${kv.join(" ")}`;
if (systemd) {
let severity = "";
if (details.severity === "info")
severity = "<6>";
else if (details.severity === "warn")
severity = "<4>";
else if (details.severity === "error")
severity = "<3>";
return `${severity}${remote}${details.message}${meta}${os.EOL}`;
}
return `${details.timestamp} [${details.severity.toUpperCase()}] ${remote}${details.message}${meta}${os.EOL}`;
}
function log(details) {
details.timestamp = new Date().toISOString();
if (LOG_FORMAT === "json") {
details = Object.assign({}, defaultMeta, details);
logStream.write(formatJson(details, LOG_SYSTEMD));
} else {
logStream.write(formatSimple(details, LOG_SYSTEMD));
}
}
function info(details) {
details.severity = "info";
log(details);
}
function warn(details) {
details.severity = "warn";
log(details);
}
function error(details) {
details.severity = "error";
log(details);
}
function accessLog(details) {
details.timestamp = new Date().toISOString();
if (ACCESS_LOG_FORMAT === "json") {
Object.assign(details, defaultMeta);
accessLogStream.write(formatJson(details, ACCESS_LOG_SYSTEMD));
} else {
accessLogStream.write(formatSimple(details, ACCESS_LOG_SYSTEMD));
}
}
function accessInfo(details) {
details.severity = "info";
accessLog(details);
}
function accessWarn(details) {
details.severity = "warn";
accessLog(details);
}
function accessError(details) {
details.severity = "error";
accessLog(details);
}
exports.init = init;
exports.close = close;
exports.info = info;
exports.warn = warn;
exports.error = error;
exports.accessInfo = accessInfo;
exports.accessWarn = accessWarn;
exports.accessError = accessError;
+12 -10
View File
@@ -15,17 +15,18 @@
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>. # along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
### ###
service = process.argv[2]
if not service?
throw new Error('Missing argument cwmp, fs, or nbi')
util = require 'util' util = require 'util'
cluster = require 'cluster' cluster = require 'cluster'
logger = require './logger'
logger.init(service, require('../package.json').version)
config = require './config' config = require './config'
db = require './db' db = require './db'
extensions = require './extensions' extensions = require './extensions'
service = config.argv[0]
if not service?
throw new Error('Missing argument cwmp, fs, or nbi')
networkInterface = config.get("#{service.toUpperCase()}_INTERFACE") networkInterface = config.get("#{service.toUpperCase()}_INTERFACE")
port = config.get("#{service.toUpperCase()}_PORT") port = config.get("#{service.toUpperCase()}_PORT")
useHttps = config.get("#{service.toUpperCase()}_SSL") useHttps = config.get("#{service.toUpperCase()}_SSL")
@@ -45,21 +46,22 @@ exit = () ->
if not server if not server
db.disconnect() db.disconnect()
extensions.killAll() extensions.killAll()
logger.close()
return return
server.close(() -> server.close(() ->
db.disconnect() db.disconnect()
extensions.killAll() extensions.killAll()
logger.close()
) )
process.on('uncaughtException', (err) -> process.on('uncaughtException', (err) ->
try logger.error({
util.error("#{new Date().toISOString()} - #{err.stack}\n") message: 'Uncaught exception'
exception: err
})
exit() exit()
catch err
util.error("#{new Date().toISOString()} - #{err.stack}\n")
throw err
) )
+2 -3
View File
@@ -164,7 +164,6 @@ transferComplete = (sessionData, rpcReq, callback) ->
operation = sessionData.operations[commandKey] operation = sessionData.operations[commandKey]
if not operation? if not operation?
# TODO Show a warning
return callback(null, {type : 'TransferCompleteResponse'}) return callback(null, {type : 'TransferCompleteResponse'})
instance = operation.args.instance instance = operation.args.instance
@@ -182,7 +181,7 @@ transferComplete = (sessionData, rpcReq, callback) ->
timestamp: operation.timestamp timestamp: operation.timestamp
} }
return callback(err, {type : 'TransferCompleteResponse'}, fault, operation) return callback(err, {type : 'TransferCompleteResponse'}, operation, fault)
) )
loadPath(sessionData, ['Downloads', instance, '*']) loadPath(sessionData, ['Downloads', instance, '*'])
@@ -218,7 +217,7 @@ transferComplete = (sessionData, rpcReq, callback) ->
{value: [timestamp, [+rpcReq.completeTime, 'xsd:dateTime']]}, toClear) {value: [timestamp, [+rpcReq.completeTime, 'xsd:dateTime']]}, toClear)
clear(sessionData, toClear, (err) -> clear(sessionData, toClear, (err) ->
return callback(err, {type : 'TransferCompleteResponse'}) return callback(err, {type : 'TransferCompleteResponse'}, operation)
) )
) )