Permalink
Browse files

Initial code

  • Loading branch information...
1 parent 224630e commit 23bf76c1f36d88fc1c9a3fc0cf7cc641c99d628b Carlos Rodriguez committed Jul 21, 2012
Showing with 181 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +7 −0 Makefile
  3. +103 −0 index.js
  4. +34 −0 package.json
  5. +36 −0 test/basic.js
View
@@ -0,0 +1 @@
+node_modules
View
@@ -0,0 +1,7 @@
+REPORTER = spec
+
+test:
+ @NODE_ENV=test ./node_modules/.bin/mocha \
+ --reporter $(REPORTER)
+
+.PHONY: test
View
103 index.js
@@ -0,0 +1,103 @@
+var addr = require('addr')
+ , dateable = require('dateable')
+ ;
+
+exports = module.exports = function accesslog(options) {
+ options || (options = {});
+
+ var stream;
+ if (options.path) {
+ stream = require('fs').createWriteStream(options.path);
+ }
+ else if (options.stream) {
+ stream = options.stream;
+ }
+ else {
+ stream = process.stdout;
+ }
+ options.format || (options.format = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"");
+ var render = compile(options.format, {options: options});
+
+ return function accessLogger(req, res, next) {
+ var end = res.end;
+ res.end = function(chunk, encoding) {
+ res.end = end;
+ res.end(chunk, encoding);
+ stream.write(render(exports.tokens, req, res) + '\n', 'ascii');
+ };
+
+ next && next();
+ };
+};
+
+function compile(format, context) {
+ format = format.replace(/"/g, '\\"');
+ var js = ' return "' + format.replace(/%(>?\w|{[\w-]+}i)/g, function(_, name) {
+ return '"\n + (tokens["' + name + '"].call(this, req, res) || "-") + "';
+ }) + '";';
+ return new Function('tokens, req, res', js).bind(context);
+};
+
+exports.tokens = {};
+
+exports.tokens['h'] = exports.tokens['a'] = function(req) {
+ return addr(req, this.options.proxies);
+};
+
+exports.tokens['l'] = function(req) {
+ return null;
+};
+
+exports.tokens['u'] = function(req) {
+ return null;
+};
+
+exports.tokens['t'] = function(req) {
+ var d = new Date();
+ var offset = d.getTimezoneOffset();
+ var tz = zerofill(Math.abs(offset) / 60, 2) + zerofill(Math.abs(offset) % 60, 2);
+ tz = (offset > 0 ? '-' : '+') + tz;
+ return '[' + dateable.format(d, 'DD/MMM/YYYY:HH:MM:ss') + ' ' + tz + ']';
+};
+
+exports.tokens['r'] = function(req) {
+ return req.method + ' ' + req.url.replace('"', '\\"') + ' HTTP/' + req.httpVersionMajor + '.' + req.httpVersionMinor;
+};
+
+exports.tokens['>s'] = exports.tokens['s'] = function(req, res) {
+ return res.statusCode;
+};
+
+exports.tokens['b'] = function(req, res) {
+ var length = parseInt(getHeader(res, 'Content-Length'), 10);
+ return isNaN(length) ? null : length;
+};
+
+exports.tokens['{Referer}i'] = function(req) {
+ return req.headers['referer'] || req.headers['referrer'];
+};
+
+exports.tokens['{User-agent}i'] = function(req) {
+ return req.headers['user-agent'];
+};
+
+function zerofill(num, length) {
+ num += '';
+ while (num.length < length) {
+ num = '0' + num;
+ }
+ return num;
+}
+
+function getHeader(res, name) {
+ if (res._header && res._headerSent && !res._sentHeaders) {
+ res._sentHeaders = {};
+ res._header.split('\n').forEach(function(line) {
+ var header = line.split(': ');
+ if (header.length > 1) {
+ res._sentHeaders[header[0]] = header[1];
+ }
+ });
+ }
+ return (res._sentHeaders && res._sentHeaders[name].replace(/\r/g, '')) || null;
+}
View
@@ -0,0 +1,34 @@
+{
+ "name": "accesslog",
+ "version": "0.0.0",
+ "description": "Simple common/combined access log middleware",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/carlos8f/node-accesslog.git"
+ },
+ "keywords": [
+ "logger",
+ "log",
+ "access",
+ "common",
+ "combined",
+ "http",
+ "apache",
+ "nsca"
+ ],
+ "author": "Carlos Rodriguez <carlos@s8f.org> (http://s8f.org/)",
+ "license": "MIT",
+ "dependencies": {
+ "addr": "~1.0.0",
+ "dateable": "~0.1.2"
+ },
+ "devDependencies": {
+ "mocha": "*",
+ "request": "*",
+ "idgen": "1.x"
+ }
+}
View
@@ -0,0 +1,36 @@
+var accesslog = require('../')
+ , http = require('http')
+ , assert = require('assert')
+ , request = require('request')
+ , port = 55222
+ , baseUrl = 'http://localhost:' + port
+ , tmpLog = '/tmp/' + require('idgen')() + '.log'
+ , exec = require('child_process').exec
+ ;
+
+describe('basic test', function() {
+ before(function(done) {
+ var logger = accesslog({path: tmpLog});
+ http.createServer(function(req, res) {
+ logger(req, res, function() {
+ var content = JSON.stringify({'hello': 'world'});
+ res.writeHead(200, {'Content-Type': 'application/json', 'Content-Length': content.length});
+ res.write(content);
+ res.end();
+ });
+ }).listen(port, done);
+ });
+ it('performs a request', function(done) {
+ request({url: baseUrl + '/', json: true}, function(err, res, data) {
+ assert.equal(res.statusCode, 200);
+ assert.strictEqual(data.hello, 'world');
+ done();
+ });
+ });
+ it('can tail the log', function(done) {
+ exec('tail ' + tmpLog, function(err, stdout, stderr) {
+ assert(/^127\.0\.0\.1 \- \- \[[0-9]{2}\/\w{3}\/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2} (\+|\-)[0-9]{4}\] "GET \/ HTTP\/1\.1" 200 17 "\-" "\-"\n$/.exec(stdout));
+ done();
+ });
+ });
+});

0 comments on commit 23bf76c

Please sign in to comment.