It was just a question of time that logging was necessary for my RM Extension. After some review I choose log4javascript, because there is a AjaxAppender. What does it mean? As you know, the RM Extension is running inside the Browser and this Browser don’t allow write access to the file system ==>How to store the log results? ==>Send it to a so called “Logging Server”. OK, so we need a external Logging Server. The next challenge is SOP (same origin policy), the RM Javascript is not allowed to send (POST) the Log results to a Logging Server in a different domain/port. They best solution is to enable CORS. If there is a running Logging Server, why not using it to host the RM Extension files? Because the Browser is complaining about a mix of https and http, we need also https.
At the end we have the following Requirements for a Logging Server:
- NodeJS, because it is easy to set up and is also Javascript based
- CORS
- HTTPS with self-signed-certificates Storing the the Log information to the filessystem (or DB like MongoDB?)
Codesnipppets (will be completed, currently just a starting point):
…with MongoDB
var http = require('http');
var url = require('url');
var mongojs = require('mongojs');
var uri = "mongodb://kanban:27017/demo_database",
db = mongojs.connect(uri, ["demo_collection"]);
var collection = db.collection('Logs');
var contentType = {'Content-Type': 'text/html'};
var OK = 'OK', BAD = 'BAD REQUEST';
var MAX_URL_LENGTH = 2048, MAX_POST_DATA = 10240;
function createLog(request) {
var logObj = {};
try {
logObj.time = new Date();
logObj.method = request.method;
logObj.host = request.headers.host;
logObj.url = request.url;
var queryStrings = url.parse(request.url, true).query;
// Check if it's an empty object
if (Object.keys(queryStrings).length) {
logObj.query = queryStrings;
for (var attrname in queryStrings) {
logObj[attrname] = queryStrings[attrname];
}
}
} catch (ex) {}
return logObj;
}
http.createServer(function (request, response) {
// Bypass request with URL larger than 2K
if (request.url.length > MAX_URL_LENGTH) {
response.writeHead(414, contentType);
response.end(BAD);
} else if (request.method == 'GET') { // HTTP GET
var logObj = createLog(request);
if (logObj.query) {
delete logObj.query;
collection.save(logObj);
response.writeHead(200, contentType);
response.end(OK);
} else { // Empty request
response.writeHead(400, contentType);
response.end(BAD);
request.connection.destroy();
}
} else if(request.method == 'POST') { // HTTP POST
var logObj = createLog(request);
var postData = '';
request.on('data', function(data) {
postData += data;
// Bypass request with POST data larger than 10K
if(postData.length > MAX_POST_DATA) {
postData = "";
response.writeHead(413, contentType);
response.end(BAD);
request.connection.destroy();
}
});
request.on('end', function() {
if (!postData && !logObj.query) { // Empty request
response.writeHead(400, contentType);
response.end(BAD);
request.connection.destroy();
} else {
if (postData) {
try {
postObjs = JSON.parse(postData);
for (var attrname in postObjs) {
logObj[attrname] = postObjs[attrname];
}
} catch (ex) {
logObj.postData = postData;
}
}
if (logObj.query) {
delete logObj.query;
}
collection.save(logObj);
response.writeHead(200, contentType);
response.end(OK);
}
});
}
}).listen(process.env.PORT || 8080);