feat: 增加debug参数
This commit is contained in:
parent
fc95159945
commit
e5844bbfe9
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -132,6 +132,7 @@ export interface LightConfig {
|
||||||
release?: string;
|
release?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
debug?: boolean;
|
||||||
sampleRate?: number;
|
sampleRate?: number;
|
||||||
maxQueueSize?: number;
|
maxQueueSize?: number;
|
||||||
flushInterval?: number;
|
flushInterval?: number;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export declare function setDebug(enabled: boolean): void;
|
||||||
|
export declare function isDebugEnabled(): boolean;
|
||||||
|
export declare function logDebug(tag: string, message: string, data?: unknown): void;
|
||||||
|
export declare function logError(tag: string, message: string, error?: unknown): void;
|
||||||
|
|
@ -3,6 +3,7 @@ import { parseDSN } from '../utils/dsn';
|
||||||
import { now } from '../utils/helper';
|
import { now } from '../utils/helper';
|
||||||
import { computeFingerprint } from '../utils/hash';
|
import { computeFingerprint } from '../utils/hash';
|
||||||
import { parseStackTrace } from '../utils/stacktrace';
|
import { parseStackTrace } from '../utils/stacktrace';
|
||||||
|
import { logDebug, setDebug } from '../utils/logger';
|
||||||
import { ConfigManager } from './ConfigManager';
|
import { ConfigManager } from './ConfigManager';
|
||||||
import { EventBus } from './EventBus';
|
import { EventBus } from './EventBus';
|
||||||
import { EventQueue } from './EventQueue';
|
import { EventQueue } from './EventQueue';
|
||||||
|
|
@ -33,6 +34,9 @@ class Client implements LightClient {
|
||||||
throw new Error('DSN is required');
|
throw new Error('DSN is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDebug(config.debug === true);
|
||||||
|
logDebug('Client', 'Initializing SDK', { dsn: config.dsn, debug: config.debug, enabled: config.enabled });
|
||||||
|
|
||||||
this.dsn = parseDSN(config.dsn);
|
this.dsn = parseDSN(config.dsn);
|
||||||
this.configManager = new ConfigManager(config);
|
this.configManager = new ConfigManager(config);
|
||||||
this.eventBus = new EventBus();
|
this.eventBus = new EventBus();
|
||||||
|
|
@ -51,6 +55,7 @@ class Client implements LightClient {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.enabled = this.configManager.get('enabled') !== false;
|
this.enabled = this.configManager.get('enabled') !== false;
|
||||||
|
logDebug('Client', 'SDK initialized', { enabled: this.enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
|
|
@ -60,16 +65,23 @@ class Client implements LightClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async flushEvents(events: SentryEvent[]): Promise<void> {
|
private async flushEvents(events: SentryEvent[]): Promise<void> {
|
||||||
|
logDebug('Client', 'flushEvents start', { eventCount: events.length });
|
||||||
|
|
||||||
const processedEvents: SentryEvent[] = [];
|
const processedEvents: SentryEvent[] = [];
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const processed = this.pluginManager.applyBeforeReport(event);
|
const processed = this.pluginManager.applyBeforeReport(event);
|
||||||
if (processed) {
|
if (processed) {
|
||||||
processedEvents.push(processed);
|
processedEvents.push(processed);
|
||||||
|
} else {
|
||||||
|
logDebug('Client', 'Event filtered by beforeReport plugin', { type: event.type, message: 'message' in event ? event.message : undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedEvents.length === 0) return;
|
if (processedEvents.length === 0) {
|
||||||
|
logDebug('Client', 'flushEvents: all events filtered by beforeReport');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let isFirstInBatch = true;
|
let isFirstInBatch = true;
|
||||||
const finalEvents: SentryEvent[] = [];
|
const finalEvents: SentryEvent[] = [];
|
||||||
|
|
@ -80,14 +92,22 @@ class Client implements LightClient {
|
||||||
|
|
||||||
const beforeSend = this.configManager.get('beforeSend');
|
const beforeSend = this.configManager.get('beforeSend');
|
||||||
const afterBeforeSend = beforeSend ? beforeSend(withConfig) : withConfig;
|
const afterBeforeSend = beforeSend ? beforeSend(withConfig) : withConfig;
|
||||||
if (!afterBeforeSend) continue;
|
if (!afterBeforeSend) {
|
||||||
|
logDebug('Client', 'Event filtered by beforeSend', { type: event.type });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
finalEvents.push(afterBeforeSend);
|
finalEvents.push(afterBeforeSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalEvents.length === 0) return;
|
if (finalEvents.length === 0) {
|
||||||
|
logDebug('Client', 'flushEvents: all events filtered by beforeSend');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug('Client', 'Reporting events', { count: finalEvents.length });
|
||||||
await this.reporter.report(finalEvents);
|
await this.reporter.report(finalEvents);
|
||||||
|
logDebug('Client', 'Report completed successfully');
|
||||||
this.configManager.markContextReported();
|
this.configManager.markContextReported();
|
||||||
|
|
||||||
for (const event of finalEvents) {
|
for (const event of finalEvents) {
|
||||||
|
|
@ -140,12 +160,23 @@ class Client implements LightClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
captureException(error: Error | unknown): void {
|
captureException(error: Error | unknown): void {
|
||||||
if (!this.enabled) return;
|
logDebug('Client', 'captureException called', { error: error instanceof Error ? error.message : String(error) });
|
||||||
|
|
||||||
|
if (!this.enabled) {
|
||||||
|
logDebug('Client', 'captureException skipped: SDK disabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const errorEvent = this.buildErrorEvent(error);
|
const errorEvent = this.buildErrorEvent(error);
|
||||||
if (this.configManager.isIgnoredError(errorEvent.message)) return;
|
logDebug('Client', 'Error event built', { type: errorEvent.type, message: errorEvent.message, fingerprint: (errorEvent as ErrorEvent).fingerprint });
|
||||||
|
|
||||||
|
if (this.configManager.isIgnoredError(errorEvent.message)) {
|
||||||
|
logDebug('Client', 'captureException skipped: error ignored by ignoreErrors');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
errorEvent.breadcrumbs = [...this.breadcrumbs];
|
errorEvent.breadcrumbs = [...this.breadcrumbs];
|
||||||
|
logDebug('Client', 'Enqueue error event', { breadcrumbs: this.breadcrumbs.length });
|
||||||
this.queue.enqueue(errorEvent);
|
this.queue.enqueue(errorEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { SentryEvent, ErrorEvent, CountEvent } from '../types';
|
import type { SentryEvent, ErrorEvent, CountEvent } from '../types';
|
||||||
import { EventBus } from './EventBus';
|
import { EventBus } from './EventBus';
|
||||||
import { now } from '../utils/helper';
|
import { now } from '../utils/helper';
|
||||||
|
import { logDebug } from '../utils/logger';
|
||||||
|
|
||||||
interface DedupeEntry {
|
interface DedupeEntry {
|
||||||
count: number;
|
count: number;
|
||||||
|
|
@ -124,7 +125,12 @@ export class EventQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueue(event: SentryEvent): void {
|
enqueue(event: SentryEvent): void {
|
||||||
if (this.paused) return;
|
logDebug('EventQueue', 'enqueue called', { type: event.type, level: event.level, message: 'message' in event ? (event as { message: string }).message : undefined });
|
||||||
|
|
||||||
|
if (this.paused) {
|
||||||
|
logDebug('EventQueue', 'enqueue skipped: queue paused (infinite loop detected)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.cleanupExpiredDedupeEntries();
|
this.cleanupExpiredDedupeEntries();
|
||||||
|
|
||||||
|
|
@ -132,6 +138,7 @@ export class EventQueue {
|
||||||
|
|
||||||
if (fingerprint) {
|
if (fingerprint) {
|
||||||
if (this.checkInfiniteLoop(fingerprint)) {
|
if (this.checkInfiniteLoop(fingerprint)) {
|
||||||
|
logDebug('EventQueue', 'enqueue skipped: infinite loop detected', { fingerprint });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,23 +155,29 @@ export class EventQueue {
|
||||||
pending.count++;
|
pending.count++;
|
||||||
pending.ts_end = currentTime;
|
pending.ts_end = currentTime;
|
||||||
}
|
}
|
||||||
|
logDebug('EventQueue', 'enqueue skipped: deduped (count >= 3)', { fingerprint, count: existing.count });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
existing.count++;
|
existing.count++;
|
||||||
existing.lastTime = currentTime;
|
existing.lastTime = currentTime;
|
||||||
|
logDebug('EventQueue', 'Dedup count incremented', { fingerprint, count: existing.count });
|
||||||
} else {
|
} else {
|
||||||
this.dedupeMap.set(fingerprint, { count: 1, lastTime: currentTime, firstReported: true });
|
this.dedupeMap.set(fingerprint, { count: 1, lastTime: currentTime, firstReported: true });
|
||||||
this.pendingCounts.delete(fingerprint);
|
this.pendingCounts.delete(fingerprint);
|
||||||
|
logDebug('EventQueue', 'Dedup entry reset (expired)', { fingerprint });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.dedupeMap.set(fingerprint, { count: 1, lastTime: currentTime, firstReported: false });
|
this.dedupeMap.set(fingerprint, { count: 1, lastTime: currentTime, firstReported: false });
|
||||||
|
logDebug('EventQueue', 'New dedup entry created', { fingerprint });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.push(event);
|
this.queue.push(event);
|
||||||
|
logDebug('EventQueue', 'Event added to queue', { queueSize: this.queue.length });
|
||||||
this.eventBus.emit('event', event);
|
this.eventBus.emit('event', event);
|
||||||
|
|
||||||
if (this.queue.length >= this.maxSize) {
|
if (this.queue.length >= this.maxSize) {
|
||||||
|
logDebug('EventQueue', 'Queue reached maxSize, triggering flush', { maxSize: this.maxSize });
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,8 +224,16 @@ export class EventQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
async flush(): Promise<void> {
|
async flush(): Promise<void> {
|
||||||
if (this.queue.length === 0 && this.pendingCounts.size === 0) return;
|
logDebug('EventQueue', 'flush called', { queueSize: this.queue.length, pendingCounts: this.pendingCounts.size, isFlushing: this.isFlushing });
|
||||||
if (this.isFlushing) return;
|
|
||||||
|
if (this.queue.length === 0 && this.pendingCounts.size === 0) {
|
||||||
|
logDebug('EventQueue', 'flush skipped: queue is empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isFlushing) {
|
||||||
|
logDebug('EventQueue', 'flush skipped: already flushing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.isFlushing = true;
|
this.isFlushing = true;
|
||||||
|
|
||||||
|
|
@ -220,14 +241,18 @@ export class EventQueue {
|
||||||
const countEvents = this.buildCountEvents();
|
const countEvents = this.buildCountEvents();
|
||||||
const allEvents = [...events, ...countEvents];
|
const allEvents = [...events, ...countEvents];
|
||||||
|
|
||||||
|
logDebug('EventQueue', 'flush events to callback', { total: allEvents.length, events: events.length, countEvents: countEvents.length });
|
||||||
|
|
||||||
this.lastFlushTime = now();
|
this.lastFlushTime = now();
|
||||||
this.eventBus.emit('report', allEvents);
|
this.eventBus.emit('report', allEvents);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.flushCallback(allEvents);
|
await this.flushCallback(allEvents);
|
||||||
this.pendingCounts.clear();
|
this.pendingCounts.clear();
|
||||||
|
logDebug('EventQueue', 'flush completed successfully');
|
||||||
this.eventBus.emit('reported', allEvents);
|
this.eventBus.emit('reported', allEvents);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logDebug('EventQueue', 'flush failed, returning events to queue', { error: e instanceof Error ? e.message : String(e) });
|
||||||
events.forEach(evt => this.queue.unshift(evt));
|
events.forEach(evt => this.queue.unshift(evt));
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { DSNInfo, SentryEvent, ErrorEvent } from '../types';
|
||||||
import { getEnvelopeUrl } from '../utils/dsn';
|
import { getEnvelopeUrl } from '../utils/dsn';
|
||||||
import { now, generateEventId } from '../utils/helper';
|
import { now, generateEventId } from '../utils/helper';
|
||||||
import { encodeFields, FIELD_ENCODE_MAP } from '../utils/field-encoder';
|
import { encodeFields, FIELD_ENCODE_MAP } from '../utils/field-encoder';
|
||||||
|
import { logDebug, logError } from '../utils/logger';
|
||||||
|
|
||||||
export class Reporter {
|
export class Reporter {
|
||||||
private dsn: DSNInfo;
|
private dsn: DSNInfo;
|
||||||
|
|
@ -16,25 +17,37 @@ export class Reporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async report(events: SentryEvent[]): Promise<void> {
|
async report(events: SentryEvent[]): Promise<void> {
|
||||||
if (events.length === 0) return;
|
logDebug('Reporter', 'report called', { eventCount: events.length });
|
||||||
|
|
||||||
|
if (events.length === 0) {
|
||||||
|
logDebug('Reporter', 'report skipped: empty events');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const envelope = this.buildEnvelope(events);
|
const envelope = this.buildEnvelope(events);
|
||||||
|
const url = getEnvelopeUrl(this.dsn);
|
||||||
|
logDebug('Reporter', 'Envelope built', { envelopeSize: envelope.length, url });
|
||||||
|
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
while (retries <= this.maxRetries) {
|
while (retries <= this.maxRetries) {
|
||||||
try {
|
try {
|
||||||
|
logDebug('Reporter', `Sending request (attempt ${retries + 1}/${this.maxRetries + 1})`);
|
||||||
await this.send(envelope, false);
|
await this.send(envelope, false);
|
||||||
|
logDebug('Reporter', 'Request succeeded');
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const status = (e as { status?: number }).status;
|
const status = (e as { status?: number }).status;
|
||||||
|
logError('Reporter', `Request failed (attempt ${retries + 1})`, { status, error: e });
|
||||||
if (status && status >= 400 && status < 500) {
|
if (status && status >= 400 && status < 500) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
retries++;
|
retries++;
|
||||||
if (retries > this.maxRetries) {
|
if (retries > this.maxRetries) {
|
||||||
|
logError('Reporter', 'All retries exhausted');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
const backoff = Math.min(this.retryDelay * Math.pow(2, retries - 1), 5000);
|
const backoff = Math.min(this.retryDelay * Math.pow(2, retries - 1), 5000);
|
||||||
|
logDebug('Reporter', `Retrying in ${backoff}ms`);
|
||||||
await this.delay(backoff);
|
await this.delay(backoff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { LightPlugin, LightClient, SentryEvent, LightConfig } from '../types';
|
import type { LightPlugin, LightClient, SentryEvent, LightConfig } from '../types';
|
||||||
import { isObject } from '../utils/helper';
|
import { isObject } from '../utils/helper';
|
||||||
|
import { logDebug } from '../utils/logger';
|
||||||
|
|
||||||
interface SampleRateConfig {
|
interface SampleRateConfig {
|
||||||
error?: number;
|
error?: number;
|
||||||
|
|
@ -73,7 +74,13 @@ class SamplingPlugin implements LightPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeReport(event: SentryEvent): SentryEvent | null {
|
beforeReport(event: SentryEvent): SentryEvent | null {
|
||||||
if (!this.shouldSample(event)) {
|
const sampled = this.shouldSample(event);
|
||||||
|
if (!sampled) {
|
||||||
|
logDebug('SamplingPlugin', 'Event dropped by sampling', {
|
||||||
|
type: event.type,
|
||||||
|
globalRate: (this.client.config as LightConfig).sampleRate ?? 1,
|
||||||
|
typeRate: this.getTypeRate(event),
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ export interface LightConfig {
|
||||||
release?: string;
|
release?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
debug?: boolean;
|
||||||
sampleRate?: number;
|
sampleRate?: number;
|
||||||
maxQueueSize?: number;
|
maxQueueSize?: number;
|
||||||
flushInterval?: number;
|
flushInterval?: number;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
let debugEnabled = false;
|
||||||
|
|
||||||
|
export function setDebug(enabled: boolean): void {
|
||||||
|
debugEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDebugEnabled(): boolean {
|
||||||
|
return debugEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logDebug(tag: string, message: string, data?: unknown): void {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const prefix = `[LightSDK][${tag}]`;
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.log(`${prefix} ${timestamp} ${message}`, data);
|
||||||
|
} else {
|
||||||
|
console.log(`${prefix} ${timestamp} ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logError(tag: string, message: string, error?: unknown): void {
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const prefix = `[LightSDK][${tag}][ERROR]`;
|
||||||
|
if (error !== undefined) {
|
||||||
|
console.error(`${prefix} ${timestamp} ${message}`, error);
|
||||||
|
} else {
|
||||||
|
console.error(`${prefix} ${timestamp} ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue