Unignored dist/ so yarn/npm can install from git URL without needing to build locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
301 lines
9.5 KiB
JavaScript
301 lines
9.5 KiB
JavaScript
import { resolveOptions, generateEventId } from './config';
|
|
import { Transport } from './transport';
|
|
import { OfflineQueue } from './queue';
|
|
import { BreadcrumbManager } from './breadcrumbs';
|
|
import { Journey, JourneyScope, StepScope } from './journey';
|
|
/**
|
|
* Global debug logging flag. When enabled, all IronTelemetry clients
|
|
* will output debug information to the console.
|
|
*/
|
|
export let enableDebugLogging = false;
|
|
/**
|
|
* Enable or disable global debug logging for all TelemetryClient instances.
|
|
* @param enabled Whether to enable debug logging
|
|
*/
|
|
export function setDebugLogging(enabled) {
|
|
enableDebugLogging = enabled;
|
|
}
|
|
/**
|
|
* Main IronTelemetry client class
|
|
*/
|
|
export class TelemetryClient {
|
|
constructor(options) {
|
|
this.tags = {};
|
|
this.extra = {};
|
|
this.isInitialized = false;
|
|
this.options = resolveOptions(options);
|
|
this.transport = new Transport(this.options.parsedDsn, this.options.apiBaseUrl, this.options.debug);
|
|
this.queue = this.options.enableOfflineQueue
|
|
? new OfflineQueue(this.options.maxOfflineQueueSize, this.options.debug)
|
|
: null;
|
|
this.breadcrumbs = new BreadcrumbManager(this.options.maxBreadcrumbs);
|
|
this.isInitialized = true;
|
|
// Start flush interval for offline queue
|
|
if (this.queue) {
|
|
this.flushInterval = setInterval(() => this.processQueue(), 30000);
|
|
}
|
|
if (this.options.debug || enableDebugLogging) {
|
|
console.log('[IronTelemetry] Initialized with DSN:', this.options.dsn);
|
|
}
|
|
}
|
|
/**
|
|
* Capture an exception
|
|
*/
|
|
async captureException(error, extra) {
|
|
const exception = this.parseException(error);
|
|
const event = this.createEvent('error', exception.message, exception);
|
|
if (extra) {
|
|
event.extra = { ...event.extra, ...extra };
|
|
}
|
|
return this.sendEvent(event);
|
|
}
|
|
/**
|
|
* Capture a message
|
|
*/
|
|
async captureMessage(message, level = 'info') {
|
|
const event = this.createEvent(level, message);
|
|
return this.sendEvent(event);
|
|
}
|
|
/**
|
|
* Log a structured message with title, message, and optional data.
|
|
* Useful for structured logging that differentiates the log title from its details.
|
|
* @param level The severity level of the log
|
|
* @param title A short, descriptive title for the log entry
|
|
* @param message Optional detailed message
|
|
* @param data Optional additional data to attach to the log
|
|
*/
|
|
async logMessage(level, title, message, data) {
|
|
const fullMessage = message ? `${title}: ${message}` : title;
|
|
const event = this.createEvent(level, fullMessage);
|
|
if (data) {
|
|
event.extra = { ...event.extra, logTitle: title, logData: data };
|
|
}
|
|
else {
|
|
event.extra = { ...event.extra, logTitle: title };
|
|
}
|
|
return this.sendEvent(event);
|
|
}
|
|
addBreadcrumb(messageOrBreadcrumb, category, level, data) {
|
|
if (typeof messageOrBreadcrumb === 'string') {
|
|
this.breadcrumbs.add(messageOrBreadcrumb, category, level, data);
|
|
}
|
|
else {
|
|
this.breadcrumbs.addBreadcrumb(messageOrBreadcrumb);
|
|
}
|
|
}
|
|
/**
|
|
* Get a copy of the current breadcrumbs list.
|
|
* @returns A read-only array of breadcrumbs
|
|
*/
|
|
getBreadcrumbs() {
|
|
return this.breadcrumbs.getAll();
|
|
}
|
|
/**
|
|
* Clear all breadcrumbs.
|
|
*/
|
|
clearBreadcrumbs() {
|
|
this.breadcrumbs.clear();
|
|
}
|
|
/**
|
|
* Set user context
|
|
*/
|
|
setUser(id, email, data) {
|
|
this.user = { id, email, data };
|
|
}
|
|
/**
|
|
* Clear user context
|
|
*/
|
|
clearUser() {
|
|
this.user = undefined;
|
|
}
|
|
/**
|
|
* Set a tag
|
|
*/
|
|
setTag(key, value) {
|
|
this.tags[key] = value;
|
|
}
|
|
/**
|
|
* Set extra context
|
|
*/
|
|
setExtra(key, value) {
|
|
this.extra[key] = value;
|
|
}
|
|
/**
|
|
* Start a new journey
|
|
*/
|
|
startJourney(name) {
|
|
this.currentJourney = new Journey(name);
|
|
// Copy user context to journey
|
|
if (this.user) {
|
|
this.currentJourney.setUser(this.user.id, this.user.email, this.user.data);
|
|
}
|
|
return new JourneyScope(this.currentJourney, () => {
|
|
this.currentJourney = undefined;
|
|
});
|
|
}
|
|
/**
|
|
* Start a step in the current journey
|
|
*/
|
|
startStep(name, category) {
|
|
if (!this.currentJourney) {
|
|
throw new Error('No active journey. Call startJourney() first.');
|
|
}
|
|
const step = this.currentJourney.startStep(name, category);
|
|
return new StepScope(step);
|
|
}
|
|
/**
|
|
* Flush pending events
|
|
*/
|
|
async flush() {
|
|
await this.processQueue();
|
|
}
|
|
/**
|
|
* Close the client
|
|
*/
|
|
close() {
|
|
if (this.flushInterval) {
|
|
clearInterval(this.flushInterval);
|
|
}
|
|
}
|
|
/**
|
|
* Create a telemetry event
|
|
*/
|
|
createEvent(level, message, exception) {
|
|
const event = {
|
|
eventId: generateEventId(),
|
|
timestamp: new Date(),
|
|
level,
|
|
message,
|
|
exception,
|
|
user: this.currentJourney?.getUser() ?? this.user,
|
|
tags: { ...this.tags },
|
|
extra: { ...this.extra },
|
|
breadcrumbs: this.breadcrumbs.getAll(),
|
|
journey: this.currentJourney?.getContext(),
|
|
environment: this.options.environment,
|
|
appVersion: this.options.appVersion,
|
|
platform: this.getPlatformInfo(),
|
|
};
|
|
return event;
|
|
}
|
|
/**
|
|
* Send an event
|
|
*/
|
|
async sendEvent(event) {
|
|
// Check sample rate
|
|
if (Math.random() > this.options.sampleRate) {
|
|
if (this.options.debug || enableDebugLogging) {
|
|
console.log('[IronTelemetry] Event dropped due to sample rate');
|
|
}
|
|
return { success: true, eventId: event.eventId };
|
|
}
|
|
// Apply beforeSend hook
|
|
const beforeSendResult = this.options.beforeSend(event);
|
|
if (beforeSendResult === false || beforeSendResult === null) {
|
|
if (this.options.debug || enableDebugLogging) {
|
|
console.log('[IronTelemetry] Event dropped by beforeSend hook');
|
|
}
|
|
return { success: true, eventId: event.eventId };
|
|
}
|
|
const eventToSend = beforeSendResult === true ? event : beforeSendResult;
|
|
// Try to send
|
|
const result = await this.transport.send(eventToSend);
|
|
if (!result.success && this.queue) {
|
|
this.queue.enqueue(eventToSend);
|
|
return { ...result, queued: true };
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Process offline queue
|
|
*/
|
|
async processQueue() {
|
|
if (!this.queue || this.queue.isEmpty) {
|
|
return;
|
|
}
|
|
const isOnline = await this.transport.isOnline();
|
|
if (!isOnline) {
|
|
return;
|
|
}
|
|
const events = this.queue.getAll();
|
|
for (const event of events) {
|
|
const result = await this.transport.send(event);
|
|
if (result.success) {
|
|
this.queue.remove(event.eventId);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Parse an error into exception info
|
|
*/
|
|
parseException(error) {
|
|
if (error instanceof Error) {
|
|
return {
|
|
type: error.name || 'Error',
|
|
message: error.message,
|
|
stacktrace: this.parseStackTrace(error.stack),
|
|
};
|
|
}
|
|
return {
|
|
type: 'Error',
|
|
message: String(error),
|
|
};
|
|
}
|
|
/**
|
|
* Parse a stack trace string into frames
|
|
*/
|
|
parseStackTrace(stack) {
|
|
if (!stack)
|
|
return undefined;
|
|
const frames = [];
|
|
const lines = stack.split('\n');
|
|
for (const line of lines) {
|
|
// Chrome/Node format: " at functionName (filename:line:column)"
|
|
const chromeMatch = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
if (chromeMatch) {
|
|
frames.push({
|
|
function: chromeMatch[1] || '<anonymous>',
|
|
filename: chromeMatch[2],
|
|
lineno: parseInt(chromeMatch[3], 10),
|
|
colno: parseInt(chromeMatch[4], 10),
|
|
});
|
|
continue;
|
|
}
|
|
// Firefox format: "functionName@filename:line:column"
|
|
const firefoxMatch = line.match(/^(.*)@(.+?):(\d+):(\d+)$/);
|
|
if (firefoxMatch) {
|
|
frames.push({
|
|
function: firefoxMatch[1] || '<anonymous>',
|
|
filename: firefoxMatch[2],
|
|
lineno: parseInt(firefoxMatch[3], 10),
|
|
colno: parseInt(firefoxMatch[4], 10),
|
|
});
|
|
}
|
|
}
|
|
return frames.length > 0 ? frames : undefined;
|
|
}
|
|
/**
|
|
* Get platform information
|
|
*/
|
|
getPlatformInfo() {
|
|
// Check for browser environment
|
|
if (typeof window !== 'undefined' && typeof navigator !== 'undefined') {
|
|
return {
|
|
name: 'browser',
|
|
userAgent: navigator.userAgent,
|
|
};
|
|
}
|
|
// Check for Node.js environment
|
|
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
return {
|
|
name: 'node',
|
|
version: process.versions.node,
|
|
os: process.platform,
|
|
};
|
|
}
|
|
return {
|
|
name: 'unknown',
|
|
};
|
|
}
|
|
}
|
|
//# sourceMappingURL=client.js.map
|