@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.
- package/README.md +523 -0
- package/dist/client-next.d.mts +20 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +64 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +11 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +47 -0
- package/dist/client.mjs.map +1 -0
- package/dist/env.d.mts +15 -0
- package/dist/env.d.mts.map +1 -0
- package/dist/env.mjs +45 -0
- package/dist/env.mjs.map +1 -0
- package/dist/factory-DkY353r8.mjs +380 -0
- package/dist/factory-DkY353r8.mjs.map +1 -0
- package/dist/hooks-useObservability.d.mts +11 -0
- package/dist/hooks-useObservability.d.mts.map +1 -0
- package/dist/hooks-useObservability.mjs +174 -0
- package/dist/hooks-useObservability.mjs.map +1 -0
- package/dist/index-CpcdzWrF.d.mts +24 -0
- package/dist/index-CpcdzWrF.d.mts.map +1 -0
- package/dist/index.d.mts +88 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +97 -0
- package/dist/index.mjs.map +1 -0
- package/dist/manager-BxQqOPEg.d.mts +33 -0
- package/dist/manager-BxQqOPEg.d.mts.map +1 -0
- package/dist/plugin-Bfq-o3nr.d.mts +60 -0
- package/dist/plugin-Bfq-o3nr.d.mts.map +1 -0
- package/dist/plugin-Bt-ygG1m.d.mts +254 -0
- package/dist/plugin-Bt-ygG1m.d.mts.map +1 -0
- package/dist/plugin-CLFwRERa.mjs +593 -0
- package/dist/plugin-CLFwRERa.mjs.map +1 -0
- package/dist/plugin-CP895lBx.mjs +534 -0
- package/dist/plugin-CP895lBx.mjs.map +1 -0
- package/dist/plugin-CaQxviDs.d.mts +61 -0
- package/dist/plugin-CaQxviDs.d.mts.map +1 -0
- package/dist/plugin-lPdJirTY.mjs +234 -0
- package/dist/plugin-lPdJirTY.mjs.map +1 -0
- package/dist/plugins-betterstack-env.d.mts +29 -0
- package/dist/plugins-betterstack-env.d.mts.map +1 -0
- package/dist/plugins-betterstack-env.mjs +75 -0
- package/dist/plugins-betterstack-env.mjs.map +1 -0
- package/dist/plugins-betterstack.d.mts +4 -0
- package/dist/plugins-betterstack.mjs +4 -0
- package/dist/plugins-console.d.mts +37 -0
- package/dist/plugins-console.d.mts.map +1 -0
- package/dist/plugins-console.mjs +196 -0
- package/dist/plugins-console.mjs.map +1 -0
- package/dist/plugins-sentry-env.d.mts +37 -0
- package/dist/plugins-sentry-env.d.mts.map +1 -0
- package/dist/plugins-sentry-env.mjs +79 -0
- package/dist/plugins-sentry-env.mjs.map +1 -0
- package/dist/plugins-sentry-microfrontend-env.d.mts +49 -0
- package/dist/plugins-sentry-microfrontend-env.d.mts.map +1 -0
- package/dist/plugins-sentry-microfrontend-env.mjs +80 -0
- package/dist/plugins-sentry-microfrontend-env.mjs.map +1 -0
- package/dist/plugins-sentry-microfrontend.d.mts +2 -0
- package/dist/plugins-sentry-microfrontend.mjs +3 -0
- package/dist/plugins-sentry.d.mts +5 -0
- package/dist/plugins-sentry.mjs +6 -0
- package/dist/server-edge.d.mts +15 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +53 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +17 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +64 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +11 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +48 -0
- package/dist/server.mjs.map +1 -0
- package/dist/utils-CuGrTcD6.d.mts +77 -0
- package/dist/utils-CuGrTcD6.d.mts.map +1 -0
- package/env.ts +67 -0
- package/package.json +147 -0
- package/src/client-next.ts +131 -0
- package/src/client.ts +70 -0
- package/src/core/index.ts +15 -0
- package/src/core/manager.ts +361 -0
- package/src/core/plugin.ts +61 -0
- package/src/core/types.ts +151 -0
- package/src/factory/builder.ts +132 -0
- package/src/factory/index.ts +67 -0
- package/src/factory/presets.ts +78 -0
- package/src/hooks/useObservability.ts +206 -0
- package/src/plugins/betterstack/env.ts +101 -0
- package/src/plugins/betterstack/index.ts +15 -0
- package/src/plugins/betterstack/plugin.ts +373 -0
- package/src/plugins/console/index.ts +323 -0
- package/src/plugins/sentry/__tests__/plugin-tracing.test.ts +511 -0
- package/src/plugins/sentry/env.ts +93 -0
- package/src/plugins/sentry/index.ts +28 -0
- package/src/plugins/sentry/plugin.ts +953 -0
- package/src/plugins/sentry/types.ts +252 -0
- package/src/plugins/sentry-microfrontend/env.ts +105 -0
- package/src/plugins/sentry-microfrontend/index.ts +12 -0
- package/src/plugins/sentry-microfrontend/multiplexed-transport.ts +221 -0
- package/src/plugins/sentry-microfrontend/plugin.ts +500 -0
- package/src/plugins/sentry-microfrontend/sentry-types.ts +140 -0
- package/src/plugins/sentry-microfrontend/types.ts +130 -0
- package/src/plugins/sentry-microfrontend/utils.ts +326 -0
- package/src/server-edge.ts +113 -0
- package/src/server-next.ts +114 -0
- package/src/server.ts +71 -0
- 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';
|