@mks2508/better-logger 2.1.0 → 3.0.0
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/.claude/settings.local.json +6 -1
- package/CHANGELOG.json +93 -1
- package/CHANGELOG.md +127 -0
- package/CLAUDE.md +14 -1
- package/README.md +111 -20
- package/dist/Logger.d.ts +205 -58
- package/dist/Logger.d.ts.map +1 -1
- package/dist/ScopedLogger.d.ts +23 -276
- package/dist/ScopedLogger.d.ts.map +1 -1
- package/dist/chunks/Logger-BCfx_yCK.js +2 -0
- package/dist/chunks/Logger-BCfx_yCK.js.map +1 -0
- package/dist/chunks/{Logger-B7L-ujY4.js → Logger-C126NTrD.js} +862 -96
- package/dist/chunks/Logger-C126NTrD.js.map +1 -0
- package/dist/exports-module.d.ts +1 -1
- package/dist/exports-module.d.ts.map +1 -1
- package/dist/exports.cjs +1 -1
- package/dist/exports.js +1 -1
- package/dist/hooks/HookManager.d.ts +21 -0
- package/dist/hooks/HookManager.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +23 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +240 -9
- package/dist/index.js.map +1 -1
- package/dist/serializers/SerializerRegistry.d.ts +16 -0
- package/dist/serializers/SerializerRegistry.d.ts.map +1 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/styling/StyleCache.d.ts +42 -0
- package/dist/styling/StyleCache.d.ts.map +1 -0
- package/dist/styling-module.d.ts +1 -1
- package/dist/styling-module.d.ts.map +1 -1
- package/dist/styling.cjs +1 -1
- package/dist/styling.js +2 -2
- package/dist/transports/ConsoleTransport.d.ts +9 -0
- package/dist/transports/ConsoleTransport.d.ts.map +1 -0
- package/dist/transports/FileTransport.d.ts +15 -0
- package/dist/transports/FileTransport.d.ts.map +1 -0
- package/dist/transports/HttpTransport.d.ts +16 -0
- package/dist/transports/HttpTransport.d.ts.map +1 -0
- package/dist/transports/TransportManager.d.ts +14 -0
- package/dist/transports/TransportManager.d.ts.map +1 -0
- package/dist/transports/index.d.ts +5 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/types/core.d.ts +38 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +36 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/serializers.d.ts +25 -0
- package/dist/types/serializers.d.ts.map +1 -0
- package/dist/types/transports.d.ts +47 -0
- package/dist/types/transports.d.ts.map +1 -0
- package/docs/API.md +227 -0
- package/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/Logger.ts +391 -102
- package/src/ScopedLogger.ts +78 -318
- package/src/hooks/HookManager.ts +177 -0
- package/src/hooks/index.ts +1 -0
- package/src/index.ts +77 -2
- package/src/serializers/SerializerRegistry.ts +173 -0
- package/src/serializers/index.ts +1 -0
- package/src/styling/StyleCache.ts +131 -0
- package/src/transports/ConsoleTransport.ts +28 -0
- package/src/transports/FileTransport.ts +53 -0
- package/src/transports/HttpTransport.ts +56 -0
- package/src/transports/TransportManager.ts +130 -0
- package/src/transports/index.ts +4 -0
- package/src/types/core.ts +46 -0
- package/src/types/hooks.ts +45 -0
- package/src/types/index.ts +39 -1
- package/src/types/serializers.ts +30 -0
- package/src/types/transports.ts +52 -0
- package/dist/chunks/Logger-B7L-ujY4.js.map +0 -1
- package/dist/chunks/Logger-DzU_c5sX.js +0 -2
- package/dist/chunks/Logger-DzU_c5sX.js.map +0 -1
- package/dist/chunks/ScopedLogger-BY3-E8Ov.js +0 -2
- package/dist/chunks/ScopedLogger-BY3-E8Ov.js.map +0 -1
- package/dist/chunks/ScopedLogger-D-RbiFZn.js +0 -361
- package/dist/chunks/ScopedLogger-D-RbiFZn.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -98,7 +98,12 @@ export const groupEnd = () => getLogger().groupEnd();
|
|
|
98
98
|
export const time = (label: string) => getLogger().time(label);
|
|
99
99
|
export const timeEnd = (label: string) => getLogger().timeEnd(label);
|
|
100
100
|
export const setGlobalPrefix = (prefix: string) => getLogger().setGlobalPrefix(prefix);
|
|
101
|
-
export const scope = (
|
|
101
|
+
export const scope = (name: string) => getLogger().scope(name);
|
|
102
|
+
export const component = (name: string) => getLogger().component(name);
|
|
103
|
+
export const api = (name: string) => getLogger().api(name);
|
|
104
|
+
export const badges = (badgeList: string[]) => getLogger().badges(badgeList);
|
|
105
|
+
export const badge = (badgeName: string) => getLogger().badge(badgeName);
|
|
106
|
+
export const clearBadges = () => getLogger().clearBadges();
|
|
102
107
|
export const setVerbosity = (level: Verbosity) => getLogger().setVerbosity(level);
|
|
103
108
|
export const addHandler = (handler: ILogHandler) => getLogger().addHandler(handler);
|
|
104
109
|
export const setTheme = (theme: ThemeVariant) => getLogger().setTheme(theme);
|
|
@@ -122,7 +127,26 @@ export type {
|
|
|
122
127
|
LogMetadata,
|
|
123
128
|
StackInfo,
|
|
124
129
|
TimerEntry,
|
|
125
|
-
|
|
130
|
+
TimerResult,
|
|
131
|
+
OutputFormat,
|
|
132
|
+
IScopedLogger,
|
|
133
|
+
IAPILogger,
|
|
134
|
+
IComponentLogger,
|
|
135
|
+
Bindings,
|
|
136
|
+
SerializerFn,
|
|
137
|
+
SerializerContext,
|
|
138
|
+
SerializerConfig,
|
|
139
|
+
ISerializerRegistry,
|
|
140
|
+
HookLogEntry,
|
|
141
|
+
HookEvent,
|
|
142
|
+
HookCallback,
|
|
143
|
+
MiddlewareFn,
|
|
144
|
+
IHookManager,
|
|
145
|
+
TransportRecord,
|
|
146
|
+
TransportOptions,
|
|
147
|
+
TransportTarget,
|
|
148
|
+
ITransport,
|
|
149
|
+
StyleCacheConfig
|
|
126
150
|
} from './types/index.js';
|
|
127
151
|
|
|
128
152
|
// Styling utilities
|
|
@@ -141,6 +165,57 @@ export {
|
|
|
141
165
|
AnalyticsLogHandler
|
|
142
166
|
} from './handlers/index.js';
|
|
143
167
|
|
|
168
|
+
// Serializers
|
|
169
|
+
export { SerializerRegistry } from './serializers/index.js';
|
|
170
|
+
|
|
171
|
+
// Hooks & Middleware
|
|
172
|
+
export { HookManager } from './hooks/index.js';
|
|
173
|
+
|
|
174
|
+
// Transports
|
|
175
|
+
export {
|
|
176
|
+
TransportManager,
|
|
177
|
+
ConsoleTransport,
|
|
178
|
+
FileTransport,
|
|
179
|
+
HttpTransport
|
|
180
|
+
} from './transports/index.js';
|
|
181
|
+
|
|
182
|
+
// Style Cache
|
|
183
|
+
export { StyleCache, getStyleCache } from './styling/StyleCache.js';
|
|
184
|
+
|
|
185
|
+
// Enterprise feature function exports
|
|
186
|
+
import type { SerializerFn, HookEvent, HookCallback, MiddlewareFn, TransportTarget } from './types/index.js';
|
|
187
|
+
export const addSerializer = <T>(
|
|
188
|
+
type: new (...args: any[]) => T,
|
|
189
|
+
serializer: SerializerFn<T>,
|
|
190
|
+
priority?: number
|
|
191
|
+
) => getLogger().addSerializer(type, serializer, priority);
|
|
192
|
+
export const removeSerializer = <T>(type: new (...args: any[]) => T) =>
|
|
193
|
+
getLogger().removeSerializer(type);
|
|
194
|
+
export const on = (event: HookEvent, callback: HookCallback, priority?: number) =>
|
|
195
|
+
getLogger().on(event, callback, priority);
|
|
196
|
+
export const once = (event: HookEvent, callback: HookCallback, priority?: number) =>
|
|
197
|
+
getLogger().once(event, callback, priority);
|
|
198
|
+
export const off = (event: HookEvent, callback: HookCallback) =>
|
|
199
|
+
getLogger().off(event, callback);
|
|
200
|
+
export const use = (middleware: MiddlewareFn, priority?: number) =>
|
|
201
|
+
getLogger().use(middleware, priority);
|
|
202
|
+
export const addTransport = (target: TransportTarget) =>
|
|
203
|
+
getLogger().addTransport(target);
|
|
204
|
+
export const removeTransport = (id: string) =>
|
|
205
|
+
getLogger().removeTransport(id);
|
|
206
|
+
export const flushTransports = () =>
|
|
207
|
+
getLogger().flushTransports();
|
|
208
|
+
export const closeTransports = () =>
|
|
209
|
+
getLogger().closeTransports();
|
|
210
|
+
|
|
211
|
+
// Scoped loggers
|
|
212
|
+
export {
|
|
213
|
+
ScopedLogger,
|
|
214
|
+
APILogger,
|
|
215
|
+
ComponentLogger,
|
|
216
|
+
ContextLogger
|
|
217
|
+
} from './ScopedLogger.js';
|
|
218
|
+
|
|
144
219
|
// Constants
|
|
145
220
|
export { DEFAULT_CONFIG } from './constants.js';
|
|
146
221
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SerializerFn,
|
|
3
|
+
SerializerContext,
|
|
4
|
+
SerializerEntry,
|
|
5
|
+
SerializerConfig,
|
|
6
|
+
ISerializerRegistry
|
|
7
|
+
} from '../types/index.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CONFIG: Required<SerializerConfig> = {
|
|
10
|
+
maxDepth: 5,
|
|
11
|
+
circular: 'placeholder',
|
|
12
|
+
preserveUndefined: false
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class SerializerRegistry implements ISerializerRegistry {
|
|
16
|
+
private serializers: Map<Function, SerializerEntry> = new Map();
|
|
17
|
+
private config: Required<SerializerConfig>;
|
|
18
|
+
|
|
19
|
+
constructor(config: SerializerConfig = {}) {
|
|
20
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
21
|
+
this.registerDefaults();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private registerDefaults(): void {
|
|
25
|
+
this.add(Error, (err: Error) => ({
|
|
26
|
+
name: err.name,
|
|
27
|
+
message: err.message,
|
|
28
|
+
stack: err.stack?.split('\n').slice(0, 10),
|
|
29
|
+
...(err.cause ? { cause: this.serialize(err.cause) } : {})
|
|
30
|
+
}), 100);
|
|
31
|
+
|
|
32
|
+
this.add(Date, (date: Date) => ({
|
|
33
|
+
iso: date.toISOString(),
|
|
34
|
+
timestamp: date.getTime()
|
|
35
|
+
}), 90);
|
|
36
|
+
|
|
37
|
+
this.add(RegExp, (regex: RegExp) => ({
|
|
38
|
+
pattern: regex.source,
|
|
39
|
+
flags: regex.flags
|
|
40
|
+
}), 90);
|
|
41
|
+
|
|
42
|
+
this.add(Map, (map: Map<any, any>, ctx) => {
|
|
43
|
+
const obj: Record<string, any> = {};
|
|
44
|
+
map.forEach((value, key) => {
|
|
45
|
+
const keyStr = typeof key === 'object' ? JSON.stringify(key) : String(key);
|
|
46
|
+
obj[keyStr] = this.serializeInternal(value, {
|
|
47
|
+
...ctx,
|
|
48
|
+
depth: ctx.depth + 1,
|
|
49
|
+
path: [...ctx.path, keyStr]
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return { __type: 'Map', entries: obj };
|
|
53
|
+
}, 80);
|
|
54
|
+
|
|
55
|
+
this.add(Set, (set: Set<any>, ctx) => ({
|
|
56
|
+
__type: 'Set',
|
|
57
|
+
values: Array.from(set).map((v, i) =>
|
|
58
|
+
this.serializeInternal(v, {
|
|
59
|
+
...ctx,
|
|
60
|
+
depth: ctx.depth + 1,
|
|
61
|
+
path: [...ctx.path, `[${i}]`]
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
}), 80);
|
|
65
|
+
|
|
66
|
+
if (typeof Buffer !== 'undefined') {
|
|
67
|
+
this.add(Buffer as any, (buf: Buffer) => ({
|
|
68
|
+
__type: 'Buffer',
|
|
69
|
+
length: buf.length,
|
|
70
|
+
preview: buf.slice(0, 50).toString('hex')
|
|
71
|
+
}), 70);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
add<T>(
|
|
76
|
+
type: new (...args: any[]) => T,
|
|
77
|
+
serializer: SerializerFn<T>,
|
|
78
|
+
priority: number = 50
|
|
79
|
+
): void {
|
|
80
|
+
this.serializers.set(type, { type, serializer, priority });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
remove<T>(type: new (...args: any[]) => T): boolean {
|
|
84
|
+
return this.serializers.delete(type);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
has<T>(type: new (...args: any[]) => T): boolean {
|
|
88
|
+
return this.serializers.has(type);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getAll(): SerializerEntry[] {
|
|
92
|
+
return Array.from(this.serializers.values())
|
|
93
|
+
.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private findSerializer(value: any): SerializerEntry | null {
|
|
97
|
+
const sorted = this.getAll();
|
|
98
|
+
for (const entry of sorted) {
|
|
99
|
+
if (value instanceof entry.type) {
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private serializeInternal(value: any, context: SerializerContext): any {
|
|
107
|
+
if (value === null) return null;
|
|
108
|
+
if (value === undefined) return this.config.preserveUndefined ? undefined : '[undefined]';
|
|
109
|
+
if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;
|
|
110
|
+
if (typeof value !== 'object') return value;
|
|
111
|
+
|
|
112
|
+
if (context.depth >= this.config.maxDepth) {
|
|
113
|
+
return '[Max Depth]';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (context.seen.has(value)) {
|
|
117
|
+
switch (this.config.circular) {
|
|
118
|
+
case 'error':
|
|
119
|
+
throw new Error(`Circular reference at ${context.path.join('.')}`);
|
|
120
|
+
case 'skip':
|
|
121
|
+
return undefined;
|
|
122
|
+
case 'placeholder':
|
|
123
|
+
default:
|
|
124
|
+
return '[Circular]';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
context.seen.add(value);
|
|
128
|
+
|
|
129
|
+
const serializer = this.findSerializer(value);
|
|
130
|
+
if (serializer) {
|
|
131
|
+
return serializer.serializer(value, context);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map((item, i) =>
|
|
136
|
+
this.serializeInternal(item, {
|
|
137
|
+
...context,
|
|
138
|
+
depth: context.depth + 1,
|
|
139
|
+
path: [...context.path, `[${i}]`]
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result: Record<string, any> = {};
|
|
145
|
+
for (const [key, val] of Object.entries(value)) {
|
|
146
|
+
result[key] = this.serializeInternal(val, {
|
|
147
|
+
...context,
|
|
148
|
+
depth: context.depth + 1,
|
|
149
|
+
path: [...context.path, key]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
serialize(value: any, config?: SerializerConfig): any {
|
|
156
|
+
const mergedConfig = { ...this.config, ...config };
|
|
157
|
+
return this.serializeInternal(value, {
|
|
158
|
+
depth: 0,
|
|
159
|
+
maxDepth: mergedConfig.maxDepth,
|
|
160
|
+
path: [],
|
|
161
|
+
seen: new WeakSet()
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let _defaultRegistry: SerializerRegistry | null = null;
|
|
167
|
+
|
|
168
|
+
export function getDefaultSerializerRegistry(): SerializerRegistry {
|
|
169
|
+
if (!_defaultRegistry) {
|
|
170
|
+
_defaultRegistry = new SerializerRegistry();
|
|
171
|
+
}
|
|
172
|
+
return _defaultRegistry;
|
|
173
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SerializerRegistry, getDefaultSerializerRegistry } from './SerializerRegistry.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { LogLevel, DevToolsTheme } from '../types/core.js';
|
|
2
|
+
|
|
3
|
+
interface CacheKey {
|
|
4
|
+
level: LogLevel | 'success';
|
|
5
|
+
theme: DevToolsTheme;
|
|
6
|
+
presetName?: string;
|
|
7
|
+
hasPrefix: boolean;
|
|
8
|
+
hasLocation: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface CacheEntry {
|
|
12
|
+
styles: string[];
|
|
13
|
+
format: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
hits: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StyleCacheConfig {
|
|
19
|
+
maxEntries?: number;
|
|
20
|
+
ttl?: number;
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_CONFIG: Required<StyleCacheConfig> = {
|
|
25
|
+
maxEntries: 100,
|
|
26
|
+
ttl: 5 * 60 * 1000,
|
|
27
|
+
enabled: true
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class StyleCache {
|
|
31
|
+
private cache: Map<string, CacheEntry> = new Map();
|
|
32
|
+
private config: Required<StyleCacheConfig>;
|
|
33
|
+
private hitCount = 0;
|
|
34
|
+
private missCount = 0;
|
|
35
|
+
|
|
36
|
+
constructor(config: StyleCacheConfig = {}) {
|
|
37
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private generateKey(key: CacheKey): string {
|
|
41
|
+
return `${key.level}:${key.theme}:${key.presetName || 'default'}:${key.hasPrefix}:${key.hasLocation}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get(key: CacheKey): CacheEntry | null {
|
|
45
|
+
if (!this.config.enabled) return null;
|
|
46
|
+
|
|
47
|
+
const cacheKey = this.generateKey(key);
|
|
48
|
+
const entry = this.cache.get(cacheKey);
|
|
49
|
+
|
|
50
|
+
if (!entry) {
|
|
51
|
+
this.missCount++;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Date.now() - entry.createdAt > this.config.ttl) {
|
|
56
|
+
this.cache.delete(cacheKey);
|
|
57
|
+
this.missCount++;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
entry.hits++;
|
|
62
|
+
this.cache.delete(cacheKey);
|
|
63
|
+
this.cache.set(cacheKey, entry);
|
|
64
|
+
this.hitCount++;
|
|
65
|
+
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set(key: CacheKey, format: string, styles: string[]): void {
|
|
70
|
+
if (!this.config.enabled) return;
|
|
71
|
+
|
|
72
|
+
const cacheKey = this.generateKey(key);
|
|
73
|
+
|
|
74
|
+
if (this.cache.size >= this.config.maxEntries) {
|
|
75
|
+
const firstKey = this.cache.keys().next().value;
|
|
76
|
+
if (firstKey) {
|
|
77
|
+
this.cache.delete(firstKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.cache.set(cacheKey, {
|
|
82
|
+
format,
|
|
83
|
+
styles: [...styles],
|
|
84
|
+
createdAt: Date.now(),
|
|
85
|
+
hits: 0
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clear(): void {
|
|
90
|
+
this.cache.clear();
|
|
91
|
+
this.hitCount = 0;
|
|
92
|
+
this.missCount = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getStats(): {
|
|
96
|
+
size: number;
|
|
97
|
+
maxSize: number;
|
|
98
|
+
hitRate: number;
|
|
99
|
+
hits: number;
|
|
100
|
+
misses: number;
|
|
101
|
+
} {
|
|
102
|
+
const total = this.hitCount + this.missCount;
|
|
103
|
+
return {
|
|
104
|
+
size: this.cache.size,
|
|
105
|
+
maxSize: this.config.maxEntries,
|
|
106
|
+
hitRate: total > 0 ? this.hitCount / total : 0,
|
|
107
|
+
hits: this.hitCount,
|
|
108
|
+
misses: this.missCount
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setEnabled(enabled: boolean): void {
|
|
113
|
+
this.config.enabled = enabled;
|
|
114
|
+
if (!enabled) {
|
|
115
|
+
this.clear();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isEnabled(): boolean {
|
|
120
|
+
return this.config.enabled;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let _styleCache: StyleCache | null = null;
|
|
125
|
+
|
|
126
|
+
export function getStyleCache(): StyleCache {
|
|
127
|
+
if (!_styleCache) {
|
|
128
|
+
_styleCache = new StyleCache();
|
|
129
|
+
}
|
|
130
|
+
return _styleCache;
|
|
131
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TransportRecord, TransportOptions, ITransport, LogLevel } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export class ConsoleTransport implements ITransport {
|
|
4
|
+
readonly name = 'console';
|
|
5
|
+
|
|
6
|
+
constructor(private options?: TransportOptions) {}
|
|
7
|
+
|
|
8
|
+
write(record: TransportRecord): void {
|
|
9
|
+
const method = this.getConsoleMethod(record.level);
|
|
10
|
+
const prefix = record.prefix ? `[${record.prefix}] ` : '';
|
|
11
|
+
const location = record.location
|
|
12
|
+
? ` (${record.location.file}:${record.location.line})`
|
|
13
|
+
: '';
|
|
14
|
+
|
|
15
|
+
console[method](`[${record.level.toUpperCase()}]${prefix} ${record.msg}${location}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private getConsoleMethod(level: LogLevel): 'log' | 'info' | 'warn' | 'error' {
|
|
19
|
+
switch (level) {
|
|
20
|
+
case 'debug': return 'log';
|
|
21
|
+
case 'info': return 'info';
|
|
22
|
+
case 'warn': return 'warn';
|
|
23
|
+
case 'error':
|
|
24
|
+
case 'critical': return 'error';
|
|
25
|
+
default: return 'log';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TransportRecord, TransportOptions, ITransport } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export interface FileTransportOptions extends TransportOptions {
|
|
4
|
+
destination?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class FileTransport implements ITransport {
|
|
8
|
+
readonly name = 'file';
|
|
9
|
+
private buffer: string[] = [];
|
|
10
|
+
private flushTimer?: ReturnType<typeof setInterval>;
|
|
11
|
+
private options: FileTransportOptions;
|
|
12
|
+
|
|
13
|
+
constructor(options?: FileTransportOptions) {
|
|
14
|
+
this.options = options || {};
|
|
15
|
+
|
|
16
|
+
if (this.options.flushInterval) {
|
|
17
|
+
this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
write(record: TransportRecord): void {
|
|
22
|
+
const line = JSON.stringify(record) + '\n';
|
|
23
|
+
this.buffer.push(line);
|
|
24
|
+
|
|
25
|
+
if (this.buffer.length >= (this.options.batchSize || 100)) {
|
|
26
|
+
this.flush();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async flush(): Promise<void> {
|
|
31
|
+
if (this.buffer.length === 0) return;
|
|
32
|
+
|
|
33
|
+
const lines = this.buffer.join('');
|
|
34
|
+
this.buffer = [];
|
|
35
|
+
|
|
36
|
+
if (typeof globalThis.process !== 'undefined' && typeof require === 'function') {
|
|
37
|
+
try {
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const dest = this.options.destination || 'app.log';
|
|
40
|
+
fs.appendFileSync(dest, lines);
|
|
41
|
+
} catch {
|
|
42
|
+
// Silent fail in browser or if fs not available
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close(): void {
|
|
48
|
+
if (this.flushTimer) {
|
|
49
|
+
clearInterval(this.flushTimer);
|
|
50
|
+
}
|
|
51
|
+
this.flush();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { TransportRecord, TransportOptions, ITransport } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export interface HttpTransportOptions extends TransportOptions {
|
|
4
|
+
url?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class HttpTransport implements ITransport {
|
|
9
|
+
readonly name = 'http';
|
|
10
|
+
private buffer: TransportRecord[] = [];
|
|
11
|
+
private flushTimer?: ReturnType<typeof setInterval>;
|
|
12
|
+
private options: HttpTransportOptions;
|
|
13
|
+
|
|
14
|
+
constructor(options?: HttpTransportOptions) {
|
|
15
|
+
this.options = options || {};
|
|
16
|
+
|
|
17
|
+
if (this.options.flushInterval) {
|
|
18
|
+
this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
write(record: TransportRecord): void {
|
|
23
|
+
this.buffer.push(record);
|
|
24
|
+
|
|
25
|
+
if (this.buffer.length >= (this.options.batchSize || 50)) {
|
|
26
|
+
this.flush();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async flush(): Promise<void> {
|
|
31
|
+
if (this.buffer.length === 0 || !this.options.url) return;
|
|
32
|
+
|
|
33
|
+
const records = [...this.buffer];
|
|
34
|
+
this.buffer = [];
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await fetch(this.options.url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
...this.options.headers
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ logs: records })
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
this.buffer.unshift(...records);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
close(): void {
|
|
51
|
+
if (this.flushTimer) {
|
|
52
|
+
clearInterval(this.flushTimer);
|
|
53
|
+
}
|
|
54
|
+
this.flush();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TransportRecord,
|
|
3
|
+
TransportTarget,
|
|
4
|
+
TransportOptions,
|
|
5
|
+
ITransport,
|
|
6
|
+
ITransportManager,
|
|
7
|
+
LogLevel
|
|
8
|
+
} from '../types/index.js';
|
|
9
|
+
import { LOG_LEVELS } from '../types/index.js';
|
|
10
|
+
|
|
11
|
+
function generateTransportId(): string {
|
|
12
|
+
return `transport-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface TransportEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
transport: ITransport;
|
|
18
|
+
options: TransportOptions;
|
|
19
|
+
level: LogLevel;
|
|
20
|
+
levelValue: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class TransportManager implements ITransportManager {
|
|
24
|
+
private transports: Map<string, TransportEntry> = new Map();
|
|
25
|
+
private defaultLevel: LogLevel = 'info';
|
|
26
|
+
|
|
27
|
+
constructor(defaultLevel?: LogLevel) {
|
|
28
|
+
if (defaultLevel) {
|
|
29
|
+
this.defaultLevel = defaultLevel;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
add(target: TransportTarget): string {
|
|
34
|
+
const id = generateTransportId();
|
|
35
|
+
const level = target.level || this.defaultLevel;
|
|
36
|
+
|
|
37
|
+
let transport: ITransport;
|
|
38
|
+
|
|
39
|
+
if (typeof target.target === 'string') {
|
|
40
|
+
throw new Error(`String transport targets not supported. Use inline transport object.`);
|
|
41
|
+
} else {
|
|
42
|
+
transport = target.target;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const entry: TransportEntry = {
|
|
46
|
+
id,
|
|
47
|
+
transport,
|
|
48
|
+
options: target.options || {},
|
|
49
|
+
level,
|
|
50
|
+
levelValue: LOG_LEVELS[level]
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.transports.set(id, entry);
|
|
54
|
+
return id;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
remove(id: string): boolean {
|
|
58
|
+
const entry = this.transports.get(id);
|
|
59
|
+
if (entry) {
|
|
60
|
+
entry.transport.close?.();
|
|
61
|
+
return this.transports.delete(id);
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async write(record: TransportRecord): Promise<void> {
|
|
67
|
+
const promises: Promise<void>[] = [];
|
|
68
|
+
|
|
69
|
+
for (const entry of this.transports.values()) {
|
|
70
|
+
if (record.levelValue < entry.levelValue) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let transformedRecord = record;
|
|
75
|
+
if (entry.options.transform) {
|
|
76
|
+
const result = entry.options.transform(record);
|
|
77
|
+
if (result === null) continue;
|
|
78
|
+
transformedRecord = result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = entry.transport.write(transformedRecord);
|
|
82
|
+
if (result instanceof Promise) {
|
|
83
|
+
promises.push(result);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await Promise.all(promises);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async flush(): Promise<void> {
|
|
91
|
+
const promises: Promise<void>[] = [];
|
|
92
|
+
|
|
93
|
+
for (const entry of this.transports.values()) {
|
|
94
|
+
if (entry.transport.flush) {
|
|
95
|
+
const result = entry.transport.flush();
|
|
96
|
+
if (result instanceof Promise) {
|
|
97
|
+
promises.push(result);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await Promise.all(promises);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async close(): Promise<void> {
|
|
106
|
+
await this.flush();
|
|
107
|
+
|
|
108
|
+
const promises: Promise<void>[] = [];
|
|
109
|
+
|
|
110
|
+
for (const entry of this.transports.values()) {
|
|
111
|
+
if (entry.transport.close) {
|
|
112
|
+
const result = entry.transport.close();
|
|
113
|
+
if (result instanceof Promise) {
|
|
114
|
+
promises.push(result);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await Promise.all(promises);
|
|
120
|
+
this.transports.clear();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get count(): number {
|
|
124
|
+
return this.transports.size;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
list(): string[] {
|
|
128
|
+
return Array.from(this.transports.keys());
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { TransportManager } from './TransportManager.js';
|
|
2
|
+
export { ConsoleTransport } from './ConsoleTransport.js';
|
|
3
|
+
export { FileTransport, type FileTransportOptions } from './FileTransport.js';
|
|
4
|
+
export { HttpTransport, type HttpTransportOptions } from './HttpTransport.js';
|