Skip to content

Instantly share code, notes, and snippets.

@Akiyamka
Last active October 10, 2024 09:03
Show Gist options
  • Save Akiyamka/db722e918e74bd7cb5e1fa3fd32170fc to your computer and use it in GitHub Desktop.
Save Akiyamka/db722e918e74bd7cb5e1fa3fd32170fc to your computer and use it in GitHub Desktop.
This logger sends messages in batches using beacon API.
const logger = new Logger('https://api.example.com/logs');
logger.log('Log message');
logger.info('Info message');
logger.warn('Warn message');
logger.error('Error message');
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { Logger } from './logger'; // Предполагаем, что Logger экспортирован из файла logger.ts
describe('Logger', () => {
let logger: Logger;
let mockBeacon: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.useFakeTimers();
mockBeacon = vi.fn().mockReturnValue(true);
Object.defineProperty(navigator, 'sendBeacon', {
value: mockBeacon,
configurable: true,
});
logger = new Logger('https://api.example.com/logs', 3, 5000);
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
it('should create log entries correctly', () => {
const spy = vi.spyOn(logger as any, 'addToQueue');
logger.log('Test log');
expect(spy).toHaveBeenCalledWith(expect.objectContaining({
level: 'log',
message: 'Test log',
}));
});
it('should flush logs when batch size is reached', () => {
logger.log('Log 1');
logger.log('Log 2');
logger.log('Log 3');
expect(mockBeacon).toHaveBeenCalledTimes(1);
expect(mockBeacon).toHaveBeenCalledWith(
'https://api.example.com/logs',
expect.any(Blob)
);
});
it('should flush logs after max wait time', () => {
logger.log('Log 1');
logger.log('Log 2');
vi.advanceTimersByTime(5000);
expect(mockBeacon).toHaveBeenCalledTimes(1);
});
it('should flush remaining logs on beforeunload', () => {
logger.log('Log 1');
logger.log('Log 2');
window.dispatchEvent(new Event('beforeunload'));
expect(mockBeacon).toHaveBeenCalledTimes(1);
});
it('should not flush if queue is empty', () => {
(logger as any).flush();
expect(mockBeacon).not.toHaveBeenCalled();
});
it('should log to console as well', () => {
const consoleSpy = vi.spyOn(console, 'log');
logger.log('Console log');
expect(consoleSpy).toHaveBeenCalledWith('Console log');
});
});
interface LogEntry {
level: 'log' | 'info' | 'warn' | 'error';
message: string;
timestamp: number;
}
class Logger {
private logQueue: LogEntry[] = [];
private readonly batchSize: number;
private readonly maxWaitTime: number;
private timer: ReturnType<typeof setTimeout> | null = null;
private readonly endpoint: string;
constructor(endpoint: string, batchSize: number = 10, maxWaitTime: number = 5000) {
this.endpoint = endpoint;
this.batchSize = batchSize;
this.maxWaitTime = maxWaitTime;
window.addEventListener('beforeunload', this.flush.bind(this));
}
private createLogEntry(level: LogEntry['level'], message: string): LogEntry {
return {
level,
message,
timestamp: Date.now(),
};
}
private addToQueue(entry: LogEntry): void {
this.logQueue.push(entry);
if (this.logQueue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.maxWaitTime);
}
}
private flush(): void {
if (this.logQueue.length === 0) return;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
const logsToSend = this.logQueue.splice(0, this.batchSize);
const blob = new Blob([JSON.stringify(logsToSend)], { type: 'application/json' });
navigator.sendBeacon(this.endpoint, blob);
}
log(message: string): void {
this.addToQueue(this.createLogEntry('log', message));
console.log(message);
}
info(message: string): void {
this.addToQueue(this.createLogEntry('info', message));
console.info(message);
}
warn(message: string): void {
this.addToQueue(this.createLogEntry('warn', message));
console.warn(message);
}
error(message: string): void {
this.addToQueue(this.createLogEntry('error', message));
console.error(message);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment