Skip to content

Commit e89f662

Browse files
committed
feat: add injectable logger system
1 parent 892fcf8 commit e89f662

File tree

6 files changed

+239
-3
lines changed

6 files changed

+239
-3
lines changed

src/logger.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
export interface LogObject {
2+
[key: string]: unknown
3+
}
4+
5+
export interface ErrorObject extends LogObject {
6+
err?: Error
7+
}
8+
9+
export interface Logger {
10+
trace(msg: string): void
11+
trace(obj: LogObject, msg: string): void
12+
13+
debug(msg: string): void
14+
debug(obj: LogObject, msg: string): void
15+
16+
info(msg: string): void
17+
info(obj: LogObject, msg: string): void
18+
19+
warn(msg: string): void
20+
warn(obj: LogObject, msg: string): void
21+
22+
error(msg: string): void
23+
error(obj: ErrorObject, msg: string): void
24+
25+
child(obj: LogObject): Logger
26+
}
27+
28+
/**
29+
* A simple logger that writes to console.
30+
*/
31+
class ConsoleLogger implements Logger {
32+
#prefix: string
33+
34+
constructor(prefix: string = 'queue') {
35+
this.#prefix = prefix
36+
}
37+
38+
#format(level: string, msgOrObj: string | LogObject, msg?: string): [string, LogObject?] {
39+
const prefix = `[${this.#prefix}] ${level}:`
40+
41+
if (typeof msgOrObj === 'object') {
42+
return [`${prefix} ${msg}`, msgOrObj]
43+
}
44+
45+
return [`${prefix} ${msgOrObj}`]
46+
}
47+
48+
trace(msg: string): void
49+
trace(obj: LogObject, msg: string): void
50+
trace(msgOrObj: string | LogObject, msg?: string): void {
51+
const [message, obj] = this.#format('TRACE', msgOrObj, msg)
52+
53+
if (obj) {
54+
return console.log(message, obj)
55+
}
56+
57+
console.log(message)
58+
}
59+
60+
debug(msg: string): void
61+
debug(obj: LogObject, msg: string): void
62+
debug(msgOrObj: string | LogObject, msg?: string): void {
63+
const [message, obj] = this.#format('DEBUG', msgOrObj, msg)
64+
65+
if (obj) {
66+
return console.log(message, obj)
67+
}
68+
69+
console.log(message)
70+
}
71+
72+
info(msg: string): void
73+
info(obj: LogObject, msg: string): void
74+
info(msgOrObj: string | LogObject, msg?: string): void {
75+
const [message, obj] = this.#format('INFO', msgOrObj, msg)
76+
77+
if (obj) {
78+
return console.log(message, obj)
79+
}
80+
81+
console.log(message)
82+
}
83+
84+
warn(msg: string): void
85+
warn(obj: LogObject, msg: string): void
86+
warn(msgOrObj: string | LogObject, msg?: string): void {
87+
const [message, obj] = this.#format('WARN', msgOrObj, msg)
88+
89+
if (obj) {
90+
return console.warn(message, obj)
91+
}
92+
93+
console.warn(message)
94+
}
95+
96+
error(msg: string): void
97+
error(obj: ErrorObject, msg: string): void
98+
error(msgOrObj: string | ErrorObject, msg?: string): void {
99+
const [message, obj] = this.#format('ERROR', msgOrObj, msg)
100+
101+
if (obj) {
102+
return console.error(message, obj)
103+
}
104+
105+
console.error(message)
106+
}
107+
108+
child(obj: LogObject): Logger {
109+
const childPrefix = obj.pkg ? String(obj.pkg) : this.#prefix
110+
return new ConsoleLogger(childPrefix)
111+
}
112+
}
113+
114+
export const consoleLogger = new ConsoleLogger()

src/queue_manager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as errors from './exceptions.js'
22
import debug from './debug.js'
33
import { Locator } from './locator.js'
4+
import { consoleLogger, type Logger } from './logger.js'
45
import type { Adapter } from './contracts/adapter.js'
56
import type { AdapterFactory, QueueConfig, QueueManagerConfig, RetryConfig } from './types/main.js'
67

