@od-oneapp/observability 2026.1.1301

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 (107) hide show
  1. package/README.md +523 -0
  2. package/dist/client-next.d.mts +20 -0
  3. package/dist/client-next.d.mts.map +1 -0
  4. package/dist/client-next.mjs +64 -0
  5. package/dist/client-next.mjs.map +1 -0
  6. package/dist/client.d.mts +11 -0
  7. package/dist/client.d.mts.map +1 -0
  8. package/dist/client.mjs +47 -0
  9. package/dist/client.mjs.map +1 -0
  10. package/dist/env.d.mts +15 -0
  11. package/dist/env.d.mts.map +1 -0
  12. package/dist/env.mjs +45 -0
  13. package/dist/env.mjs.map +1 -0
  14. package/dist/factory-DkY353r8.mjs +380 -0
  15. package/dist/factory-DkY353r8.mjs.map +1 -0
  16. package/dist/hooks-useObservability.d.mts +11 -0
  17. package/dist/hooks-useObservability.d.mts.map +1 -0
  18. package/dist/hooks-useObservability.mjs +174 -0
  19. package/dist/hooks-useObservability.mjs.map +1 -0
  20. package/dist/index-CpcdzWrF.d.mts +24 -0
  21. package/dist/index-CpcdzWrF.d.mts.map +1 -0
  22. package/dist/index.d.mts +88 -0
  23. package/dist/index.d.mts.map +1 -0
  24. package/dist/index.mjs +97 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/manager-BxQqOPEg.d.mts +33 -0
  27. package/dist/manager-BxQqOPEg.d.mts.map +1 -0
  28. package/dist/plugin-Bfq-o3nr.d.mts +60 -0
  29. package/dist/plugin-Bfq-o3nr.d.mts.map +1 -0
  30. package/dist/plugin-Bt-ygG1m.d.mts +254 -0
  31. package/dist/plugin-Bt-ygG1m.d.mts.map +1 -0
  32. package/dist/plugin-CLFwRERa.mjs +593 -0
  33. package/dist/plugin-CLFwRERa.mjs.map +1 -0
  34. package/dist/plugin-CP895lBx.mjs +534 -0
  35. package/dist/plugin-CP895lBx.mjs.map +1 -0
  36. package/dist/plugin-CaQxviDs.d.mts +61 -0
  37. package/dist/plugin-CaQxviDs.d.mts.map +1 -0
  38. package/dist/plugin-lPdJirTY.mjs +234 -0
  39. package/dist/plugin-lPdJirTY.mjs.map +1 -0
  40. package/dist/plugins-betterstack-env.d.mts +29 -0
  41. package/dist/plugins-betterstack-env.d.mts.map +1 -0
  42. package/dist/plugins-betterstack-env.mjs +75 -0
  43. package/dist/plugins-betterstack-env.mjs.map +1 -0
  44. package/dist/plugins-betterstack.d.mts +4 -0
  45. package/dist/plugins-betterstack.mjs +4 -0
  46. package/dist/plugins-console.d.mts +37 -0
  47. package/dist/plugins-console.d.mts.map +1 -0
  48. package/dist/plugins-console.mjs +196 -0
  49. package/dist/plugins-console.mjs.map +1 -0
  50. package/dist/plugins-sentry-env.d.mts +37 -0
  51. package/dist/plugins-sentry-env.d.mts.map +1 -0
  52. package/dist/plugins-sentry-env.mjs +79 -0
  53. package/dist/plugins-sentry-env.mjs.map +1 -0
  54. package/dist/plugins-sentry-microfrontend-env.d.mts +49 -0
  55. package/dist/plugins-sentry-microfrontend-env.d.mts.map +1 -0
  56. package/dist/plugins-sentry-microfrontend-env.mjs +80 -0
  57. package/dist/plugins-sentry-microfrontend-env.mjs.map +1 -0
  58. package/dist/plugins-sentry-microfrontend.d.mts +2 -0
  59. package/dist/plugins-sentry-microfrontend.mjs +3 -0
  60. package/dist/plugins-sentry.d.mts +5 -0
  61. package/dist/plugins-sentry.mjs +6 -0
  62. package/dist/server-edge.d.mts +15 -0
  63. package/dist/server-edge.d.mts.map +1 -0
  64. package/dist/server-edge.mjs +53 -0
  65. package/dist/server-edge.mjs.map +1 -0
  66. package/dist/server-next.d.mts +17 -0
  67. package/dist/server-next.d.mts.map +1 -0
  68. package/dist/server-next.mjs +64 -0
  69. package/dist/server-next.mjs.map +1 -0
  70. package/dist/server.d.mts +11 -0
  71. package/dist/server.d.mts.map +1 -0
  72. package/dist/server.mjs +48 -0
  73. package/dist/server.mjs.map +1 -0
  74. package/dist/utils-CuGrTcD6.d.mts +77 -0
  75. package/dist/utils-CuGrTcD6.d.mts.map +1 -0
  76. package/env.ts +67 -0
  77. package/package.json +147 -0
  78. package/src/client-next.ts +131 -0
  79. package/src/client.ts +70 -0
  80. package/src/core/index.ts +15 -0
  81. package/src/core/manager.ts +361 -0
  82. package/src/core/plugin.ts +61 -0
  83. package/src/core/types.ts +151 -0
  84. package/src/factory/builder.ts +132 -0
  85. package/src/factory/index.ts +67 -0
  86. package/src/factory/presets.ts +78 -0
  87. package/src/hooks/useObservability.ts +206 -0
  88. package/src/plugins/betterstack/env.ts +101 -0
  89. package/src/plugins/betterstack/index.ts +15 -0
  90. package/src/plugins/betterstack/plugin.ts +373 -0
  91. package/src/plugins/console/index.ts +323 -0
  92. package/src/plugins/sentry/__tests__/plugin-tracing.test.ts +511 -0
  93. package/src/plugins/sentry/env.ts +93 -0
  94. package/src/plugins/sentry/index.ts +28 -0
  95. package/src/plugins/sentry/plugin.ts +953 -0
  96. package/src/plugins/sentry/types.ts +252 -0
  97. package/src/plugins/sentry-microfrontend/env.ts +105 -0
  98. package/src/plugins/sentry-microfrontend/index.ts +12 -0
  99. package/src/plugins/sentry-microfrontend/multiplexed-transport.ts +221 -0
  100. package/src/plugins/sentry-microfrontend/plugin.ts +500 -0
  101. package/src/plugins/sentry-microfrontend/sentry-types.ts +140 -0
  102. package/src/plugins/sentry-microfrontend/types.ts +130 -0
  103. package/src/plugins/sentry-microfrontend/utils.ts +326 -0
  104. package/src/server-edge.ts +113 -0
  105. package/src/server-next.ts +114 -0
  106. package/src/server.ts +71 -0
  107. package/src/shared.ts +148 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * @fileoverview Better Stack (Logtail) plugin implementation
