All files / logging log-export.service.ts

100% Statements 56/56
100% Branches 25/25
100% Functions 8/8
100% Lines 54/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 1321x   1x 1x 1x           1x 13x 13x 13x 13x             8x 1x     7x 7x 7x 1x 1x       6x     6x   5x 2x 2x       3x     3x   1x 1x               2x 2x 1x               2x 2x 1x   1x         3x 3x     3x   3x 4x               4x     3x                 16x 2x   14x       3x 3x       3x 3x   3x 3x 3x 3x   3x 3x 3x   3x      
import { LogDispatcher } from './log-dispatcher.service';
import { LogEntry } from './log-provider.interface';
import { NotificationService } from '../services/notification.service';
import { isPlatformBrowser } from '@angular/common';
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
 
/**
 * Service for exporting logs as a downloadable CSV file
 */
@Injectable({ providedIn: 'root' })
export class LogExportService {
    private readonly platformId = inject(PLATFORM_ID);
    private readonly isBrowser = isPlatformBrowser(this.platformId);
    private readonly dispatcher = inject(LogDispatcher);
    private readonly notification = inject(NotificationService);
 
    /**
     * Download all logs as a CSV file
     * File format: semicolon-delimited CSV with headers
     */
    async downloadLogs(): Promise<void> {
        if (!this.isBrowser) {
            return;
        }
 
        try {
            const storageProvider = this.dispatcher.getStorageProvider();
            if (!storageProvider) {
                this.notification.info('Хранилище логов недоступно');
                return;
            }
 
            // Flush any buffered logs first
            await this.dispatcher.flush();
 
            // Get all logs
            const logs = await storageProvider.getLogs();
 
            if (logs.length === 0) {
                this.notification.info('Нет логов для экспорта');
                return;
            }
 
            // Generate CSV content
            const csv = this.generateCSV(logs);
 
            // Trigger download
            this.downloadFile(csv, this.generateFilename());
        } catch (error) {
            console.error('LogExportService: Failed to download logs', error);
            this.notification.error('Не удалось скачать логи');
        }
    }
 
    /**
     * Clear all stored logs
     */
    async clearLogs(): Promise<void> {
        const storageProvider = this.dispatcher.getStorageProvider();
        if (storageProvider) {
            await storageProvider.clearLogs();
        }
    }
 
    /**
     * Get current storage size in bytes
     */
    async getStorageSize(): Promise<number> {
        const storageProvider = this.dispatcher.getStorageProvider();
        if (storageProvider) {
            return storageProvider.getStorageSize();
        }
        return 0;
    }
 
    private generateCSV(logs: LogEntry[]): string {
        // CSV header
        const headers = ['timestamp', 'level', 'context', 'message', 'data', 'url'];
        const rows: string[] = [headers.join(';')];
 
        // Add data rows (oldest first for chronological order)
        const sortedLogs = [...logs].reverse();
 
        for (const log of sortedLogs) {
            const row = [
                log.timestamp.toISOString(),
                log.level.toUpperCase(),
                this.escapeCSV(log.context ?? ''),
                this.escapeCSV(log.message),
                this.escapeCSV(log.data !== undefined ? JSON.stringify(log.data) : ''),
                this.escapeCSV(log.url ?? ''),
            ];
            rows.push(row.join(';'));
        }
 
        return rows.join('\n');
    }
 
    /**
     * Escape a value for CSV
     * - Wrap in quotes if contains semicolon, quote, or newline
     * - Double quotes are escaped as ""
     */
    private escapeCSV(value: string): string {
        if (value.includes(';') || value.includes('"') || value.includes('\n')) {
            return `"${value.replace(/"/g, '""')}"`;
        }
        return value;
    }
 
    private generateFilename(): string {
        const date = new Date().toISOString().split('T')[0];
        return `drevo-logs-${date}.csv`;
    }
 
    private downloadFile(content: string, filename: string): void {
        const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
        const url = URL.createObjectURL(blob);
 
        const link = document.createElement('a');
        link.setAttribute('href', url);
        link.setAttribute('download', filename);
        link.style.visibility = 'hidden';
 
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
 
        URL.revokeObjectURL(url);
    }
}