@@ -10,6 +11,7 @@ class QueueManagerSingleton {
1011
#adapterInstances: Map<string, Adapter> = new Map()
1112
#globalRetryConfig?: RetryConfig
1213
#queueConfigs: Map<string, QueueConfig> = new Map()
14+
#logger: Logger = consoleLogger
1315

1416
async init(config: QueueManagerConfig) {
1517
debug('initializing queue manager with config: %O', config)
@@ -21,6 +23,7 @@ class QueueManagerSingleton {
2123
this.#defaultAdapter = config.default
2224
this.#adapters = config.adapters
2325
this.#globalRetryConfig = config.retry
26+
this.#logger = config.logger ?? consoleLogger
2427

2528
if (config.queues) {
2629
for (const [queue, queueConfig] of Object.entries(config.queues)) {

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type {
1111
WorkerCycle,
1212
AdapterFactory,
1313
QueueManagerConfig,
14+
Logger,
1415
} from './main.js'
1516

1617
export type { Adapter, AcquiredJob } from '../contracts/adapter.js'

src/types/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { BackoffStrategy as BackoffStrategyClass } from '../strategies/backoff_strategy.js'
22
import type { Adapter } from '../contracts/adapter.js'
3+
import type { Logger } from '../logger.js'
34
import { Job } from '../job.js'
45

6+
export type { Logger }
7+
58
export type Duration = number | string
69

710
export interface JobData {
@@ -103,4 +106,5 @@ export interface QueueManagerConfig {
103106
queues?: Record<string, QueueConfig>
104107
worker?: WorkerConfig
105108
locations?: string[]
109+
logger?: Logger
106110
}

tests/_mocks/memory_logger.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { Logger, LogObject, ErrorObject } from '../../src/logger.js'
2+
3+
export interface LogEntry {
4+
level: 'trace' | 'debug' | 'info' | 'warn' | 'error'
5+
message: string
6+
obj?: LogObject
7+
}
8+
9+
export class MemoryLogger implements Logger {
10+
logs: LogEntry[] = []
11+
12+
trace(msg: string): void
13+
trace(obj: LogObject, msg: string): void
14+
trace(msgOrObj: string | LogObject, msg?: string): void {
15+
this.#log('trace', msgOrObj, msg)
16+
}
17+
18+
debug(msg: string): void
19+
debug(obj: LogObject, msg: string): void
20+
debug(msgOrObj: string | LogObject, msg?: string): void {
21+
this.#log('debug', msgOrObj, msg)
22+
}
23+
24+
info(msg: string): void
25+
info(obj: LogObject, msg: string): void
26+
info(msgOrObj: string | LogObject, msg?: string): void {
27+
this.#log('info', msgOrObj, msg)
28+
}
29+
30+
warn(msg: string): void
31+
warn(obj: LogObject, msg: string): void
32+
warn(msgOrObj: string | LogObject, msg?: string): void {
33+
this.#log('warn', msgOrObj, msg)
34+
}
35+
36+
error(msg: string): void
37+
error(obj: ErrorObject, msg: string): void
38+
error(msgOrObj: string | ErrorObject, msg?: string): void {
39+
this.#log('error', msgOrObj, msg)
40+
}
41+
42+
child(_obj: LogObject): Logger {
43+
return this
44+
}
45+
46+
clear(): void {
47+
this.logs = []
48+
}
49+
50+
#log(level: LogEntry['level'], msgOrObj: string | LogObject, msg?: string): void {
51+
if (typeof msgOrObj === 'object') {
52+
this.logs.push({ level, message: msg!, obj: msgOrObj })
53+
} else {
54+
this.logs.push({ level, message: msgOrObj })
55+
}
56+
}
57+
}

tests/queue_manager.spec.ts

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as errors from '../src/exceptions.js'
33
import { QueueManager } from '../src/queue_manager.js'
44
import { sync } from '../src/drivers/sync_adapter.js'
55
import { exponentialBackoff } from '../src/strategies/backoff_strategy.js'
6+
import { MemoryLogger } from './_mocks/memory_logger.js'
67

78
test.group('QueueManager', () => {
89
test('should validate adapter presence', async ({ assert }) => {
@@ -78,7 +79,7 @@ test.group('QueueManager', () => {
7879
await QueueManager.init({
7980
default: 'sync',
8081
adapters: { sync: sync() },
81-
locations: ['./jobs/**/*'],
82+
locations: ['./examples/jobs/**/*'],
8283
retry: { maxRetries: 5, backoff: exponentialBackoff() },
8384
})
8485

@@ -90,7 +91,7 @@ test.group('QueueManager', () => {
9091
await QueueManager.init({
9192
default: 'sync',
9293
adapters: { sync: sync() },
93-
locations: ['./jobs/**/*'],
94+
locations: ['./examples/jobs/**/*'],
9495
retry: { maxRetries: 5, backoff: exponentialBackoff() },
9596
queues: {
9697
email: { retry: { maxRetries: 3 } },
@@ -105,7 +106,7 @@ test.group('QueueManager', () => {
105106
await QueueManager.init({
106107
default: 'sync',
107108
adapters: { sync: sync() },
108-
locations: ['./jobs/**/*'],
109+
locations: ['./examples/jobs/**/*'],
109110
retry: { maxRetries: 5, backoff: exponentialBackoff() },
110111
queues: {
111112
email: { retry: { maxRetries: 3 } },
@@ -115,4 +116,60 @@ test.group('QueueManager', () => {
115116
let config = QueueManager.getMergedRetryConfig('email', { maxRetries: 2 })
116117
assert.equal(config.maxRetries, 2)
117118
})
119+
120+
test('should throw E_QUEUE_NOT_INITIALIZED when use() called before init()', async ({
121+
assert,
122+
}) => {
123+
assert.plan(2)
124+
125+
await QueueManager.destroy()
126+
127+
try {
128+
QueueManager.use()
129+
} catch (error) {
130+
assert.instanceOf(error, errors.E_QUEUE_NOT_INITIALIZED)
131+
assert.equal(
132+
error.message,
133+
'QueueManager is not initialized. Call QueueManager.init() before using it.'
134+
)
135+
}
136+
})
137+
138+
test('should throw E_ADAPTER_INIT_ERROR when adapter factory throws', async ({ assert }) => {
139+
assert.plan(2)
140+
141+
await QueueManager.init({
142+
default: 'broken',
143+
adapters: {
144+
broken: () => {
145+
throw new Error('Connection failed')
146+
},
147+
},
148+
})
149+
150+
try {
151+
QueueManager.use()
152+
} catch (error) {
153+
assert.instanceOf(error, errors.E_ADAPTER_INIT_ERROR)
154+
assert.equal(
155+
error.message,
156+
'Failed to initialize adapter "broken". Reason: Connection failed'
157+
)
158+
}
159+
})
160+
161+
test('should log warning when locations match no jobs', async ({ assert }) => {
162+
const logger = new MemoryLogger()
163+
164+
await QueueManager.init({
165+
default: 'sync',
166+
adapters: { sync: sync() },
167+
locations: ['./non-existent-path/**/*.ts'],
168+
logger,
169+
})
170+
171+
assert.equal(logger.logs.length, 1)
172+
assert.equal(logger.logs[0].level, 'warn')
173+
assert.include(logger.logs[0].message, 'No jobs found for locations')
174+
})
118175
})

0 commit comments

Comments
 (0)