"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TelemetryClient = exports.enableDebugLogging = void 0; exports.setDebugLogging = setDebugLogging; const config_1 = require("./config"); const transport_1 = require("./transport"); const queue_1 = require("./queue"); const breadcrumbs_1 = require("./breadcrumbs"); const journey_1 = require("./journey"); /** * Global debug logging flag. When enabled, all IronTelemetry clients * will output debug information to the console. */ exports.enableDebugLogging = false; /** * Enable or disable global debug logging for all TelemetryClient instances. * @param enabled Whether to enable debug logging */ function setDebugLogging(enabled) { exports.enableDebugLogging = enabled; } /** * Main IronTelemetry client class */ class TelemetryClient { constructor(options) { this.tags = {}; this.extra = {}; this.isInitialized = false; this.options = (0, config_1.resolveOptions)(options); this.transport = new transport_1.Transport(this.options.parsedDsn, this.options.apiBaseUrl, this.options.debug); this.queue = this.options.enableOfflineQueue ? new queue_1.OfflineQueue(this.options.maxOfflineQueueSize, this.options.debug) : null; this.breadcrumbs = new breadcrumbs_1.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 || exports.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_1.Journey(name); // Copy user context to journey if (this.user) { this.currentJourney.setUser(this.user.id, this.user.email, this.user.data); } return new journey_1.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 journey_1.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: (0, config_1.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 || exports.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 || exports.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] || '', 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] || '', 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', }; } } exports.TelemetryClient = TelemetryClient; //# sourceMappingURL=client.js.map