Skip to content

Commit df508fc

Browse files
committed
feat(injecta): logger support for easier debugging
1 parent 35259b5 commit df508fc

File tree

7 files changed

+223
-132
lines changed

7 files changed

+223
-132
lines changed

packages/injecta/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"scripts": {
2424
"typecheck": "tsc --noEmit",
2525
"build": "tsdown"
26+
},
27+
"dependencies": {
28+
"@guiiai/logg": "catalog:"
2629
}
2730
}

packages/injecta/src/global.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import type { DependencyMap, InvokeOption, ProvideOption } from '.'
1+
import type { DependencyMap, InvokeOption, Logger, ProvideOption } from '.'
22

33
import { createContainer, provide as indexProvide, start as indexStart, stop as indexStop } from '.'
44

55
const globalContainer = createContainer()
66

7+
export function setLogger(logger: Logger) {
8+
globalContainer.logger = logger
9+
}
10+
711
export function provide<D extends DependencyMap | undefined, T = any>(
812
name: string,
913
option: ProvideOption<T, D>,

packages/injecta/src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
import {
22
invoke as globalInvoke,
33
provide as globalProvide,
4+
setLogger as globalSetLogger,
45
start as globalStart,
56
stop as globalStop,
67
} from './global'
78

89
export type { Lifecycle } from './builtin'
910

11+
export type { Logger, LoggerOptions } from './logger'
12+
13+
export {
14+
createDefaultLogger,
15+
createLoggLogger,
16+
createNoopLogger,
17+
} from './logger'
18+
1019
export {
1120
createContainer,
1221
invoke,
1322
provide,
1423
start,
1524
stop,
1625
} from './scoped'
17-
1826
export type {
1927
BuildContext,
2028
Container,
@@ -32,4 +40,5 @@ export const injecta = {
3240
invoke: globalInvoke,
3341
start: globalStart,
3442
stop: globalStop,
43+
setLogger: globalSetLogger,
3544
}

packages/injecta/src/logger.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { useLogg } from '@guiiai/logg'
2+
3+
import { name } from '../package.json'
4+
5+
export interface Logger {
6+
provide: (name: string, dependencies: string[]) => void
7+
invoke: (dependencies: string[]) => void
8+
beforeRun: (name: string) => void
9+
run: (name: string, duration: number) => void
10+
hookOnStart: (name: string) => void
11+
hookOnStartComplete: (name: string, duration: number) => void
12+
hookOnStop: (name: string) => void
13+
hookOnStopComplete: (name: string, duration: number) => void
14+
running: () => void
15+
}
16+
17+
export interface LoggerOptions {
18+
enabled?: boolean
19+
logger?: Logger
20+
}
21+
22+
function formatDuration(ms: number): string {
23+
if (ms < 1) {
24+
return `${(ms * 1000).toFixed(3)}µs`
25+
}
26+
if (ms < 1000) {
27+
return `${ms.toFixed(3)}ms`
28+
}
29+
return `${(ms / 1000).toFixed(3)}s`
30+
}
31+
32+
export function createDefaultLogger(): Logger {
33+
return {
34+
provide: (name: string, dependencies: string[]) => {
35+
const depsStr = dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''
36+
// eslint-disable-next-line no-console
37+
console.log(`[${name}] PROVIDE ${name}${depsStr}`)
38+
},
39+
40+
invoke: (dependencies: string[]) => {
41+
const depsStr = dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''
42+
// eslint-disable-next-line no-console
43+
console.log(`[${name}] INVOKE${depsStr}`)
44+
},
45+
46+
beforeRun: (name: string) => {
47+
// eslint-disable-next-line no-console
48+
console.log(`[${name}] BEFORE RUN provide: ${name}()`)
49+
},
50+
51+
run: (name: string, duration: number) => {
52+
// eslint-disable-next-line no-console
53+
console.log(`[${name}] RUN provide: ${name}() in ${formatDuration(duration)}`)
54+
},
55+
56+
hookOnStart: (name: string) => {
57+
// eslint-disable-next-line no-console
58+
console.log(`[${name}] HOOK OnStart ${name}() executing`)
59+
},
60+
61+
hookOnStartComplete: (name: string, duration: number) => {
62+
// eslint-disable-next-line no-console
63+
console.log(`[${name}] HOOK OnStart ${name}() ran successfully in ${formatDuration(duration)}`)
64+
},
65+
66+
hookOnStop: (name: string) => {
67+
// eslint-disable-next-line no-console
68+
console.log(`[${name}] HOOK OnStop ${name}() executing`)
69+
},
70+
71+
hookOnStopComplete: (name: string, duration: number) => {
72+
// eslint-disable-next-line no-console
73+
console.log(`[${name}] HOOK OnStop ${name}() ran successfully in ${formatDuration(duration)}`)
74+
},
75+
76+
running: () => {
77+
// eslint-disable-next-line no-console
78+
console.log(`[${name}] RUNNING`)
79+
},
80+
}
81+
}
82+
83+
export function createLoggLogger(): Logger {
84+
const logg = useLogg(name).useGlobalConfig()
85+
86+
return {
87+
provide: (name: string, dependencies: string[]) => {
88+
const depsStr = dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''
89+
logg.log(`PROVIDE ${name}${depsStr}`)
90+
},
91+
92+
invoke: (dependencies: string[]) => {
93+
const depsStr = dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''
94+
logg.log(`INVOKE${depsStr}`)
95+
},
96+
97+
beforeRun: (name: string) => {
98+
logg.log(`BEFORE RUN provide: ${name}()`)
99+
},
100+
101+
run: (name: string, duration: number) => {
102+
logg.log(`RUN provide: ${name}() in ${formatDuration(duration)}`)
103+
},
104+
105+
hookOnStart: (name: string) => {
106+
logg.log(`HOOK OnStart ${name}() executing`)
107+
},
108+
109+
hookOnStartComplete: (name: string, duration: number) => {
110+
logg.log(`HOOK OnStart ${name}() ran successfully in ${formatDuration(duration)}`)
111+
},
112+
113+
hookOnStop: (name: string) => {
114+
logg.log(`HOOK OnStop ${name}() executing`)
115+
},
116+
117+
hookOnStopComplete: (name: string, duration: number) => {
118+
logg.log(`HOOK OnStop ${name}() ran successfully in ${formatDuration(duration)}`)
119+
},
120+
121+
running: () => {
122+
logg.log(`RUNNING`)
123+
},
124+
}
125+
}
126+
127+
export function createNoopLogger(): Logger {
128+
return {
129+
provide: () => {},
130+
invoke: () => {},
131+
beforeRun: () => {},
132+
run: () => {},
133+
hookOnStart: () => {},
134+
hookOnStartComplete: () => {},
135+
hookOnStop: () => {},
136+
hookOnStopComplete: () => {},
137+
running: () => {},
138+
}
139+
}

packages/injecta/src/scoped.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import type { LifecycleTriggerable } from './builtin'
2+
import type { Logger, LoggerOptions } from './logger'
3+
4+
import { createDefaultLogger, createNoopLogger } from './logger'
25

36
export type DependencyMap = Record<string, any>
47

@@ -8,6 +11,7 @@ export interface Container {
811
lifecycleHooks: Map<string, LifecycleTriggerable>
912
dependencyGraph: Map<string, string[]>
1013
invocations: InvokeOptionObject<any>[]
14+
logger: Logger
1115
}
1216

1317
export type BuildContext<D extends DependencyMap | undefined = undefined> = {
@@ -23,13 +27,18 @@ export type InvokeOptionObject<D extends DependencyMap | undefined = undefined>
2327
export type InvokeOptionFunc<D extends DependencyMap | undefined = undefined> = (dependencies: D) => void | Promise<void>
2428
export type InvokeOption<D extends DependencyMap | undefined = undefined> = InvokeOptionObject<D> | InvokeOptionFunc<D>
2529

26-
export function createContainer(): Container {
30+
export function createContainer(options?: LoggerOptions): Container {
31+
const logger = options?.enabled === false
32+
? createNoopLogger()
33+
: (options?.logger ?? createDefaultLogger())
34+
2735
return {
2836
providers: new Map(),
2937
instances: new Map(),
3038
lifecycleHooks: new Map(),
3139
dependencyGraph: new Map(),
3240
invocations: [],
41+
logger,
3342
}
3443
}
3544

@@ -46,16 +55,20 @@ export function provide<D extends DependencyMap | undefined, T = any>(
4655

4756
// Track dependencies for lifecycle ordering
4857
const dependencies = providerObject.dependsOn ? Object.values(providerObject.dependsOn) : []
58+
59+
container.logger.provide(name, dependencies)
4960
container.dependencyGraph.set(name, dependencies)
5061
}
5162

5263
export function invoke<D extends DependencyMap>(container: Container, option: InvokeOption<D>): void {
53-
if (typeof option === 'function') {
54-
container.invocations.push({ callback: option } as InvokeOptionObject<any>)
55-
}
56-
else {
57-
container.invocations.push(option as InvokeOptionObject<any>)
58-
}
64+
const invocationObject = typeof option === 'function'
65+
? { callback: option } as InvokeOptionObject<any>
66+
: option as InvokeOptionObject<any>
67+
68+
const dependencies = invocationObject.dependsOn ? Object.values(invocationObject.dependsOn) : []
69+
70+
container.logger.invoke(dependencies)
71+
container.invocations.push(invocationObject)
5972
}
6073

6174
async function resolveInstance<T>(container: Container, name: string): Promise<T> {
@@ -93,7 +106,14 @@ async function resolveInstance<T>(container: Container, name: string): Promise<T
93106
name,
94107
}
95108

109+
container.logger.beforeRun(name)
110+
111+
const startTime = performance.now()
96112
const instance = await provider.build(context)
113+
const duration = performance.now() - startTime
114+
115+
container.logger.run(name, duration)
116+
97117
container.instances.set(name, instance)
98118

99119
return instance as T
@@ -137,7 +157,13 @@ export async function startLifecycleHooks(container: Container): Promise<void> {
137157
for (const serviceName of sortedServices) {
138158
const lifecycle = container.lifecycleHooks.get(serviceName)
139159
if (lifecycle?.emitOnStart) {
160+
container.logger.hookOnStart(serviceName)
161+
162+
const startTime = performance.now()
140163
await lifecycle.emitOnStart()
164+
const duration = performance.now() - startTime
165+
166+
container.logger.hookOnStartComplete(serviceName, duration)
141167
}
142168
}
143169
}
@@ -149,7 +175,13 @@ export async function stopLifecycleHooks(container: Container): Promise<void> {
149175
for (const serviceName of sortedServices.reverse()) {
150176
const lifecycle = container.lifecycleHooks.get(serviceName)
151177
if (lifecycle?.emitOnStop) {
178+
container.logger.hookOnStop(serviceName)
179+
180+
const startTime = performance.now()
152181
await lifecycle.emitOnStop()
182+
const duration = performance.now() - startTime
183+
184+
container.logger.hookOnStopComplete(serviceName, duration)
153185
}
154186
}
155187
}
@@ -168,6 +200,8 @@ export async function start(container: Container): Promise<void> {
168200

169201
await invocation.callback(resolvedDependencies)
170202
}
203+
204+
container.logger.running()
171205
}
172206

173207
export async function stop(container: Container): Promise<void> {

0 commit comments

Comments
 (0)