@mks2508/better-logger 2.0.1 → 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.
Files changed (172) hide show
  1. package/.claude/settings.local.json +6 -1
  2. package/CHANGELOG.json +93 -1
  3. package/CHANGELOG.md +127 -0
  4. package/CLAUDE.md +14 -1
  5. package/README.md +111 -20
  6. package/bun.lock +333 -0
  7. package/dist/Logger.d.ts +851 -0
  8. package/dist/Logger.d.ts.map +1 -0
  9. package/dist/ScopedLogger.d.ts +57 -0
  10. package/dist/ScopedLogger.d.ts.map +1 -0
  11. package/dist/chunks/Logger-BCfx_yCK.js +2 -0
  12. package/dist/chunks/Logger-BCfx_yCK.js.map +1 -0
  13. package/dist/chunks/Logger-C126NTrD.js +4269 -0
  14. package/dist/chunks/Logger-C126NTrD.js.map +1 -0
  15. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js +33 -0
  16. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js.map +1 -0
  17. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js +2 -0
  18. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js.map +1 -0
  19. package/dist/chunks/environment-Ba5kShbx.js +4 -0
  20. package/dist/chunks/environment-Ba5kShbx.js.map +1 -0
  21. package/dist/chunks/environment-TI2ByCPT.js +1209 -0
  22. package/dist/chunks/environment-TI2ByCPT.js.map +1 -0
  23. package/dist/chunks/formatting-Blwy-f0W.js +2 -0
  24. package/dist/chunks/formatting-Blwy-f0W.js.map +1 -0
  25. package/dist/chunks/formatting-CuNUqGks.js +380 -0
  26. package/dist/chunks/formatting-CuNUqGks.js.map +1 -0
  27. package/dist/cli/CommandProcessor.d.ts +100 -0
  28. package/dist/cli/CommandProcessor.d.ts.map +1 -0
  29. package/dist/cli/commands/ConfigCommand.d.ts +14 -0
  30. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
  31. package/dist/cli/commands/ExportCommand.d.ts +48 -0
  32. package/dist/cli/commands/ExportCommand.d.ts.map +1 -0
  33. package/dist/cli/commands/HistoryCommand.d.ts +47 -0
  34. package/dist/cli/commands/HistoryCommand.d.ts.map +1 -0
  35. package/dist/cli/commands/StatusCommand.d.ts +30 -0
  36. package/dist/cli/commands/StatusCommand.d.ts.map +1 -0
  37. package/dist/cli/commands/ThemeCommand.d.ts +30 -0
  38. package/dist/cli/commands/ThemeCommand.d.ts.map +1 -0
  39. package/dist/cli/help.d.ts +12 -0
  40. package/dist/cli/help.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +15 -0
  42. package/dist/cli/index.d.ts.map +1 -0
  43. package/dist/constants.d.ts +227 -0
  44. package/dist/constants.d.ts.map +1 -0
  45. package/dist/core.cjs +2 -0
  46. package/dist/core.cjs.map +1 -0
  47. package/dist/core.d.ts +127 -0
  48. package/dist/core.d.ts.map +1 -0
  49. package/dist/core.js +291 -0
  50. package/dist/core.js.map +1 -0
  51. package/dist/example.d.ts +18 -0
  52. package/dist/example.d.ts.map +1 -0
  53. package/dist/exports-module.d.ts +196 -0
  54. package/dist/exports-module.d.ts.map +1 -0
  55. package/dist/exports.cjs +2 -0
  56. package/dist/exports.cjs.map +1 -0
  57. package/dist/exports.js +238 -0
  58. package/dist/exports.js.map +1 -0
  59. package/dist/handlers/AnalyticsLogHandler.d.ts +8 -0
  60. package/dist/handlers/AnalyticsLogHandler.d.ts.map +1 -0
  61. package/dist/handlers/ExportLogHandler.d.ts +100 -0
  62. package/dist/handlers/ExportLogHandler.d.ts.map +1 -0
  63. package/dist/handlers/FileLogHandler.d.ts +33 -0
  64. package/dist/handlers/FileLogHandler.d.ts.map +1 -0
  65. package/dist/handlers/RemoteLogHandler.d.ts +11 -0
  66. package/dist/handlers/RemoteLogHandler.d.ts.map +1 -0
  67. package/dist/handlers/index.d.ts +8 -0
  68. package/dist/handlers/index.d.ts.map +1 -0
  69. package/dist/hooks/HookManager.d.ts +21 -0
  70. package/dist/hooks/HookManager.d.ts.map +1 -0
  71. package/dist/hooks/index.d.ts +2 -0
  72. package/dist/hooks/index.d.ts.map +1 -0
  73. package/dist/index.cjs +2 -0
  74. package/dist/index.cjs.map +1 -0
  75. package/dist/index.d.ts +144 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +478 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/main.d.ts +2 -0
  80. package/dist/main.d.ts.map +1 -0
  81. package/dist/serializers/SerializerRegistry.d.ts +16 -0
  82. package/dist/serializers/SerializerRegistry.d.ts.map +1 -0
  83. package/dist/serializers/index.d.ts +2 -0
  84. package/dist/serializers/index.d.ts.map +1 -0
  85. package/dist/styling/LogStyleBuilder.d.ts +146 -0
  86. package/dist/styling/LogStyleBuilder.d.ts.map +1 -0
  87. package/dist/styling/SemanticStyles.d.ts +178 -0
  88. package/dist/styling/SemanticStyles.d.ts.map +1 -0
  89. package/dist/styling/SmartPresets.d.ts +22 -0
  90. package/dist/styling/SmartPresets.d.ts.map +1 -0
  91. package/dist/styling/StyleBuilder.d.ts +140 -0
  92. package/dist/styling/StyleBuilder.d.ts.map +1 -0
  93. package/dist/styling/StyleCache.d.ts +42 -0
  94. package/dist/styling/StyleCache.d.ts.map +1 -0
  95. package/dist/styling/banners.d.ts +42 -0
  96. package/dist/styling/banners.d.ts.map +1 -0
  97. package/dist/styling/index.d.ts +10 -0
  98. package/dist/styling/index.d.ts.map +1 -0
  99. package/dist/styling/themes.d.ts +7 -0
  100. package/dist/styling/themes.d.ts.map +1 -0
  101. package/dist/styling-module.d.ts +178 -0
  102. package/dist/styling-module.d.ts.map +1 -0
  103. package/dist/styling.cjs +2 -0
  104. package/dist/styling.cjs.map +1 -0
  105. package/dist/styling.js +146 -0
  106. package/dist/styling.js.map +1 -0
  107. package/dist/terminal/terminal-renderer.d.ts +63 -0
  108. package/dist/terminal/terminal-renderer.d.ts.map +1 -0
  109. package/dist/transports/ConsoleTransport.d.ts +9 -0
  110. package/dist/transports/ConsoleTransport.d.ts.map +1 -0
  111. package/dist/transports/FileTransport.d.ts +15 -0
  112. package/dist/transports/FileTransport.d.ts.map +1 -0
  113. package/dist/transports/HttpTransport.d.ts +16 -0
  114. package/dist/transports/HttpTransport.d.ts.map +1 -0
  115. package/dist/transports/TransportManager.d.ts +14 -0
  116. package/dist/transports/TransportManager.d.ts.map +1 -0
  117. package/dist/transports/index.d.ts +5 -0
  118. package/dist/transports/index.d.ts.map +1 -0
  119. package/dist/types/core.d.ts +272 -0
  120. package/dist/types/core.d.ts.map +1 -0
  121. package/dist/types/handlers.d.ts +85 -0
  122. package/dist/types/handlers.d.ts.map +1 -0
  123. package/dist/types/hooks.d.ts +36 -0
  124. package/dist/types/hooks.d.ts.map +1 -0
  125. package/dist/types/index.d.ts +11 -0
  126. package/dist/types/index.d.ts.map +1 -0
  127. package/dist/types/serializers.d.ts +25 -0
  128. package/dist/types/serializers.d.ts.map +1 -0
  129. package/dist/types/transports.d.ts +47 -0
  130. package/dist/types/transports.d.ts.map +1 -0
  131. package/dist/utils/adapter.d.ts +48 -0
  132. package/dist/utils/adapter.d.ts.map +1 -0
  133. package/dist/utils/ansi-colors.d.ts +156 -0
  134. package/dist/utils/ansi-colors.d.ts.map +1 -0
  135. package/dist/utils/environment-detector.d.ts +35 -0
  136. package/dist/utils/environment-detector.d.ts.map +1 -0
  137. package/dist/utils/environment.d.ts +47 -0
  138. package/dist/utils/environment.d.ts.map +1 -0
  139. package/dist/utils/formatting.d.ts +58 -0
  140. package/dist/utils/formatting.d.ts.map +1 -0
  141. package/dist/utils/index.d.ts +8 -0
  142. package/dist/utils/index.d.ts.map +1 -0
  143. package/dist/utils/opentui-detection.d.ts +34 -0
  144. package/dist/utils/opentui-detection.d.ts.map +1 -0
  145. package/dist/utils/output.d.ts +46 -0
  146. package/dist/utils/output.d.ts.map +1 -0
  147. package/dist/utils/stackTrace.d.ts +6 -0
  148. package/dist/utils/stackTrace.d.ts.map +1 -0
  149. package/dist/utils/timestamps.d.ts +20 -0
  150. package/dist/utils/timestamps.d.ts.map +1 -0
  151. package/dist/vite.svg +1 -0
  152. package/docs/API.md +227 -0
  153. package/package.json +1 -1
  154. package/packages/core/package.json +1 -1
  155. package/src/Logger.ts +391 -102
  156. package/src/ScopedLogger.ts +78 -318
  157. package/src/hooks/HookManager.ts +177 -0
  158. package/src/hooks/index.ts +1 -0
  159. package/src/index.ts +77 -2
  160. package/src/serializers/SerializerRegistry.ts +173 -0
  161. package/src/serializers/index.ts +1 -0
  162. package/src/styling/StyleCache.ts +131 -0
  163. package/src/transports/ConsoleTransport.ts +28 -0
  164. package/src/transports/FileTransport.ts +53 -0
  165. package/src/transports/HttpTransport.ts +56 -0
  166. package/src/transports/TransportManager.ts +130 -0
  167. package/src/transports/index.ts +4 -0
  168. package/src/types/core.ts +46 -0
  169. package/src/types/hooks.ts +45 -0
  170. package/src/types/index.ts +39 -1
  171. package/src/types/serializers.ts +30 -0
  172. package/src/types/transports.ts +52 -0
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 = (prefix: string) => getLogger().scope(prefix);
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
- OutputFormat
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';