Skip to content

Commit c74bce0

Browse files
committed
made sure color only works in supported env and updated README
1 parent 9efe208 commit c74bce0

File tree

6 files changed

+247
-207
lines changed

6 files changed

+247
-207
lines changed

packages/logger-middleware/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ let router = createRouter({
7373

7474
### Colorized Output
7575

76-
You can enable colorized output by setting the `colors` option to `true`. This is useful for development environments to improve readability.
76+
You can enable colorized output by setting the `colors` option to `true`. This is useful for development environments to improve readability. Colorization is automatically disabled in environments that don't support it (e.g., non-TTY terminals or when the `NO_COLOR` environment variable is set).
7777

7878
```ts
7979
let router = createRouter({
@@ -88,7 +88,9 @@ let router = createRouter({
8888
When `colors` is enabled, the following tokens will be color-coded:
8989
- `%method`
9090
- `%status`
91+
- `%duration`
9192
- `%durationPretty`
93+
- `%contentLength`
9294
- `%contentLengthPretty`
9395

9496
### Custom Logger
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { describe, it } from 'node:test'
2+
import assert from 'node:assert'
3+
import { Colorizer } from './colorizer.ts'
4+
5+
const RESET = '\x1b[0m'
6+
const GREEN = '\x1b[32m'
7+
const CYAN = '\x1b[36m'
8+
const YELLOW = '\x1b[33m'
9+
const RED = '\x1b[31m'
10+
const MAGENTA = '\x1b[35m'
11+
12+
describe('Colorizer', () => {
13+
describe('with colors disabled', () => {
14+
let colorizer = new Colorizer(false)
15+
16+
it('status returns plain code', () => {
17+
assert.strictEqual(colorizer.status(200), '200')
18+
assert.strictEqual(colorizer.status(404), '404')
19+
assert.strictEqual(colorizer.status(500), '500')
20+
})
21+
22+
it('method returns plain method', () => {
23+
assert.strictEqual(colorizer.method('GET'), 'GET')
24+
assert.strictEqual(colorizer.method('POST'), 'POST')
25+
})
26+
27+
it('duration returns plain value', () => {
28+
assert.strictEqual(colorizer.duration(50, '50ms'), '50ms')
29+
assert.strictEqual(colorizer.duration(1500, '1.5s'), '1.5s')
30+
})
31+
32+
it('contentLength returns plain value', () => {
33+
assert.strictEqual(colorizer.contentLength(1024, '1KB'), '1KB')
34+
assert.strictEqual(colorizer.contentLength(undefined, 'N/A'), 'N/A')
35+
})
36+
})
37+
38+
describe('status method', () => {
39+
let colorizer = new Colorizer(true)
40+
41+
it('handles 2xx codes', () => {
42+
let result = colorizer.status(200)
43+
assert.ok(result === '200' || result === `${GREEN}200${RESET}`)
44+
})
45+
46+
it('handles 3xx codes', () => {
47+
let result = colorizer.status(302)
48+
assert.ok(result === '302' || result === `${CYAN}302${RESET}`)
49+
})
50+
51+
it('handles 4xx codes', () => {
52+
let result = colorizer.status(404)
53+
assert.ok(result === '404' || result === `${RED}404${RESET}`)
54+
})
55+
56+
it('handles 5xx codes', () => {
57+
let result = colorizer.status(500)
58+
assert.ok(result === '500' || result === `${MAGENTA}500${RESET}`)
59+
})
60+
61+
it('handles 1xx codes without color', () => {
62+
assert.strictEqual(colorizer.status(100), '100')
63+
})
64+
})
65+
66+
describe('method colorization', () => {
67+
let colorizer = new Colorizer(true)
68+
69+
it('handles GET', () => {
70+
let result = colorizer.method('GET')
71+
assert.ok(result === 'GET' || result === `${GREEN}GET${RESET}`)
72+
})
73+
74+
it('handles POST', () => {
75+
let result = colorizer.method('POST')
76+
assert.ok(result === 'POST' || result === `${CYAN}POST${RESET}`)
77+
})
78+
79+
it('handles PUT and PATCH', () => {
80+
let put = colorizer.method('PUT')
81+
let patch = colorizer.method('PATCH')
82+
assert.ok(put === 'PUT' || put === `${YELLOW}PUT${RESET}`)
83+
assert.ok(patch === 'PATCH' || patch === `${YELLOW}PATCH${RESET}`)
84+
})
85+
86+
it('handles DELETE', () => {
87+
let result = colorizer.method('DELETE')
88+
assert.ok(result === 'DELETE' || result === `${RED}DELETE${RESET}`)
89+
})
90+
91+
it('handles HEAD and OPTIONS', () => {
92+
let head = colorizer.method('HEAD')
93+
let options = colorizer.method('OPTIONS')
94+
assert.ok(head === 'HEAD' || head === `${MAGENTA}HEAD${RESET}`)
95+
assert.ok(options === 'OPTIONS' || options === `${MAGENTA}OPTIONS${RESET}`)
96+
})
97+
98+
it('handles unknown methods', () => {
99+
assert.strictEqual(colorizer.method('UNKNOWN'), 'UNKNOWN')
100+
})
101+
})
102+
103+
describe('duration colorization', () => {
104+
let colorizer = new Colorizer(true)
105+
106+
it('handles fast durations', () => {
107+
let result = colorizer.duration(50, '50ms')
108+
assert.ok(result === '50ms' || result === `${GREEN}50ms${RESET}`)
109+
})
110+
111+
it('handles medium durations', () => {
112+
let result = colorizer.duration(150, '150ms')
113+
assert.ok(result === '150ms' || result === `${YELLOW}150ms${RESET}`)
114+
})
115+
116+
it('handles slow durations', () => {
117+
let result = colorizer.duration(600, '600ms')
118+
assert.ok(result === '600ms' || result === `${MAGENTA}600ms${RESET}`)
119+
})
120+
121+
it('handles very slow durations', () => {
122+
let result = colorizer.duration(1500, '1.5s')
123+
assert.ok(result === '1.5s' || result === `${RED}1.5s${RESET}`)
124+
})
125+
})
126+
127+
describe('contentLength colorization', () => {
128+
let colorizer = new Colorizer(true)
129+
130+
it('handles small sizes', () => {
131+
assert.strictEqual(colorizer.contentLength(500, '500B'), '500B')
132+
})
133+
134+
it('handles KB sizes', () => {
135+
let result = colorizer.contentLength(2000, '2KB')
136+
assert.ok(result === '2KB' || result === `${CYAN}2KB${RESET}`)
137+
})
138+
139+
it('handles 100KB+ sizes', () => {
140+
let result = colorizer.contentLength(150000, '150KB')
141+
assert.ok(result === '150KB' || result === `${YELLOW}150KB${RESET}`)
142+
})
143+
144+
it('handles MB+ sizes', () => {
145+
let result = colorizer.contentLength(2000000, '2MB')
146+
assert.ok(result === '2MB' || result === `${RED}2MB${RESET}`)
147+
})
148+
149+
it('handles undefined', () => {
150+
assert.strictEqual(colorizer.contentLength(undefined, 'N/A'), 'N/A')
151+
})
152+
})
153+
})
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const RESET = '\x1b[0m'
2+
const GREEN = '\x1b[32m'
3+
const CYAN = '\x1b[36m'
4+
const YELLOW = '\x1b[33m'
5+
const RED = '\x1b[31m'
6+
const MAGENTA = '\x1b[35m'
7+
8+
export class Colorizer {
9+
readonly #enabled?: boolean
10+
11+
constructor(colors?: boolean) {
12+
this.#enabled = colors
13+
}
14+
15+
#colorize(text: string, color: string): string {
16+
return this.#enabled ? `${color}${text}${RESET}` : text
17+
}
18+
19+
status(code: number): string {
20+
let value = String(code)
21+
if (!this.#enabled) return value
22+
if (code >= 500) return this.#colorize(value, MAGENTA)
23+
if (code >= 400) return this.#colorize(value, RED)
24+
if (code >= 300) return this.#colorize(value, CYAN)
25+
if (code >= 200) return this.#colorize(value, GREEN)
26+
return value
27+
}
28+
29+
method(method: string): string {
30+
if (!this.#enabled) return method
31+
switch (method.toUpperCase()) {
32+
case 'GET':
33+
return this.#colorize(method, GREEN)
34+
case 'POST':
35+
return this.#colorize(method, CYAN)
36+
case 'PUT':
37+
case 'PATCH':
38+
return this.#colorize(method, YELLOW)
39+
case 'DELETE':
40+
return this.#colorize(method, RED)
41+
case 'HEAD':
42+
case 'OPTIONS':
43+
return this.#colorize(method, MAGENTA)
44+
default:
45+
return method
46+
}
47+
}
48+
49+
duration(ms: number, prettyValue: string): string {
50+
if (!this.#enabled) return prettyValue
51+
if (ms >= 1000) return this.#colorize(prettyValue, RED)
52+
if (ms >= 500) return this.#colorize(prettyValue, MAGENTA)
53+
if (ms >= 100) return this.#colorize(prettyValue, YELLOW)
54+
return this.#colorize(prettyValue, GREEN)
55+
}
56+
57+
contentLength(bytes: number | undefined, prettyValue: string): string {
58+
let ONE_MB = 1024 * 1024
59+
let ONE_HUNDRED_KB = 100 * 1024
60+
let ONE_KB = 1024
61+
62+
if (!this.#enabled || bytes === undefined) return prettyValue
63+
if (bytes >= ONE_MB) return this.#colorize(prettyValue, RED)
64+
if (bytes >= ONE_HUNDRED_KB) return this.#colorize(prettyValue, YELLOW)
65+
if (bytes >= ONE_KB) return this.#colorize(prettyValue, CYAN)
66+
return prettyValue
67+
}
68+
}

packages/logger-middleware/src/lib/colors.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)