Skip to content

Commit

Permalink
Merge afcafe3 into 2fcaf32
Browse files Browse the repository at this point in the history
  • Loading branch information
keis committed Jun 5, 2015
2 parents 2fcaf32 + afcafe3 commit 32bfd33
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 94 deletions.
4 changes: 2 additions & 2 deletions lib/index.js
Expand Up @@ -327,7 +327,7 @@ Logger.prototype.callSinks = function (record) {
for (i = 0, length = this.sinks.length; i < length; i++) {
sink = this.sinks[i]
if (record.level >= (sink.level || 0)) {
sink.handle(record)
sink.write(record)
}
}
}
Expand Down Expand Up @@ -374,7 +374,7 @@ Logger.prototype.log = function () {
// given function as the write function.
Logger.prototype.addSink = function (sink) {
if (!(sink instanceof Sink)) {
sink = new Sink(sink)
sink = new Sink(sinks.writableStream({}, sink)())
}
this.sinks.push(sink)
}
Expand Down
84 changes: 62 additions & 22 deletions lib/sinks.js
@@ -1,5 +1,21 @@
var fs = require('fs')
, stream = require('readable-stream')
, assign = require('object-assign')
, inherits = require('util').inherits
, defaultFormat
, defaultWrite

// # Exports
exports.createFormatter = createFormatter
exports.writableStream = writableStream

// The Sink constructor is exported for third-party or application specific
// sink implementations
exports.Sink = Sink

// Export built-in sinks
exports.console = console
exports.file = file

// Last resort error printing for when everything fails
function printError(err) {
Expand Down Expand Up @@ -32,7 +48,24 @@ function createFormatter(pattern) {
}
}

exports.createFormatter = createFormatter
// # writableStream

// A helper to create a write a stream from a function
function writableStream(options, write) {
function Writable (override) {
if (!(this instanceof Writable)) {
return new Writable()
}
this.options = assign({}, options, override)
stream.Writable.call(this, this.options)
}

inherits(Writable, stream.Writable)

Writable.prototype._write = write

return Writable
}

// Default formatter used by sinks when nothing else is specified that tries
// to provide a human friendly format that contains most information without
Expand All @@ -41,33 +74,42 @@ defaultFormat = createFormatter('[:date :time] - :levelName - :message')

// # Sink

// Default sink write function that will prepare a output line using the
// `format` function of the bound sink and write the formatted record to STDERR
function defaultWrite(record) {
process.stderr.write(this.format(record) + '\n')
// Default sink write stream that expects a stream of formatted log messages
// that will be written to STDERR joined with newlines.
function WithNewLine() {
stream.Transform.call(this, {objectMode: true})
}

inherits(WithNewLine, stream.Transform)

WithNewLine.prototype._transform = function (obj, enc, done) {
this.push(obj + '\n')
done()
}

defaultWrite = new WithNewLine()
defaultWrite.pipe(process.stderr)

// Constructor for a sink object that couple a write function and a format
// function. A sink may also have a log level associated that is inspected by
// the log methods. If the sink defines a `reset` function it will be called to
// indicated that any associated file streams etc should be reopened.
function Sink(write, format, level) {
this.write = write || defaultWrite
stream.Transform.call(this, {objectMode: true})
this.format = format || defaultFormat
this.setLevel(level)
this.pipe(write || defaultWrite)
}

Sink.prototype.setLevel = require('./levels').setLevel
inherits(Sink, stream.Transform)

// Call the write function, `this` is bound to the sink to provide access the
// format function.
Sink.prototype.handle = function (record) {
this.write.call(this, record)
Sink.prototype._transform = function (obj, enc, done) {
this.push(this.format(obj))
done()
}

// Export sink constructor for third-party or application specific sink
// implementations
exports.Sink = Sink

Sink.prototype.setLevel = require('./levels').setLevel

// Each sink type needs to have a factory method that takes a single option
// hash as argument. The details of the hash is implementation specific except
Expand All @@ -77,33 +119,31 @@ exports.Sink = Sink
// # console

// Create a logger that writes to stderr as per default write
exports.console = function console(options) {
function console(options) {
options = options || {}
return new Sink(null, options.format, options.level)
}

// # file

// Create a logger that writes to a file
exports.file = function file(options) {
function file(options) {
var path = options.path
, streamOptions = {flags: 'a', encoding: 'utf-8'}
, nl = new WithNewLine()
, sink

// write function writes to the bound stream
function write(record) {
this.stream.write(this.format(record) + '\n')
}

// Create a sink instance and attach a function to reopen the stream to the
// file which may be useful when logrotate and similar systems comes into
// play.
sink = new Sink(write, options.format, options.level)
sink = new Sink(nl, options.format, options.level)
sink.reset = function () {
if (this.stream) {
nl.unpipe(this.stream)
this.stream.end()
}
this.stream = fs.createWriteStream(path, streamOptions)
nl.pipe(this.stream)
this.stream.on('error', printError)
}
// reset the file stream immediately to open the first stream
Expand Down
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -27,9 +27,12 @@
"istanbul": "^0.3.5",
"matcha": "^0.6.0",
"mocha": "^2.0.1",
"rewire": "^2.3.3",
"sinon": "^1.10.0"
},
"dependencies": {
"dateformat": "^1.0.11"
"dateformat": "^1.0.11",
"object-assign": "^3.0.0",
"readable-stream": "^1.0.33"
}
}
24 changes: 24 additions & 0 deletions test/unit/test.console.coffee
@@ -0,0 +1,24 @@
rewire = require 'rewire'
{Writable} = require 'readable-stream'

describe "console", ->
sinks = rewire '../../lib/sinks'
write = new Writable

beforeEach ->
sinks.__set__ 'defaultWrite', write

it "returns a `Sink` instance", ->
sink = sinks.console()
assert.instanceOf sink, sinks.Sink

it "attaches given formatter to sink", ->
sentinel = ->
sink = sinks.console
format: sentinel
assert.strictEqual sink.format, sentinel

it "sets level on sink", ->
sink = sinks.console
level: 20
assert.strictEqual sink.level, 20
20 changes: 20 additions & 0 deletions test/unit/test.file.coffee
@@ -0,0 +1,20 @@
describe "file", ->
sinks = require '../../lib/sinks'

it "returns a `Sink` instance", ->
sink = sinks.file
path: '/dev/null'
assert.instanceOf sink, sinks.Sink

it "attaches given formatter to sink", ->
sentinel = ->
sink = sinks.file
path: '/dev/null'
format: sentinel
assert.strictEqual sink.format, sentinel

it "sets level on sink", ->
sink = sinks.file
path: '/dev/null'
level: 20
assert.strictEqual sink.level, 20
2 changes: 1 addition & 1 deletion test/unit/test.hierarchy.coffee
Expand Up @@ -59,7 +59,7 @@ describe "Hierarchy", ->

suba.addSink sinka
suba.addSink sinkb
suba.addSink {}
suba.addSink new Sink

hier.resetSinks()

Expand Down
68 changes: 39 additions & 29 deletions test/unit/test.logger.coffee
@@ -1,9 +1,15 @@
sinon = require 'sinon'
{Writable} = require 'readable-stream'

describe "Logger", ->
{Logger, Record, Sink} = require '../../lib'
hier = {}

stubSink = ->
s = new Sink new Writable
s.write = sinon.stub()
s

it "accepts level as symbolic name", ->
log = new Logger hier, 'foo', 'error'
assert.equal log.getEffectiveLevel(), 40
Expand Down Expand Up @@ -96,8 +102,8 @@ describe "Logger", ->
describe "addSink", ->
it "wraps function in Sink instance", ->
log = new Logger hier, 'foo'
log.addSink (record) ->
something
log.addSink (record, encoding, callback) ->
callback()
assert.instanceOf log.sinks[0], Sink

it "appends sink instances as is", ->
Expand All @@ -110,34 +116,38 @@ describe "Logger", ->
describe "log", ->
it "calls sinks with record created from input", ->
log = new Logger hier, 'foo'
sink = sinon.stub()
sink = stubSink()
log.addSink sink
log.log 10, "foo"
assert.calledOnce sink
assert.calledWith sink, sinon.match.instanceOf Record
assert.calledOnce sink.write
assert.calledWith sink.write, sinon.match.instanceOf Record

it "does not call any sinks when level is below logger threshold", ->
log = new Logger hier, 'foo', 20
sink = sinon.stub()
sink = stubSink()
log.addSink sink
log.log 10, 'foo'
assert.equal sink.called, 0
assert.equal sink.write.called, 0

it "does not call sink when level is below sink threshold", ->
log = new Logger hier, 'foo', 20
sink = sinon.stub()
log.addSink new Sink sink, null, 30
log.addSink new Sink sink, null, 20
sinka = stubSink()
sinka.setLevel 30
sinkb = stubSink()
sinkb.setLevel 20
log.addSink sinka
log.addSink sinkb
log.log 20, 'foo'
assert.calledOnce sink
assert.notCalled sinka.write
assert.calledOnce sinkb.write

it "converts the symbolic names of log levels", ->
log = new Logger hier, 'foo'
sink = sinon.stub()
sink = stubSink()
log.addSink sink
log.log 'WARNING', 'foo'
assert.calledOnce sink
assert.equal sink.args[0][0].level, 30
assert.calledOnce sink.write
assert.equal sink.write.args[0][0].level, 30

it "sends record to proxy", ->
proxyHier =
Expand All @@ -164,45 +174,45 @@ describe "Logger", ->
it "calls sink further up the hierarchy", ->
log = new Logger hier, 'foo.bar', 20
log.parent = new Logger hier, 'foo', 20
psink = sinon.stub()
psink = stubSink()
log.parent.addSink psink
sink = sinon.stub()
sink = stubSink()
log.addSink sink

log.log 'WARNING', 'foo'

assert.calledOnce psink
assert.calledOnce sink
assert.equal sink.args[0][0].level, 30
assert.calledOnce psink.write
assert.calledOnce sink.write
assert.equal sink.write.args[0][0].level, 30

it "does not propagate records further when `propagate` is false", ->
log = new Logger hier, 'foo.bar', 20
log.parent = new Logger hier, 'foo', 20
log.propagate = false
psink = sinon.stub()
psink = stubSink()
log.parent.addSink psink
sink = sinon.stub()
sink = stubSink()
log.addSink sink

log.log 'WARNING', 'foo'

assert.calledOnce sink
assert.equal psink.callCount, 0, "call count of parent"
assert.calledOnce sink.write
assert.equal psink.write.callCount, 0, "call count of parent"

describe "debug", ->
it "create log message at debug level", ->
log = new Logger hier, 'foo'
sink = sinon.stub()
sink = stubSink()
log.addSink sink
log.debug 'foo'
assert.calledOnce sink
assert.equal sink.args[0][0].level, 10
assert.calledOnce sink.write
assert.equal sink.write.args[0][0].level, 10

describe "trace", ->
it "create log message at trace level", ->
log = new Logger hier, 'foo'
sink = sinon.stub()
sink = stubSink()
log.addSink sink
log.trace 'foo'
assert.calledOnce sink
assert.equal sink.args[0][0].level, 5
assert.calledOnce sink.write
assert.equal sink.write.args[0][0].level, 5

0 comments on commit 32bfd33

Please sign in to comment.