@heojeongbo/log-palette 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heojeongbo/log-palette",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Browser logging library with domain-based colored prefixes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -14,8 +14,7 @@
14
14
  }
15
15
  },
16
16
  "files": [
17
- "dist",
18
- "src"
17
+ "dist"
19
18
  ],
20
19
  "scripts": {
21
20
  "build": "rolldown -c rolldown.config.ts && tsc --emitDeclarationOnly",
@@ -1,31 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { domainToColor } from '../color.js'
3
-
4
- describe('domainToColor', () => {
5
- it('returns a valid hsl color string', () => {
6
- const color = domainToColor('auth')
7
- expect(color).toMatch(/^hsl\(\d+,\s*75%,\s*60%\)$/)
8
- })
9
-
10
- it('returns the same color for the same domain', () => {
11
- expect(domainToColor('auth')).toBe(domainToColor('auth'))
12
- expect(domainToColor('api')).toBe(domainToColor('api'))
13
- expect(domainToColor('payments')).toBe(domainToColor('payments'))
14
- })
15
-
16
- it('returns different colors for different domains', () => {
17
- const colors = new Set(['auth', 'api', 'ui', 'payments', 'websocket'].map(domainToColor))
18
- expect(colors.size).toBeGreaterThan(1)
19
- })
20
-
21
- it('hue is within 0-359 range', () => {
22
- for (const domain of ['a', 'test', 'some-long-domain-name', '']) {
23
- const color = domainToColor(domain)
24
- const match = color.match(/^hsl\((\d+),/)
25
- expect(match).not.toBeNull()
26
- const hue = parseInt(match?.[1] ?? '', 10)
27
- expect(hue).toBeGreaterThanOrEqual(0)
28
- expect(hue).toBeLessThan(360)
29
- }
30
- })
31
- })
@@ -1,41 +0,0 @@
1
- import { afterEach, describe, expect, it } from 'vitest'
2
- import { configure, getConfig } from '../config.js'
3
-
4
- describe('configure / getConfig', () => {
5
- // Restore default config after each test
6
- afterEach(() => {
7
- configure({ innerWidth: 8, timestamp: true })
8
- })
9
-
10
- it('returns default config', () => {
11
- const config = getConfig()
12
- expect(config.innerWidth).toBe(8)
13
- expect(config.timestamp).toBe(true)
14
- })
15
-
16
- it('updates innerWidth', () => {
17
- configure({ innerWidth: 12 })
18
- expect(getConfig().innerWidth).toBe(12)
19
- })
20
-
21
- it('updates timestamp', () => {
22
- configure({ timestamp: false })
23
- expect(getConfig().timestamp).toBe(false)
24
- })
25
-
26
- it('partial update leaves other fields unchanged', () => {
27
- configure({ innerWidth: 10 })
28
- expect(getConfig().timestamp).toBe(true)
29
- })
30
-
31
- it('throws RangeError for innerWidth < 1', () => {
32
- expect(() => configure({ innerWidth: 0 })).toThrow(RangeError)
33
- expect(() => configure({ innerWidth: -1 })).toThrow(RangeError)
34
- })
35
-
36
- it('getConfig returns a snapshot (mutating it has no effect)', () => {
37
- const snap = getConfig() as { innerWidth: number }
38
- snap.innerWidth = 999
39
- expect(getConfig().innerWidth).toBe(8)
40
- })
41
- })
@@ -1,82 +0,0 @@
1
- import { afterEach, describe, expect, it } from 'vitest'
2
- import { configure } from '../config.js'
3
- import { formatPrefix, formatTimestamp, prefixStyle } from '../formatter.js'
4
-
5
- describe('formatTimestamp', () => {
6
- it('returns HH:mm:ss.SSS format', () => {
7
- const date = new Date('2024-01-01T05:35:41.039Z')
8
- // Use local time representation
9
- const ts = formatTimestamp(date)
10
- expect(ts).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/)
11
- })
12
-
13
- it('pads single-digit values with zeros', () => {
14
- const date = new Date(2024, 0, 1, 1, 2, 3, 4)
15
- const ts = formatTimestamp(date)
16
- expect(ts).toBe('01:02:03.004')
17
- })
18
- })
19
-
20
- describe('formatPrefix', () => {
21
- it('pads short domains with dots on the left', () => {
22
- expect(formatPrefix('auth')).toBe('[....auth]')
23
- expect(formatPrefix('api')).toBe('[.....api]')
24
- expect(formatPrefix('ui')).toBe('[......ui]')
25
- })
26
-
27
- it('uses no dots for exactly 8-char domains', () => {
28
- expect(formatPrefix('payments')).toBe('[payments]')
29
- })
30
-
31
- it('all prefixes have consistent bracket width (10 chars)', () => {
32
- const domains = ['a', 'ui', 'auth', 'api', 'payments']
33
- for (const domain of domains) {
34
- expect(formatPrefix(domain)).toHaveLength(10)
35
- }
36
- })
37
-
38
- it('truncates long domains to 7 chars + ellipsis', () => {
39
- const result = formatPrefix('websocket')
40
- expect(result).toBe('[websock\u2026]')
41
- expect(result).toHaveLength(10)
42
- })
43
-
44
- it('truncates very long domains consistently', () => {
45
- const result = formatPrefix('verylongdomainname')
46
- expect(result).toHaveLength(10)
47
- expect(result).toMatch(/^\[.{8}\]$/)
48
- expect(result).toContain('\u2026')
49
- })
50
- })
51
-
52
- describe('formatPrefix with custom innerWidth', () => {
53
- afterEach(() => configure({ innerWidth: 8 }))
54
-
55
- it('respects innerWidth: 4', () => {
56
- configure({ innerWidth: 4 })
57
- expect(formatPrefix('ui')).toBe('[..ui]')
58
- expect(formatPrefix('auth')).toBe('[auth]')
59
- expect(formatPrefix('login')).toBe('[log\u2026]')
60
- })
61
-
62
- it('all prefixes match innerWidth + 2', () => {
63
- configure({ innerWidth: 12 })
64
- const domains = ['a', 'auth', 'payments', 'verylongdomainname']
65
- for (const domain of domains) {
66
- expect(formatPrefix(domain)).toHaveLength(14)
67
- }
68
- })
69
- })
70
-
71
- describe('prefixStyle', () => {
72
- it('returns a CSS string with the given color', () => {
73
- const style = prefixStyle('#ff0000')
74
- expect(style).toContain('#ff0000')
75
- expect(style).toContain('font-weight: bold')
76
- })
77
-
78
- it('works with hsl colors', () => {
79
- const style = prefixStyle('hsl(200, 75%, 60%)')
80
- expect(style).toContain('hsl(200, 75%, 60%)')
81
- })
82
- })
@@ -1,204 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
- import { createLogger, LogPalette } from '../logger.js'
3
-
4
- describe('createLogger', () => {
5
- it('returns a Logger instance', () => {
6
- const log = createLogger('test')
7
- expect(log).toBeDefined()
8
- expect(log.domain).toBe('test')
9
- })
10
-
11
- it('assigns auto color when no color option provided', () => {
12
- const log = createLogger('test')
13
- expect(log.color).toMatch(/^hsl\(/)
14
- })
15
-
16
- it('uses provided color', () => {
17
- const log = createLogger('test', { color: '#ff0000' })
18
- expect(log.color).toBe('#ff0000')
19
- })
20
- })
21
-
22
- describe('LogPalette', () => {
23
- let consoleSpy: {
24
- log: ReturnType<typeof vi.spyOn>
25
- info: ReturnType<typeof vi.spyOn>
26
- warn: ReturnType<typeof vi.spyOn>
27
- error: ReturnType<typeof vi.spyOn>
28
- debug: ReturnType<typeof vi.spyOn>
29
- group: ReturnType<typeof vi.spyOn>
30
- groupCollapsed: ReturnType<typeof vi.spyOn>
31
- groupEnd: ReturnType<typeof vi.spyOn>
32
- time: ReturnType<typeof vi.spyOn>
33
- timeEnd: ReturnType<typeof vi.spyOn>
34
- }
35
-
36
- beforeEach(() => {
37
- consoleSpy = {
38
- log: vi.spyOn(console, 'log').mockImplementation(() => {}),
39
- info: vi.spyOn(console, 'info').mockImplementation(() => {}),
40
- warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
41
- error: vi.spyOn(console, 'error').mockImplementation(() => {}),
42
- debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
43
- group: vi.spyOn(console, 'group').mockImplementation(() => {}),
44
- groupCollapsed: vi.spyOn(console, 'groupCollapsed').mockImplementation(() => {}),
45
- groupEnd: vi.spyOn(console, 'groupEnd').mockImplementation(() => {}),
46
- time: vi.spyOn(console, 'time').mockImplementation(() => {}),
47
- timeEnd: vi.spyOn(console, 'timeEnd').mockImplementation(() => {}),
48
- }
49
- })
50
-
51
- afterEach(() => {
52
- vi.restoreAllMocks()
53
- })
54
-
55
- it('calls the correct console method for each level', () => {
56
- const log = new LogPalette('test')
57
- log.log('a')
58
- log.info('b')
59
- log.warn('c')
60
- log.error('d')
61
- log.debug('e')
62
-
63
- expect(consoleSpy.log).toHaveBeenCalledOnce()
64
- expect(consoleSpy.info).toHaveBeenCalledOnce()
65
- expect(consoleSpy.warn).toHaveBeenCalledOnce()
66
- expect(consoleSpy.error).toHaveBeenCalledOnce()
67
- expect(consoleSpy.debug).toHaveBeenCalledOnce()
68
- })
69
-
70
- it('includes the domain prefix in log output', () => {
71
- const log = new LogPalette('auth')
72
- log.info('hello')
73
-
74
- const call = consoleSpy.info.mock.calls[0]
75
- expect(call[0]).toContain('[....auth]')
76
- })
77
-
78
- it('passes additional args to console', () => {
79
- const log = new LogPalette('test')
80
- const obj = { key: 'value' }
81
- log.info('message', obj)
82
-
83
- const call = consoleSpy.info.mock.calls[0]
84
- expect(call).toContain(obj)
85
- })
86
-
87
- describe('level filtering', () => {
88
- it('suppresses levels below the configured minimum', () => {
89
- const log = new LogPalette('test', { level: 'warn' })
90
- log.debug('no')
91
- log.log('no')
92
- log.info('no')
93
- log.warn('yes')
94
- log.error('yes')
95
-
96
- expect(consoleSpy.debug).not.toHaveBeenCalled()
97
- expect(consoleSpy.log).not.toHaveBeenCalled()
98
- expect(consoleSpy.info).not.toHaveBeenCalled()
99
- expect(consoleSpy.warn).toHaveBeenCalledOnce()
100
- expect(consoleSpy.error).toHaveBeenCalledOnce()
101
- })
102
-
103
- it('setLevel changes the minimum level at runtime', () => {
104
- const log = new LogPalette('test')
105
- log.setLevel('error')
106
- log.info('suppressed')
107
- log.error('shown')
108
-
109
- expect(consoleSpy.info).not.toHaveBeenCalled()
110
- expect(consoleSpy.error).toHaveBeenCalledOnce()
111
- })
112
- })
113
-
114
- describe('enabled flag', () => {
115
- it('suppresses all output when enabled is false', () => {
116
- const log = new LogPalette('test', { enabled: false })
117
- log.debug('no')
118
- log.info('no')
119
- log.warn('no')
120
- log.error('no')
121
-
122
- expect(consoleSpy.debug).not.toHaveBeenCalled()
123
- expect(consoleSpy.info).not.toHaveBeenCalled()
124
- expect(consoleSpy.warn).not.toHaveBeenCalled()
125
- expect(consoleSpy.error).not.toHaveBeenCalled()
126
- })
127
-
128
- it('setEnabled toggles output at runtime', () => {
129
- const log = new LogPalette('test')
130
- log.setEnabled(false)
131
- log.info('suppressed')
132
- log.setEnabled(true)
133
- log.info('shown')
134
-
135
- expect(consoleSpy.info).toHaveBeenCalledOnce()
136
- })
137
- })
138
-
139
- describe('group()', () => {
140
- it('calls console.group with domain prefix in label', () => {
141
- const log = new LogPalette('auth')
142
- log.group('Login flow')
143
-
144
- expect(consoleSpy.group).toHaveBeenCalledOnce()
145
- const call = consoleSpy.group.mock.calls[0]
146
- expect(call[0]).toContain('[....auth]')
147
- expect(call[0]).toContain('Login flow')
148
- })
149
-
150
- it('does not call console.group when disabled', () => {
151
- const log = new LogPalette('auth', { enabled: false })
152
- log.group('silent')
153
- expect(consoleSpy.group).not.toHaveBeenCalled()
154
- })
155
- })
156
-
157
- describe('groupCollapsed()', () => {
158
- it('calls console.groupCollapsed with domain prefix', () => {
159
- const log = new LogPalette('api')
160
- log.groupCollapsed('Details')
161
-
162
- expect(consoleSpy.groupCollapsed).toHaveBeenCalledOnce()
163
- const call = consoleSpy.groupCollapsed.mock.calls[0]
164
- expect(call[0]).toContain('[.....api]')
165
- })
166
- })
167
-
168
- describe('groupEnd()', () => {
169
- it('calls console.groupEnd', () => {
170
- const log = new LogPalette('test')
171
- log.groupEnd()
172
- expect(consoleSpy.groupEnd).toHaveBeenCalledOnce()
173
- })
174
- })
175
-
176
- describe('time() / timeEnd()', () => {
177
- it('calls console.time with namespaced label', () => {
178
- const log = new LogPalette('auth')
179
- log.time('login')
180
-
181
- expect(consoleSpy.time).toHaveBeenCalledOnce()
182
- const label = consoleSpy.time.mock.calls[0][0]
183
- expect(label).toContain('[....auth]')
184
- expect(label).toContain('login')
185
- })
186
-
187
- it('calls console.timeEnd with the same namespaced label', () => {
188
- const log = new LogPalette('auth')
189
- log.time('login')
190
- log.timeEnd('login')
191
-
192
- expect(consoleSpy.timeEnd).toHaveBeenCalledOnce()
193
- const timeLabel = consoleSpy.time.mock.calls[0][0]
194
- const timeEndLabel = consoleSpy.timeEnd.mock.calls[0][0]
195
- expect(timeLabel).toBe(timeEndLabel)
196
- })
197
-
198
- it('does not call console.time when disabled', () => {
199
- const log = new LogPalette('test', { enabled: false })
200
- log.time('silent')
201
- expect(consoleSpy.time).not.toHaveBeenCalled()
202
- })
203
- })
204
- })
@@ -1,88 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest'
2
- import { LogPalette } from '../logger.js'
3
- import { disableAll, enableAll, getAllLoggers, getLogger, setGlobalLevel } from '../registry.js'
4
-
5
- // Reset registry between tests by clearing the internal map via module re-import isn't easy,
6
- // so we use unique domain names per test to avoid cross-test contamination.
7
-
8
- describe('getLogger', () => {
9
- it('returns a Logger instance', () => {
10
- const log = getLogger('reg-test-1')
11
- expect(log).toBeInstanceOf(LogPalette)
12
- expect(log.domain).toBe('reg-test-1')
13
- })
14
-
15
- it('returns the same instance for the same domain', () => {
16
- const a = getLogger('reg-singleton')
17
- const b = getLogger('reg-singleton')
18
- expect(a).toBe(b)
19
- })
20
-
21
- it('returns different instances for different domains', () => {
22
- const a = getLogger('reg-domain-a')
23
- const b = getLogger('reg-domain-b')
24
- expect(a).not.toBe(b)
25
- })
26
-
27
- it('applies options on first call', () => {
28
- const log = getLogger('reg-color', { color: '#ff0000' })
29
- expect(log.color).toBe('#ff0000')
30
- })
31
- })
32
-
33
- describe('getAllLoggers', () => {
34
- it('returns a map containing registered loggers', () => {
35
- getLogger('reg-all-1')
36
- getLogger('reg-all-2')
37
- const all = getAllLoggers()
38
- expect(all.has('reg-all-1')).toBe(true)
39
- expect(all.has('reg-all-2')).toBe(true)
40
- })
41
-
42
- it('is a Map instance', () => {
43
- const all = getAllLoggers()
44
- expect(all).toBeInstanceOf(Map)
45
- })
46
- })
47
-
48
- describe('setGlobalLevel', () => {
49
- afterEach(() => vi.restoreAllMocks())
50
-
51
- it('suppresses levels below the new global minimum', () => {
52
- const log = getLogger('reg-level-test') as LogPalette
53
- const spy = vi.spyOn(console, 'debug').mockImplementation(() => {})
54
- const spyInfo = vi.spyOn(console, 'info').mockImplementation(() => {})
55
-
56
- setGlobalLevel('warn')
57
- log.debug('no')
58
- log.info('no')
59
- expect(spy).not.toHaveBeenCalled()
60
- expect(spyInfo).not.toHaveBeenCalled()
61
-
62
- // Restore to debug so other tests are not affected
63
- setGlobalLevel('debug')
64
- })
65
- })
66
-
67
- describe('disableAll / enableAll', () => {
68
- afterEach(() => vi.restoreAllMocks())
69
-
70
- it('disableAll silences all loggers', () => {
71
- const log = getLogger('reg-disable-test')
72
- const spy = vi.spyOn(console, 'info').mockImplementation(() => {})
73
-
74
- disableAll()
75
- log.info('silent')
76
- expect(spy).not.toHaveBeenCalled()
77
- })
78
-
79
- it('enableAll re-activates all loggers', () => {
80
- const log = getLogger('reg-enable-test')
81
- const spy = vi.spyOn(console, 'info').mockImplementation(() => {})
82
-
83
- disableAll()
84
- enableAll()
85
- log.info('active')
86
- expect(spy).toHaveBeenCalledOnce()
87
- })
88
- })
package/src/color.ts DELETED
@@ -1,13 +0,0 @@
1
- function djb2(str: string): number {
2
- let hash = 5381
3
- for (let i = 0; i < str.length; i++) {
4
- hash = ((hash << 5) + hash) ^ str.charCodeAt(i)
5
- hash = hash >>> 0
6
- }
7
- return hash
8
- }
9
-
10
- export function domainToColor(domain: string): string {
11
- const hue = djb2(domain) % 360
12
- return `hsl(${hue}, 75%, 60%)`
13
- }
package/src/config.ts DELETED
@@ -1,58 +0,0 @@
1
- /**
2
- * Global format configuration for log-palette.
3
- * Change these settings once to affect all loggers.
4
- */
5
- export interface GlobalConfig {
6
- /**
7
- * Number of inner characters inside the prefix brackets.
8
- * Total prefix width = innerWidth + 2 (for the `[` and `]`).
9
- *
10
- * - Short domains are left-padded with dots to fill this width.
11
- * - Domains longer than this are truncated with `…`.
12
- *
13
- * @default 8 → prefix is always `[XXXXXXXX]` (10 chars total)
14
- */
15
- innerWidth: number
16
-
17
- /**
18
- * Whether to include a timestamp in each log line.
19
- * @default true
20
- */
21
- timestamp: boolean
22
- }
23
-
24
- const _config: GlobalConfig = {
25
- innerWidth: 8,
26
- timestamp: true,
27
- }
28
-
29
- /**
30
- * Read the current global configuration.
31
- * Returns a snapshot — mutating the returned object has no effect.
32
- */
33
- export function getConfig(): Readonly<GlobalConfig> {
34
- return { ..._config }
35
- }
36
-
37
- /**
38
- * Update one or more global format settings.
39
- * Changes apply immediately to all subsequent log calls across all loggers.
40
- *
41
- * @example
42
- * ```ts
43
- * // Wider prefix column (good when you have long domain names)
44
- * configure({ innerWidth: 12 })
45
- *
46
- * // Strip timestamps (cleaner in environments that add their own)
47
- * configure({ timestamp: false })
48
- * ```
49
- */
50
- export function configure(options: Partial<GlobalConfig>): void {
51
- if (options.innerWidth !== undefined) {
52
- if (options.innerWidth < 1) throw new RangeError('innerWidth must be >= 1')
53
- _config.innerWidth = options.innerWidth
54
- }
55
- if (options.timestamp !== undefined) {
56
- _config.timestamp = options.timestamp
57
- }
58
- }
package/src/formatter.ts DELETED
@@ -1,26 +0,0 @@
1
- import { getConfig } from './config.js'
2
-
3
- export function formatTimestamp(date: Date): string {
4
- const h = String(date.getHours()).padStart(2, '0')
5
- const m = String(date.getMinutes()).padStart(2, '0')
6
- const s = String(date.getSeconds()).padStart(2, '0')
7
- const ms = String(date.getMilliseconds()).padStart(3, '0')
8
- return `${h}:${m}:${s}.${ms}`
9
- }
10
-
11
- export function formatPrefix(domain: string): string {
12
- const { innerWidth } = getConfig()
13
- if (domain.length < innerWidth) {
14
- const dots = '.'.repeat(innerWidth - domain.length)
15
- return `[${dots}${domain}]`
16
- }
17
- if (domain.length === innerWidth) {
18
- return `[${domain}]`
19
- }
20
- // Truncate long domains: keep (innerWidth - 1) chars + ellipsis
21
- return `[${domain.slice(0, innerWidth - 1)}\u2026]`
22
- }
23
-
24
- export function prefixStyle(color: string): string {
25
- return `color: ${color}; font-weight: bold;`
26
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export type { GlobalConfig } from './config.js'
2
- export { configure, getConfig } from './config.js'
3
- export { createLogger, LogPalette } from './logger.js'
4
- export { disableAll, enableAll, getAllLoggers, getLogger, setGlobalLevel } from './registry.js'
5
- export type { Logger, LoggerOptions, LogLevel } from './types.js'
package/src/logger.ts DELETED
@@ -1,169 +0,0 @@
1
- import { domainToColor } from './color.js'
2
- import { getConfig } from './config.js'
3
- import { formatPrefix, formatTimestamp, prefixStyle } from './formatter.js'
4
- import type { Logger, LoggerOptions, LogLevel } from './types.js'
5
-
6
- const LEVEL_RANK: Record<LogLevel, number> = {
7
- debug: 0,
8
- log: 1,
9
- info: 2,
10
- warn: 3,
11
- error: 4,
12
- }
13
-
14
- /**
15
- * A logger instance tied to a specific domain.
16
- * Outputs colored prefix badges to the browser console.
17
- *
18
- * @example
19
- * ```ts
20
- * const log = new LogPalette('auth', { color: '#7c3aed', level: 'info' })
21
- * log.info('User signed in', { userId: 42 })
22
- * // → [....auth] 05:35:41.039 User signed in { userId: 42 }
23
- * ```
24
- */
25
- export class LogPalette implements Logger {
26
- readonly domain: string
27
- readonly color: string
28
- private _minLevel: number
29
- private _enabled: boolean
30
-
31
- /**
32
- * @param domain Service/feature name shown in the prefix badge (e.g. 'auth', 'api').
33
- * @param options Optional configuration for color, level filtering, and enabled state.
34
- */
35
- constructor(domain: string, options: LoggerOptions = {}) {
36
- this.domain = domain
37
- this.color = options.color ?? domainToColor(domain)
38
- this._minLevel = LEVEL_RANK[options.level ?? 'debug']
39
- this._enabled = options.enabled ?? true
40
- }
41
-
42
- /**
43
- * Dynamically change the minimum log level for this logger.
44
- * Useful for adjusting verbosity at runtime without recreating the logger.
45
- */
46
- setLevel(level: LogLevel): void {
47
- this._minLevel = LEVEL_RANK[level]
48
- }
49
-
50
- /**
51
- * Enable or disable this logger at runtime.
52
- * When disabled, all log calls are silently dropped.
53
- */
54
- setEnabled(enabled: boolean): void {
55
- this._enabled = enabled
56
- }
57
-
58
- private _log(level: LogLevel, args: unknown[]): void {
59
- if (!this._enabled) return
60
- if (LEVEL_RANK[level] < this._minLevel) return
61
-
62
- const prefix = formatPrefix(this.domain)
63
- const style = prefixStyle(this.color)
64
- const { timestamp } = getConfig()
65
- const time = timestamp ? ` ${formatTimestamp(new Date())}` : ''
66
-
67
- console[level](`%c${prefix}%c${time}`, style, '', ...args)
68
- }
69
-
70
- /** @inheritdoc */
71
- log(...args: unknown[]): void {
72
- this._log('log', args)
73
- }
74
- /** @inheritdoc */
75
- info(...args: unknown[]): void {
76
- this._log('info', args)
77
- }
78
- /** @inheritdoc */
79
- warn(...args: unknown[]): void {
80
- this._log('warn', args)
81
- }
82
- /** @inheritdoc */
83
- error(...args: unknown[]): void {
84
- this._log('error', args)
85
- }
86
- /** @inheritdoc */
87
- debug(...args: unknown[]): void {
88
- this._log('debug', args)
89
- }
90
-
91
- /**
92
- * Open a DevTools console group with the domain prefix applied to the label.
93
- * Always call `groupEnd()` to close it.
94
- *
95
- * @example
96
- * ```ts
97
- * apiLog.group('Fetch /users')
98
- * apiLog.info('response', data)
99
- * apiLog.groupEnd()
100
- * ```
101
- */
102
- group(label: string): void {
103
- if (!this._enabled) return
104
- const prefix = formatPrefix(this.domain)
105
- const style = prefixStyle(this.color)
106
- console.group(`%c${prefix}%c ${label}`, style, '')
107
- }
108
-
109
- /**
110
- * Open a collapsed DevTools console group with the domain prefix applied to the label.
111
- * Always call `groupEnd()` to close it.
112
- */
113
- groupCollapsed(label: string): void {
114
- if (!this._enabled) return
115
- const prefix = formatPrefix(this.domain)
116
- const style = prefixStyle(this.color)
117
- console.groupCollapsed(`%c${prefix}%c ${label}`, style, '')
118
- }
119
-
120
- /** Close the most recently opened group. */
121
- groupEnd(): void {
122
- console.groupEnd()
123
- }
124
-
125
- /**
126
- * Start a named performance timer, namespaced to this domain.
127
- * Use `timeEnd(label)` to stop it and log the elapsed time.
128
- *
129
- * @example
130
- * ```ts
131
- * authLog.time('login')
132
- * await performLogin()
133
- * authLog.timeEnd('login')
134
- * // DevTools: [....auth] login: 42.1ms
135
- * ```
136
- */
137
- time(label: string): void {
138
- if (!this._enabled) return
139
- console.time(`${formatPrefix(this.domain)} ${label}`)
140
- }
141
-
142
- /**
143
- * Stop the named timer started with `time(label)` and log the elapsed duration.
144
- * @param label Must match the label passed to `time()`.
145
- */
146
- timeEnd(label: string): void {
147
- if (!this._enabled) return
148
- console.timeEnd(`${formatPrefix(this.domain)} ${label}`)
149
- }
150
- }
151
-
152
- /**
153
- * Create a new logger for the given domain.
154
- * Each call creates a fresh instance; use `getLogger()` for singleton behavior.
155
- *
156
- * @param domain Service/feature name (e.g. 'auth', 'api', 'payments').
157
- * @param options Optional color, level, and enabled settings.
158
- *
159
- * @example
160
- * ```ts
161
- * const log = createLogger('auth')
162
- * log.info('App started')
163
- *
164
- * const payLog = createLogger('payments', { color: '#16a34a', level: 'warn' })
165
- * ```
166
- */
167
- export function createLogger(domain: string, options?: LoggerOptions): Logger {
168
- return new LogPalette(domain, options)
169
- }
package/src/registry.ts DELETED
@@ -1,93 +0,0 @@
1
- import { LogPalette } from './logger.js'
2
- import type { Logger, LoggerOptions, LogLevel } from './types.js'
3
-
4
- const _registry = new Map<string, LogPalette>()
5
-
6
- /**
7
- * Get or create a logger for the given domain.
8
- * Unlike `createLogger()`, this returns the same instance every time it is
9
- * called with the same domain — making it safe to call from multiple modules.
10
- *
11
- * Options are only applied on first call for a domain; subsequent calls with
12
- * the same domain return the existing instance regardless of options.
13
- *
14
- * @example
15
- * ```ts
16
- * // In module A
17
- * const log = getLogger('api')
18
- *
19
- * // In module B — same instance
20
- * const log = getLogger('api')
21
- * ```
22
- */
23
- export function getLogger(domain: string, options?: LoggerOptions): Logger {
24
- let logger = _registry.get(domain)
25
- if (!logger) {
26
- logger = new LogPalette(domain, options)
27
- _registry.set(domain, logger)
28
- }
29
- return logger
30
- }
31
-
32
- /**
33
- * Return a read-only snapshot of all registered loggers, keyed by domain.
34
- * Useful for debugging or building DevTools integrations.
35
- *
36
- * @example
37
- * ```ts
38
- * const loggers = getAllLoggers()
39
- * console.log([...loggers.keys()]) // ['api', 'auth', 'payments']
40
- * ```
41
- */
42
- export function getAllLoggers(): ReadonlyMap<string, Logger> {
43
- return _registry
44
- }
45
-
46
- /**
47
- * Set the minimum log level for every registered logger at once.
48
- * Loggers created after this call are not affected — they use their own
49
- * options or the default ('debug').
50
- *
51
- * Call this early in your app entry point to control verbosity globally:
52
- *
53
- * @example
54
- * ```ts
55
- * // Only show warnings and errors in production
56
- * if (import.meta.env.PROD) setGlobalLevel('warn')
57
- * ```
58
- */
59
- export function setGlobalLevel(level: LogLevel): void {
60
- for (const logger of _registry.values()) {
61
- logger.setLevel(level)
62
- }
63
- }
64
-
65
- /**
66
- * Enable all registered loggers.
67
- *
68
- * @example
69
- * ```ts
70
- * enableAll()
71
- * ```
72
- */
73
- export function enableAll(): void {
74
- for (const logger of _registry.values()) {
75
- logger.setEnabled(true)
76
- }
77
- }
78
-
79
- /**
80
- * Disable all registered loggers. All log calls will be silently dropped
81
- * until `enableAll()` or individual `setEnabled(true)` is called.
82
- *
83
- * @example
84
- * ```ts
85
- * // Silence everything in production builds
86
- * if (import.meta.env.PROD) disableAll()
87
- * ```
88
- */
89
- export function disableAll(): void {
90
- for (const logger of _registry.values()) {
91
- logger.setEnabled(false)
92
- }
93
- }
package/src/types.ts DELETED
@@ -1,80 +0,0 @@
1
- export type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug'
2
-
3
- export interface LoggerOptions {
4
- /**
5
- * CSS color string for this domain's prefix badge.
6
- * If omitted, a color is auto-derived from the domain name via djb2 hash.
7
- * Accepts any valid CSS color value: '#f06', 'hsl(200,80%,55%)', 'coral', etc.
8
- */
9
- color?: string
10
-
11
- /**
12
- * Minimum log level to output. Levels below this are silently dropped.
13
- * Order (lowest→highest): debug < log < info < warn < error
14
- * Defaults to 'debug' (all levels pass through).
15
- */
16
- level?: LogLevel
17
-
18
- /**
19
- * When false, suppress all output regardless of level.
20
- * Defaults to true.
21
- */
22
- enabled?: boolean
23
- }
24
-
25
- export interface Logger {
26
- /** Log at 'log' level. */
27
- log(...args: unknown[]): void
28
- /** Log at 'info' level. */
29
- info(...args: unknown[]): void
30
- /** Log at 'warn' level. */
31
- warn(...args: unknown[]): void
32
- /** Log at 'error' level. */
33
- error(...args: unknown[]): void
34
- /** Log at 'debug' level. */
35
- debug(...args: unknown[]): void
36
-
37
- /**
38
- * Open a collapsible DevTools group with the domain prefix.
39
- * Must be closed with `groupEnd()`.
40
- */
41
- group(label: string): void
42
-
43
- /**
44
- * Open a collapsed DevTools group with the domain prefix.
45
- * Must be closed with `groupEnd()`.
46
- */
47
- groupCollapsed(label: string): void
48
-
49
- /** Close the most recently opened group. */
50
- groupEnd(): void
51
-
52
- /**
53
- * Start a named timer prefixed with the domain.
54
- * Use `timeEnd(label)` to stop and print elapsed time.
55
- */
56
- time(label: string): void
57
-
58
- /**
59
- * Stop a named timer and log the elapsed time to the console.
60
- * @param label Must match the label passed to `time()`.
61
- */
62
- timeEnd(label: string): void
63
-
64
- /**
65
- * Dynamically change the minimum log level for this logger.
66
- * Useful for adjusting verbosity at runtime.
67
- */
68
- setLevel(level: LogLevel): void
69
-
70
- /**
71
- * Enable or disable this logger at runtime.
72
- * When disabled, all log calls are silently dropped.
73
- */
74
- setEnabled(enabled: boolean): void
75
-
76
- /** The domain name this logger was created with. */
77
- readonly domain: string
78
- /** The resolved CSS color string for this logger's prefix. */
79
- readonly color: string
80
- }