Permalink
Browse files

Implemented logs functionality. Closes #5

  • Loading branch information...
1 parent 49ab46a commit ef6c2721431f802d11d28d64aba6711eff0d60d2 @pboos pboos committed Mar 13, 2013
View
@@ -4,3 +4,4 @@ lib-cov/
coverage.html
test/mock_data/_documentation/
+access_logs.db
View
@@ -0,0 +1,78 @@
+/*global require:true, exports:true */
+var sqlite3 = require('sqlite3');
+var _ = require('underscore');
+
+function AccessLog() {
+ this.db = new sqlite3.Database('access_logs.db');
+ this.ensureCreation();
+}
+
+var extractRequestHeaders = function(req) {
+ var result = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\n';
+ result += _.map(_.pairs(req.headers), function(pair) {
+ return pair[0] + ': ' + pair[1];
+ }).join('\n');
+ return result;
+};
+
+var extractResponseHeaders = function(res) {
+ return _.map(_.pairs(res._headers), function(pair) {
+ return res._headerNames[pair[0]] + ': ' + pair[1];
+ }).join('\n');
+};
+
+AccessLog.prototype.insertDb = function(req, resStatus, resHeaders, resBody) {
+ if (req.url === '/favicon.ico') { return; }
+ var requestHeaders = extractRequestHeaders(req);
+ var requestBody = req.rawBody;
+
+ if (typeof(resBody) === 'object') {
+ resBody = JSON.stringify(resBody);
+ }
+
+ this.db.run('INSERT INTO logs ' +
+ '(client, request_method, request_path, request_headers, request_body, response_status, response_headers, response_body) ' +
+ 'VALUES (?,?,?,?,?,?,?,?)', [ req.connection.remoteAddress, req.method.toUpperCase(), req.url, requestHeaders, requestBody, resStatus, resHeaders, resBody ]);
+};
+
+AccessLog.prototype.ensureCreation = function() {
+ this.db.run('CREATE TABLE IF NOT EXISTS logs (' +
+ '_id INTEGER PRIMARY KEY AUTOINCREMENT,' +
+ 'time DATETIME DEFAULT CURRENT_TIMESTAMP,' +
+ 'client TEXT,' +
+ 'request_method TEXT,' +
+ 'request_path TEXT,' +
+ 'request_headers TEXT,' +
+ 'request_body TEXT,' +
+ 'response_status INTEGER,' +
+ 'response_headers TEXT,' +
+ 'response_body TEXT' +
+ ')');
+};
+
+AccessLog.prototype.insert = function(req, status, body) {
+ var responseHeaders = extractResponseHeaders(req.res);
+ this.insertDb(req, status, responseHeaders, body);
+};
+
+AccessLog.prototype.insertProxy = function(req) {
+ this.insertDb(req, 0, 'PROXY', 'PROXY');
+};
+
+AccessLog.prototype.getLogs = function(callback) {
+ this.db.all('SELECT *, strftime("%s", "time") AS timestamp FROM logs ORDER BY _id DESC LIMIT 100', function(err, rows) {
+ rows = _.map(rows, function(row) {
+ row.isProxied = function() {
+ return this.response_status === 0;
+ };
+ return row;
+ });
+ callback(err, rows);
+ });
+};
+
+AccessLog.prototype.clear = function(callback) {
+ this.db.run('DELETE FROM logs;', callback);
+};
+
+exports.AccessLog = AccessLog;
View
@@ -5,6 +5,7 @@ var url = require('url');
var fs = require('fs');
var httpProxy = require('http-proxy');
var _ = require('underscore');
+var AccessLog = require('./access_log').AccessLog;
require('broware');
var TEMPLATE_PATTERN = new RegExp(/"?\{\{([a-zA-Z0-9\-_]+)(\([""0-9a-zA-Z,]+\))?\}\}"?/g);
@@ -16,6 +17,7 @@ exports.version = '0.1.6';
function MockServer(options) {
this.options = options;
this.ensureOptions();
+ this.log = new AccessLog();
}
MockServer.prototype.start = function() {
@@ -89,12 +91,31 @@ MockServer.prototype.startMock = function() {
if (this.readConfig().cors) {
app.use(this.allowCrossDomain);
}
+ app.use(function(req, res, next) {
+ var data = '';
+ req.setEncoding('utf8');
+ req.on('data', function(chunk) {
+ data += chunk;
+ });
+ req.on('end', function() {
+ req.rawBody = data;
+ next();
+ });
+ });
+ app.use(express.bodyParser());
+
+ app.locals.moment = require('moment');
+
app.use('/_documentation', express.static(__dirname + '/static'));
+ app.use('/_logs', express.static(__dirname + '/static'));
app.get('/_documentation/', this.getApiDocumentation);
+ app.get('/_logs/', this.getLogs);
+
app.get('*', this.handleAnyRequest);
app.post('*', this.handleAnyRequest);
app.delete('*', this.handleAnyRequest);
app.put('*', this.handleAnyRequest);
+
this.mock_server = app.listen(3001);
};
@@ -111,6 +132,7 @@ MockServer.prototype.startProxy = function() {
if (self.options.log_enabled) {
if (self.shouldProxy(req)) {
console.log('==> Proxy');
+ self.log.insertProxy(req);
} else {
console.log('==> ' + self.getRequestInfo(req).file);
}
@@ -188,8 +210,10 @@ MockServer.prototype.handleAnyRequest = function(req, res){
if (!fs.existsSync(info.file)) {
var staticFile = mock.options.path + url.parse(req.url).pathname;
if (fs.existsSync(staticFile)) {
+ mock.log.insert(req, 200, 'File contents');
return res.sendfile(staticFile);
} else {
+ mock.log.insert(req, 404, '');
return res.send(404);
}
}
@@ -204,6 +228,7 @@ MockServer.prototype.handleAnyRequest = function(req, res){
}
}
res.set(data.response.headers);
+ mock.log.insert(req, data.response.status, body);
res.send(data.response.status, body);
};
@@ -435,9 +460,24 @@ MockServer.prototype.getApiDocumentation = function(req, res) {
});
}
function getDocumentationHtml(documentation) {
- res.render('documentation', {calls: documentation});
+ res.render('documentation', {title: 'API Documentation', calls: documentation});
}
getDocumentation();
};
+MockServer.prototype.getLogs = function(req, res) {
+ res.app.set('mock').log.getLogs(function(err, logs) {
+ if (err) { return res.send(400, err); }
+ logs = _.map(logs, function(log) {
+ if (log.response_body &&
+ log.response_body.length > 0 &&
+ (log.response_body.charAt(0) === '{' || log.response_body.charAt(0) === '[')) {
+ log.response_body = JSON.stringify(JSON.parse(log.response_body), null, 2);
+ }
+ return log;
+ });
+ res.render('logs', {title: 'Access Logs', logs: logs});
+ });
+};
+
exports.MockServer = MockServer;
@@ -18,6 +18,14 @@ ul, li {
background: #cccccc;
}
+.right {
+ float: right;
+}
+
+label.client, label.status {
+ margin-right: 10px;
+}
+
label.method {
min-width: 80px;
display: inline-block;
Oops, something went wrong.

0 comments on commit ef6c272

Please sign in to comment.