3
+ * Better Stack (Logtail) plugin implementation
4
+ * Updated to use official @logtail packages
5
+ */
6
+
7
+ import { logError, logWarn } from '@repo/shared/logger';
8
+ import type { ObservabilityServerPlugin } from '../../core/plugin';
9
+ import type {
10
+ Breadcrumb,
11
+ LogLevel,
12
+ ObservabilityContext,
13
+ ObservabilityUser,
14
+ } from '../../core/types';
15
+ import { safeEnv } from './env';
16
+
17
+ /**
18
+ * Maximum number of breadcrumbs to include in error/message context
19
+ * Limits context size to prevent excessive data transmission
20
+ */
21
+ const MAX_BREADCRUMBS_IN_CONTEXT = 10;
22
+
23
+ /**
24
+ * Maximum number of breadcrumbs to store in memory
25
+ * Prevents unbounded memory growth while maintaining useful history
26
+ */
27
+ const MAX_BREADCRUMBS_STORED = 100;
28
+
29
+ /**
30
+ * Minimal Logtail interface for common methods across all @logtail packages
31
+ */
32
+ interface LogtailClient {
33
+ info(message: string, data?: any): void;
34
+ warn(message: string, data?: any): void;
35
+ error(message: string, data?: any): void;
36
+ debug(message: string, data?: any): void;
37
+ flush(): Promise<void>;
38
+ }
39
+
40
+ /**
41
+ * Better Stack plugin configuration
42
+ */
43
+ export interface BetterStackPluginConfig {
44
+ /**
45
+ * The @logtail package to use (e.g., '@logtail/js', '@logtail/next')
46
+ * If not provided, the plugin will auto-detect based on environment
47
+ */
48
+ logtailPackage?: string;
49
+
50
+ // Core configuration options
51
+ sourceToken?: string;
52
+ endpoint?: string;
53
+ logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'off';
54
+ enabled?: boolean;
55
+
56
+ // Advanced options
57
+ batchInterval?: number;
58
+ batchSize?: number;
59
+ retryCount?: number;
60
+ retryBackoff?: boolean;
61
+ middleware?: any[];
62
+ }
63
+
64
+ /**
65
+ * Better Stack plugin implementation using official @logtail packages
66
+ */
67
+ /**
68
+ * Better Stack (Logtail) plugin implementation.
69
+ *
70
+ * Integrates Better Stack logging service into the observability system.
71
+ * Uses official @logtail packages (@logtail/js or @logtail/next) for unified
72
+ * logging across environments.
73
+ */
74
+ export class BetterStackPlugin<
75
+ T extends LogtailClient = LogtailClient,
76
+ > implements ObservabilityServerPlugin<T> {
77
+ name = 'betterstack';
78
+ enabled: boolean;
79
+ protected client: T | undefined;
80
+ protected initialized = false;
81
+ protected logtailPackage: string;
82
+ protected currentUser: ObservabilityUser | null = null;
83
+ protected breadcrumbs: Breadcrumb[] = [];
84
+ protected breadcrumbIndex = 0;
85
+ protected config: BetterStackPluginConfig;
86
+
87
+ /**
88
+ * Create a new BetterStackPlugin instance.
89
+ *
90
+ * @param config - Better Stack plugin configuration
91
+ */
92
+ constructor(config: BetterStackPluginConfig = {}) {
93
+ this.config = config;
94
+ const env = safeEnv();
95
+ // Auto-enable if token is provided
96
+ const hasToken =
97
+ config.sourceToken ??
98
+ env.BETTER_STACK_SOURCE_TOKEN ??
99
+ env.BETTERSTACK_SOURCE_TOKEN ??
100
+ env.LOGTAIL_SOURCE_TOKEN ??
101
+ env.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ??
102
+ env.NEXT_PUBLIC_BETTERSTACK_TOKEN ??
103
+ env.NEXT_PUBLIC_LOGTAIL_TOKEN;
104
+ this.enabled = config.enabled ?? Boolean(hasToken);
105
+
106
+ // Determine Logtail package to use
107
+ this.logtailPackage = config.logtailPackage ?? this.detectLogtailPackage();
108
+ }
109
+
110
+ /**
111
+ * Detect which Logtail package to use based on the runtime environment.
112
+ *
113
+ * @returns Package name ('@logtail/next' for Next.js, '@logtail/js' otherwise)
114
+ */
115
+ private detectLogtailPackage(): string {
116
+ // Next.js environments use Next.js package
117
+ if (process.env.NEXT_RUNTIME) {
118
+ return '@logtail/next';
119
+ }
120
+ // All other environments use the unified js package (bundles both node and browser)
121
+ return '@logtail/js';
122
+ }
123
+
124
+ getClient(): T | undefined {
125
+ return this.client;
126
+ }
127
+
128
+ /**
129
+ * Get safe environment access (for testing/mocking).
130
+ *
131
+ * @returns Environment configuration object
132
+ */
133
+ protected getSafeEnv() {
134
+ return safeEnv();
135
+ }
136
+
137
+ async initialize(config?: BetterStackPluginConfig): Promise<void> {
138
+ if (this.initialized || !this.enabled) return;
139
+
140
+ const env = safeEnv();
141
+ const mergedConfig = { ...this.config, ...config };
142
+
143
+ // Get source token with priority: config > new env vars > legacy env vars
144
+ const sourceToken =
145
+ mergedConfig.sourceToken ??
146
+ env.BETTER_STACK_SOURCE_TOKEN ??
147
+ env.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ??
148
+ env.LOGTAIL_SOURCE_TOKEN ??
149
+ env.BETTERSTACK_SOURCE_TOKEN ??
150
+ env.NEXT_PUBLIC_LOGTAIL_TOKEN ??
151
+ env.NEXT_PUBLIC_BETTERSTACK_TOKEN;
152
+
153
+ if (!sourceToken) {
154
+ logWarn('Better Stack plugin: No source token provided, skipping initialization');
155
+ this.enabled = false;
156
+ return;
157
+ }
158
+
159
+ try {
160
+ // Dynamic import of the specified Logtail package
161
+ const LogtailModule = await import(/* webpackIgnore: true */ this.logtailPackage);
162
+
163
+ // Handle different export patterns
164
+ const LogtailClass = LogtailModule.default ?? LogtailModule.Logtail ?? LogtailModule;
165
+
166
+ if (!LogtailClass) {
167
+ logError(`Better Stack plugin: Could not find Logtail class in ${this.logtailPackage}`, {
168
+ logtailPackage: this.logtailPackage,
169
+ });
170
+ this.enabled = false;
171
+ return;
172
+ }
173
+
174
+ // Build configuration object
175
+ const logtailConfig: any = {
176
+ sourceToken,
177
+ };
178
+
179
+ // Add optional configuration
180
+ if (
181
+ mergedConfig.endpoint ??
182
+ env.BETTER_STACK_INGESTING_URL ??
183
+ env.NEXT_PUBLIC_BETTER_STACK_INGESTING_URL
184
+ ) {
185
+ logtailConfig.endpoint =
186
+ mergedConfig.endpoint ??
187
+ env.BETTER_STACK_INGESTING_URL ??
188
+ env.NEXT_PUBLIC_BETTER_STACK_INGESTING_URL;
189
+ }
190
+
191
+ // Initialize the client
192
+ this.client = new LogtailClass(logtailConfig) as T;
193
+
194
+ this.initialized = true;
195
+ } catch (error) {
196
+ logError(`Failed to import Logtail package '${this.logtailPackage}'`, {
197
+ error,
198
+ logtailPackage: this.logtailPackage,
199
+ });
200
+ this.enabled = false;
201
+ }
202
+ }
203
+
204
+ async shutdown(): Promise<void> {
205
+ if (this.client && this.initialized) {
206
+ await this.client.flush();
207
+ this.initialized = false;
208
+ }
209
+ }
210
+
211
+ captureException(error: Error | unknown, context?: ObservabilityContext): void {
212
+ if (!this.enabled || !this.client) return;
213
+
214
+ const errorObj = error instanceof Error ? error : new Error(String(error));
215
+
216
+ // Build structured data
217
+ const data = {
218
+ error: {
219
+ name: errorObj.name,
220
+ message: errorObj.message,
221
+ stack: errorObj.stack,
222
+ },
223
+ context: context?.extra,
224
+ tags: context?.tags,
225
+ user: context?.user ?? this.currentUser,
226
+ breadcrumbs: this.getRecentBreadcrumbs(MAX_BREADCRUMBS_IN_CONTEXT),
227
+ };
228
+
229
+ this.client.error(errorObj.message, data);
230
+ }
231
+
232
+ captureMessage(message: string, level: LogLevel = 'info', context?: ObservabilityContext): void {
233
+ if (!this.enabled || !this.client) return;
234
+
235
+ // Build structured data
236
+ const data = {
237
+ level,
238
+ context: context?.extra,
239
+ tags: context?.tags,
240
+ user: context?.user ?? this.currentUser,
241
+ breadcrumbs: this.getRecentBreadcrumbs(MAX_BREADCRUMBS_IN_CONTEXT),
242
+ };
243
+
244
+ // Map log levels to Logtail methods
245
+ switch (level) {
246
+ case 'debug':
247
+ this.client.debug(message, data);
248
+ break;
249
+ case 'warning':
250
+ this.client.warn(message, data);
251
+ break;
252
+ case 'error':
253
+ this.client.error(message, data);
254
+ break;
255
+ case 'info':
256
+ default:
257
+ this.client.info(message, data);
258
+ break;
259
+ }
260
+ }
261
+
262
+ setUser(user: ObservabilityUser | null): void {
263
+ if (!this.enabled) return;
264
+ this.currentUser = user;
265
+ }
266
+
267
+ addBreadcrumb(breadcrumb: Breadcrumb): void {
268
+ if (!this.enabled) return;
269
+
270
+ const enriched = {
271
+ ...breadcrumb,
272
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1000,
273
+ };
274
+
275
+ // Use circular buffer to avoid array reallocation
276
+ if (this.breadcrumbs.length < MAX_BREADCRUMBS_STORED) {
277
+ this.breadcrumbs.push(enriched);
278
+ } else {
279
+ // Overwrite oldest entry (circular buffer)
280
+ this.breadcrumbs[this.breadcrumbIndex] = enriched;
281
+ this.breadcrumbIndex = (this.breadcrumbIndex + 1) % MAX_BREADCRUMBS_STORED;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Get the most recent breadcrumbs in chronological order
287
+ * @param count - Number of breadcrumbs to retrieve
288
+ * @returns Array of recent breadcrumbs
289
+ */
290
+ private getRecentBreadcrumbs(count: number): Breadcrumb[] {
291
+ const { length } = this.breadcrumbs;
292
+ if (length === 0) return [];
293
+ if (length <= count) return this.breadcrumbs.slice();
294
+
295
+ // If buffer is not full yet, return last N items from end of array
296
+ if (length < MAX_BREADCRUMBS_STORED) {
297
+ return this.breadcrumbs.slice(length - count);
298
+ }
299
+
300
+ // Circular buffer logic:
301
+ // - When the buffer is full, `this.breadcrumbIndex` points to the oldest entry (the next to be overwritten).
302
+ // - The most recent breadcrumb is at index `(this.breadcrumbIndex - 1 + length) % length`.
303
+ // - To get the last `count` breadcrumbs in chronological order, we start at
304
+ // `(this.breadcrumbIndex - count + length) % length` and read `count` entries, wrapping around as needed.
305
+ // - This ensures we always get the correct slice, even if the buffer has wrapped around.
306
+ const result: Breadcrumb[] = [];
307
+ const start = (this.breadcrumbIndex - count + length) % length;
308
+
309
+ for (let i = 0; i < count; i++) {
310
+ const index = (start + i) % length;
311
+ const breadcrumb = this.breadcrumbs[index];
312
+ if (breadcrumb) {
313
+ result.push(breadcrumb);
314
+ }
315
+ }
316
+
317
+ return result;
318
+ }
319
+
320
+ withScope(callback: (scope: any) => void): void {
321
+ if (!this.enabled) return;
322
+
323
+ try {
324
+ // Simple scope implementation
325
+ const scope = {
326
+ setContext: (_key: string, _context: unknown) => {
327
+ // Could store this for next log entry
328
+ },
329
+ setUser: (user: ObservabilityUser | null) => {
330
+ this.setUser(user);
331
+ },
332
+ };
333
+ callback(scope);
334
+ } catch {
335
+ // Gracefully handle scope errors
336
+ }
337
+ }
338
+
339
+ async flush(_timeout?: number): Promise<boolean> {
340
+ if (!this.enabled || !this.client) return true;
341
+
342
+ try {
343
+ await this.client.flush();
344
+ return true;
345
+ } catch (error) {
346
+ logError('Better Stack flush error', { error });
347
+ return false;
348
+ }
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Factory function to create a Better Stack plugin.
354
+ *
355
+ * Creates a configured BetterStackPlugin instance with optional configuration.
356
+ * Auto-detects the appropriate @logtail package based on the environment.
357
+ *
358
+ * @param config - Optional Better Stack plugin configuration
359
+ * @returns BetterStackPlugin instance
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * const betterstack = createBetterStackPlugin({
364
+ * sourceToken: '...',
365
+ * logLevel: 'info',
366
+ * });
367
+ * ```
368
+ */
369
+ export const createBetterStackPlugin = <T extends LogtailClient = LogtailClient>(
370
+ config?: BetterStackPluginConfig,
371
+ ): BetterStackPlugin<T> => {
372
+ return new BetterStackPlugin<T>(config);
373
+ };
@@ -0,0 +1,323 @@
1
+ /**
2
+ * @fileoverview Console plugin for observability
3
+ * Console plugin for observability
4
+ * Simple logging implementation for development
5
+ */
6
+
7
+ import { isBrowser } from '../../shared';
8
+
9
+ import type {
10
+ ObservabilityPlugin,
11
+ ObservabilityServerPlugin,
12
+ PluginFactory,
13
+ } from '../../core/plugin';
14
+ import type {
15
+ Breadcrumb,
16
+ LogLevel,
17
+ ObservabilityContext,
18
+ ObservabilityUser,
19
+ } from '../../core/types';
20
+
21
+ /**
22
+ * Console plugin configuration
23
+ */
24
+ export interface ConsolePluginConfig {
25
+ prefix?: string;
26
+ logLevel?: LogLevel;
27
+ enabled?: boolean;
28
+ colors?: boolean;
29
+ }
30
+
31
+ /**
32
+ * Console plugin implementation for browser environments
33
+ */
34
+ /**
35
+ * Console plugin implementation for browser environments.
36
+ *
37
+ * Provides simple console logging with color support for development.
38
+ * Supports both browser (CSS colors) and Node.js (ANSI colors) environments.
39
+ */
40
+ export class ConsolePlugin implements ObservabilityPlugin<Console> {
41
+ name = 'console';
42
+ enabled: boolean;
43
+ protected prefix: string;
44
+ private colors: boolean;
45
+ private isBrowser: boolean;
46
+
47
+ /**
48
+ * Create a new ConsolePlugin instance.
49
+ *
50
+ * @param config - Console plugin configuration
51
+ */
52
+ constructor(config: ConsolePluginConfig = {}) {
53
+ this.enabled = config.enabled ?? true;
54
+ this.prefix = config.prefix ?? '[Console]';
55
+ // Note: config.logLevel is accepted but not yet used (reserved for future log level filtering)
56
+ this.colors = config.colors ?? true;
57
+ this.isBrowser = isBrowser();
58
+ }
59
+
60
+ async initialize(): Promise<void> {
61
+ // Console doesn't need initialization
62
+ }
63
+
64
+ async shutdown(): Promise<void> {
65
+ // Console doesn't need shutdown
66
+ }
67
+
68
+ getClient(): Console | undefined {
69
+ return undefined; // Console has no client
70
+ }
71
+
72
+ captureException(error: Error | unknown, context?: ObservabilityContext): void {
73
+ if (!this.enabled) return;
74
+
75
+ try {
76
+ const contextData = this.formatContext(context);
77
+
78
+ if (this.colors && this.isBrowser) {
79
+ // Browser: Use CSS colors
80
+ console.error(
81
+ `%c${this.prefix} Error:`,
82
+ 'color: red; font-weight: bold',
83
+ error,
84
+ contextData,
85
+ );
86
+ } else if (this.colors) {
87
+ // Node.js: Use ANSI codes
88
+ console.error(`\x1b[31m${this.prefix} Error:\x1b[0m`, error, contextData);
89
+ } else {
90
+ // No colors
91
+ console.error(`${this.prefix} Error:`, error, contextData);
92
+ }
93
+ } catch {
94
+ // Gracefully handle console errors
95
+ }
96
+ }
97
+
98
+ captureMessage(message: string, level: LogLevel = 'info', context?: ObservabilityContext): void {
99
+ if (!this.enabled) return;
100
+
101
+ try {
102
+ const contextData = this.formatContext(context);
103
+ const logMethod = this.getLogMethod(level);
104
+ const levelLabel = this.getLevelLabel(level);
105
+
106
+ if (this.colors && this.isBrowser) {
107
+ // Browser: Use CSS colors
108
+ const cssColor = this.getBrowserColorCSS(level);
109
+ (console[logMethod] as (...args: any[]) => void)(
110
+ `%c${this.prefix} ${levelLabel}:`,
111
+ cssColor,
112
+ message,
113
+ contextData,
114
+ );
115
+ } else if (this.colors) {
116
+ // Node.js: Use ANSI codes
117
+ const ansiColor = this.getAnsiColorCode(level);
118
+ (console[logMethod] as (...args: any[]) => void)(
119
+ `${ansiColor}${this.prefix} ${levelLabel}:\x1b[0m`,
120
+ message,
121
+ contextData,
122
+ );
123
+ } else {
124
+ // No colors
125
+ (console[logMethod] as (...args: any[]) => void)(
126
+ `${this.prefix} ${levelLabel}:`,
127
+ message,
128
+ contextData,
129
+ );
130
+ }
131
+ } catch {
132
+ // Gracefully handle console errors
133
+ }
134
+ }
135
+
136
+ setUser(user: ObservabilityUser | null): void {
137
+ if (!this.enabled) return;
138
+
139
+ try {
140
+ if (user === null) {
141
+ console.info(this.prefix, 'User cleared');
142
+ } else {
143
+ console.info(this.prefix, 'User set:', JSON.stringify(user));
144
+ }
145
+ } catch {
146
+ // Gracefully handle console errors
147
+ }
148
+ }
149
+
150
+ addBreadcrumb(breadcrumb: Breadcrumb): void {
151
+ if (!this.enabled) return;
152
+
153
+ try {
154
+ const enrichedBreadcrumb = {
155
+ ...breadcrumb,
156
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1000,
157
+ };
158
+ console.log(this.prefix, 'Breadcrumb:', JSON.stringify(enrichedBreadcrumb));
159
+ } catch {
160
+ // Gracefully handle console errors
161
+ }
162
+ }
163
+
164
+ withScope(callback: (scope: any) => void): void {
165
+ if (!this.enabled) return;
166
+
167
+ try {
168
+ const scope = {
169
+ setContext: (key: string, context: unknown) => {
170
+ console.log(this.prefix, 'Context set:', key, JSON.stringify(context));
171
+ },
172
+ setUser: (user: ObservabilityUser | null) => {
173
+ this.setUser(user);
174
+ },
175
+ };
176
+ callback(scope);
177
+ } catch {
178
+ // Gracefully handle scope errors
179
+ }
180
+ }
181
+
182
+ async flush(): Promise<boolean> {
183
+ // Console doesn't need flushing
184
+ if (this.enabled) {
185
+ console.log(this.prefix, 'Flushed');
186
+ }
187
+ return true;
188
+ }
189
+
190
+ private formatContext(context?: ObservabilityContext): any {
191
+ if (!context) return {};
192
+
193
+ // Handle new format with explicit extra/tags structure
194
+ if (context.extra !== undefined || context.tags !== undefined) {
195
+ return {
196
+ context: context.extra,
197
+ tags: context.tags,
198
+ };
199
+ }
200
+
201
+ // Handle AI package format with operation/metadata structure
202
+ if ('operation' in context || 'metadata' in context) {
203
+ return {
204
+ context: context.metadata ?? {},
205
+ tags: { operation: context.operation },
206
+ };
207
+ }
208
+
209
+ // Handle any other context format - use as-is for context
210
+ return {
211
+ context,
212
+ tags: {},
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Get CSS color style for browser console
218
+ * @param level - Log level
219
+ * @returns CSS style string
220
+ */
221
+ private getBrowserColorCSS(level: LogLevel): string {
222
+ const colorMap: Record<LogLevel, string> = {
223
+ debug: 'color: #00CED1; font-weight: bold', // Dark Cyan
224
+ info: 'color: #32CD32; font-weight: bold', // Lime Green
225
+ warning: 'color: #FFA500; font-weight: bold', // Orange
226
+ error: 'color: #DC143C; font-weight: bold', // Crimson
227
+ };
228
+ return colorMap[level] ?? 'color: inherit';
229
+ }
230
+
231
+ /**
232
+ * Get ANSI color code for Node.js terminal
233
+ * @param level - Log level
234
+ * @returns ANSI escape code
235
+ */
236
+ private getAnsiColorCode(level: LogLevel): string {
237
+ const colorMap: Record<LogLevel, string> = {
238
+ debug: '\x1b[36m', // Cyan
239
+ info: '\x1b[32m', // Green
240
+ warning: '\x1b[33m', // Yellow
241
+ error: '\x1b[31m', // Red
242
+ };
243
+ return colorMap[level] ?? '';
244
+ }
245
+
246
+ /**
247
+ * Get the console method name for a log level.
248
+ *
249
+ * @param level - Log level
250
+ * @returns Console method name ('debug', 'info', 'warn', or 'error')
251
+ */
252
+ private getLogMethod(level: LogLevel): keyof Console {
253
+ switch (level) {
254
+ case 'debug':
255
+ return 'debug';
256
+ case 'info':
257
+ return 'info';
258
+ case 'warning':
259
+ return 'warn';
260
+ case 'error':
261
+ return 'error';
262
+ default:
263
+ return 'info';
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Get a human-readable label for a log level.
269
+ *
270
+ * @param level - Log level
271
+ * @returns Capitalized level label ('Debug', 'Info', 'Warning', 'Error')
272
+ */
273
+ private getLevelLabel(level: LogLevel): string {
274
+ switch (level) {
275
+ case 'debug':
276
+ return 'Debug';
277
+ case 'info':
278
+ return 'Info';
279
+ case 'warning':
280
+ return 'Warning';
281
+ case 'error':
282
+ return 'Error';
283
+ default:
284
+ return 'Info';
285
+ }
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Console plugin implementation for server environments
291
+ * Inherits flush() from ConsolePlugin - no override needed
292
+ */
293
+ export class ConsoleServerPlugin
294
+ extends ConsolePlugin
295
+ implements ObservabilityServerPlugin<Console> {}
296
+
297
+ /**
298
+ * Factory function to create a console plugin for browser environments.
299
+ *
300
+ * @param config - Console plugin configuration
301
+ * @returns ConsolePlugin instance configured for browser
302
+ */
303
+ export const createConsolePlugin: PluginFactory<ConsolePluginConfig, ConsolePlugin> = config => {
304
+ return new ConsolePlugin(config);
305
+ };
306
+
307
+ /**
308
+ * Factory function to create a console plugin for server environments.
309
+ *
310
+ * Creates a server-compatible console plugin that includes flush() capability.
311
+ *
312
+ * @param config - Console plugin configuration
313
+ * @returns ConsoleServerPlugin instance configured for server
314
+ */
315
+ export const createConsoleServerPlugin: PluginFactory<
316
+ ConsolePluginConfig,
317
+ ConsoleServerPlugin
318
+ > = config => {
319
+ return new ConsoleServerPlugin(config);
320
+ };
321
+
322
+ // Re-export types
323
+ export type { ObservabilityPlugin, ObservabilityServerPlugin } from '../../core/plugin';