Skip to content

Commit 0338399

Browse files
authored
feat: NO_COLOR env var removes color in dev format (#377)
1 parent 51007f9 commit 0338399

5 files changed

Lines changed: 223 additions & 1 deletion

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ for information codes.
168168
GET /dev 200 0.224 ms - 2
169169
```
170170

171+
Coloring is disabled when the [`NO_COLOR`](https://no-color.org) environment
172+
variable is set to any non-empty value, in which case the output above is
173+
written without any escape sequences. The variable is read once, when `morgan`
174+
is first required.
175+
171176
##### short
172177

173178
Shorter than default, also including response time.

index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var CLF_MONTH = [
4747

4848
var DEFAULT_BUFFER_DURATION = 1000
4949

50+
var NO_COLOR = Boolean(process.env.NO_COLOR)
51+
5052
/**
5153
* Escape control characters and backslashes so a value is safe for
5254
* line-oriented logs.
@@ -235,6 +237,12 @@ morgan.format('dev', function developmentFormatLine (tokens, req, res) {
235237
return fn(tokens, req, res)
236238
})
237239

240+
// NO_COLOR (https://no-color.org): when set and not an empty string, the dev
241+
// format is replaced at load time with a variant free of escape sequences
242+
if (NO_COLOR) {
243+
morgan.format('dev', compile(':method :url :status :response-time ms - :res[content-length]'))
244+
}
245+
238246
/**
239247
* request url
240248
*/

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
},
4949
"scripts": {
5050
"lint": "eslint --plugin markdown --ext js,md .",
51-
"test": "mocha --check-leaks --reporter spec",
51+
"test": "mocha --check-leaks --reporter spec test/morgan.js && mocha --check-leaks --reporter spec test/noColor.js",
5252
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
5353
"test-cov": "nyc --reporter=html --reporter=text npm test"
5454
}

test/morgan.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11

22
process.env.NO_DEPRECATION = 'morgan'
3+
// empty, not deleted: per https://no-color.org an empty NO_COLOR must not
4+
// disable color, so the dev tests below guard that. Set: test/noColor.js
5+
process.env.NO_COLOR = ''
36

47
var assert = require('assert')
58
var fs = require('fs')

test/noColor.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
2+
process.env.NO_DEPRECATION = 'morgan'
3+
// read once at require time, hence a separate file from test/morgan.js.
4+
// '0' is deliberate: any non-empty value disables color, whatever it is
5+
process.env.NO_COLOR = '0'
6+
7+
var assert = require('assert')
8+
var http = require('http')
9+
var morgan = require('..')
10+
var request = require('supertest')
11+
var split = require('split')
12+
13+
describe('morgan()', function () {
14+
describe('formats', function () {
15+
describe('dev', function () {
16+
it('should not color 1xx', function (done) {
17+
var cb = after(2, function (err, res, line) {
18+
if (err) return done(err)
19+
assertPlainDevLine(line, 102)
20+
done()
21+
})
22+
23+
var stream = createLineStream(function onLine (line) {
24+
cb(null, null, line)
25+
})
26+
27+
var server = createServer('dev', { stream: stream }, function (req, res, next) {
28+
res.statusCode = 102
29+
next()
30+
})
31+
32+
request(server)
33+
.get('/')
34+
.expect(102, function (err, res) {
35+
if (err && err.code === 'ECONNRESET') {
36+
// finishing response with 1xx is invalid http
37+
// but node.js server lets the server do this, so
38+
// morgan needs to test in this condition even if
39+
// the http client doesn't like it
40+
err = null
41+
}
42+
cb(err, res)
43+
})
44+
})
45+
46+
it('should not color 2xx', function (done) {
47+
var cb = after(2, function (err, res, line) {
48+
if (err) return done(err)
49+
assertPlainDevLine(line, 200)
50+
done()
51+
})
52+
53+
var stream = createLineStream(function onLine (line) {
54+
cb(null, null, line)
55+
})
56+
57+
var server = createServer('dev', { stream: stream }, function (req, res, next) {
58+
res.statusCode = 200
59+
next()
60+
})
61+
62+
request(server)
63+
.get('/')
64+
.expect(200, cb)
65+
})
66+
67+
it('should not color 3xx', function (done) {
68+
var cb = after(2, function (err, res, line) {
69+
if (err) return done(err)
70+
assertPlainDevLine(line, 300)
71+
done()
72+
})
73+
74+
var stream = createLineStream(function onLine (line) {
75+
cb(null, null, line)
76+
})
77+
78+
var server = createServer('dev', { stream: stream }, function (req, res, next) {
79+
res.statusCode = 300
80+
next()
81+
})
82+
83+
request(server)
84+
.get('/')
85+
.expect(300, cb)
86+
})
87+
88+
it('should not color 4xx', function (done) {
89+
var cb = after(2, function (err, res, line) {
90+
if (err) return done(err)
91+
assertPlainDevLine(line, 400)
92+
done()
93+
})
94+
95+
var stream = createLineStream(function onLine (line) {
96+
cb(null, null, line)
97+
})
98+
99+
var server = createServer('dev', { stream: stream }, function (req, res, next) {
100+
res.statusCode = 400
101+
next()
102+
})
103+
104+
request(server)
105+
.get('/')
106+
.expect(400, cb)
107+
})
108+
109+
it('should not color 5xx', function (done) {
110+
var cb = after(2, function (err, res, line) {
111+
if (err) return done(err)
112+
assertPlainDevLine(line, 500)
113+
done()
114+
})
115+
116+
var stream = createLineStream(function onLine (line) {
117+
cb(null, null, line)
118+
})
119+
120+
var server = createServer('dev', { stream: stream }, function (req, res, next) {
121+
res.statusCode = 500
122+
next()
123+
})
124+
125+
request(server)
126+
.get('/')
127+
.expect(500, cb)
128+
})
129+
130+
it('should match the documented dev format', function (done) {
131+
var cb = after(2, function (err, res, line) {
132+
if (err) return done(err)
133+
assert.ok(/^GET \/ 200 \d+\.\d{3} ms - -$/.test(line),
134+
'unexpected line ' + JSON.stringify(line))
135+
done()
136+
})
137+
138+
var stream = createLineStream(function onLine (line) {
139+
cb(null, null, line)
140+
})
141+
142+
request(createServer('dev', { stream: stream }))
143+
.get('/')
144+
.expect(200, cb)
145+
})
146+
})
147+
})
148+
})
149+
150+
function after (count, callback) {
151+
var args = new Array(3)
152+
var i = 0
153+
154+
return function (err, arg1, arg2) {
155+
assert.ok(i++ < count, 'callback called ' + count + ' times')
156+
157+
args[0] = args[0] || err
158+
args[1] = args[1] || arg1
159+
args[2] = args[2] || arg2
160+
161+
if (count === i) {
162+
callback.apply(null, args)
163+
}
164+
}
165+
}
166+
167+
function assertPlainDevLine (line, status) {
168+
assert.strictEqual(line.indexOf('\x1b'), -1, 'expected no escapes in ' + JSON.stringify(line))
169+
assert.ok(
170+
new RegExp('^GET / ' + status + ' \\d+\\.\\d{3} ms - (\\d+|-)$').test(line),
171+
'unexpected line ' + JSON.stringify(line)
172+
)
173+
}
174+
175+
function createLineStream (callback) {
176+
return split().on('data', callback)
177+
}
178+
179+
function createServer (format, opts, fn, fn1) {
180+
var logger = morgan(format, opts)
181+
var middle = fn || noopMiddleware
182+
183+
return http.createServer().on('request', function onRequest (req, res) {
184+
// prior alterations
185+
if (fn1) {
186+
fn1(req, res)
187+
}
188+
189+
logger(req, res, function onNext (err) {
190+
// allow req, res alterations
191+
middle(req, res, function onDone () {
192+
if (err) {
193+
res.statusCode = 500
194+
res.end(err.message)
195+
}
196+
197+
res.setHeader('X-Sent', 'true')
198+
res.end((req.connection && req.connection.remoteAddress) || '-')
199+
})
200+
})
201+
})
202+
}
203+
204+
function noopMiddleware (req, res, next) {
205+
next()
206+
}

0 commit comments

Comments
 (0)