• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dev-ignis / cross-log / 16982792483

15 Aug 2025 04:15AM UTC coverage: 85.706% (-9.9%) from 95.62%
16982792483

push

github

dev-ignis
test: - ci;

489 of 647 branches covered (75.58%)

Branch coverage included in aggregate %.

944 of 1025 relevant lines covered (92.1%)

357.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.45
/src/loggers/browser.ts
1
/**
2
 * Browser-specific logger implementation
3
 */
4

5
import { BaseLogger } from './base';
27✔
6
import { LogLevel, LogEntry, PartialLoggerConfig } from '../core/types';
27✔
7

8
// Window interface extension for TypeScript
9
interface WindowWithLogger {
10
  [key: string]: unknown;
11
}
12

13
export class BrowserLogger extends BaseLogger {
27✔
14
  private storageAvailable: boolean;
15
  private originalConsole: {
16
    debug: typeof console.debug;
17
    info: typeof console.info;
18
    warn: typeof console.warn;
19
    error: typeof console.error;
20
    log: typeof console.log;
21
  };
22

23
  constructor(initialConfig?: PartialLoggerConfig) {
24
    super(initialConfig);
147✔
25

26
    // Store references to original console methods to prevent infinite recursion
27
    this.originalConsole = {
147✔
28
      debug: console.debug.bind(console),
29
      info: console.info.bind(console),
30
      warn: console.warn.bind(console),
31
      error: console.error.bind(console),
32
      log: console.log.bind(console)
33
    };
34

35
    this.storageAvailable = this.checkStorageAvailability();
147✔
36
    this.loadConfigFromStorage();
147✔
37
    this.setupBrowserControls();
147✔
38
  }
39

40
  /**
41
   * Output log with browser-specific styling
42
   */
43
  protected outputLog(
44
    level: LogLevel,
45
    formattedMessage: string,
46
    _logEntry: LogEntry,
47
    ...args: unknown[]
48
  ): void {
49
    const config = this.configManager.getConfig();
60✔
50

51
    // Get the original console method to avoid infinite recursion
52
    // when logger methods are used to replace console methods
53
    const originalConsole = this.getOriginalConsoleMethod(level);
60✔
54

55
    if (config.colors.enabled) {
60✔
56
      const color = this.getColorForLevel(level);
48✔
57
      const style = `color: ${color}; font-weight: bold`;
48✔
58
      originalConsole(`%c${formattedMessage}`, style, ...args);
48✔
59
    } else {
60
      originalConsole(formattedMessage, ...args);
12✔
61
    }
62
  }
63

64
  /**
65
   * Output stack trace for errors
66
   */
67
  protected outputStackTrace(error: Error): void {
68
    if (error.stack) {
9✔
69
      // Use original console.error to avoid infinite recursion
70
      const originalError = this.getOriginalConsoleMethod(LogLevel.ERROR);
6✔
71
      originalError(error.stack);
6✔
72
    }
73
  }
74

75
  /**
76
   * Override configure to save to storage
77
   */
78
  configure(newConfig: PartialLoggerConfig): void {
79
    super.configure(newConfig);
15✔
80
    this.saveConfigToStorage();
15✔
81
  }
82

83
  /**
84
   * Override setLevel to save to storage
85
   */
86
  setLevel(level: LogLevel): void {
87
    super.setLevel(level);
6✔
88
    this.saveConfigToStorage();
6✔
89
  }
90

91
  /**
92
   * Override enableCategory to save to storage
93
   */
94
  enableCategory(category: string, minLevel: LogLevel = LogLevel.DEBUG): void {
3✔
95
    super.enableCategory(category, minLevel);
9✔
96
    this.saveConfigToStorage();
9✔
97
  }
98

99
  /**
100
   * Override disableCategory to save to storage
101
   */
102
  disableCategory(category: string): void {
103
    super.disableCategory(category);
6✔
104
    this.saveConfigToStorage();
6✔
105
  }
106

107
  /**
108
   * Override enableAll to save to storage
109
   */
110
  enableAll(): void {
111
    super.enableAll();
6✔
112
    this.saveConfigToStorage();
6✔
113
  }
114

115
  /**
116
   * Override disableAll to save to storage
117
   */
118
  disableAll(): void {
119
    super.disableAll();
6✔
120
    this.saveConfigToStorage();
6✔
121
  }
122

123
  /**
124
   * Get original console method to avoid infinite recursion
125
   */
126
  private getOriginalConsoleMethod(level: LogLevel): (message?: unknown, ...optionalParams: unknown[]) => void {
127
    switch (level) {
66!
128
      case LogLevel.DEBUG:
129
        return this.originalConsole.debug;
6✔
130
      case LogLevel.INFO:
131
        return this.originalConsole.info;
24✔
132
      case LogLevel.WARN:
133
        return this.originalConsole.warn;
9✔
134
      case LogLevel.ERROR:
135
        return this.originalConsole.error;
27✔
136
      default:
137
        return this.originalConsole.log;
×
138
    }
139
  }
140

141
  /**
142
   * Get color for log level
143
   */
144
  private getColorForLevel(level: LogLevel): string {
145
    const config = this.configManager.getConfig();
48✔
146
    const colors = config.colors.browser;
48✔
147

148
    switch (level) {
48!
149
      case LogLevel.DEBUG:
150
        return colors.debug;
6✔
151
      case LogLevel.INFO:
152
        return colors.info;
12✔
153
      case LogLevel.WARN:
154
        return colors.warn;
9✔
155
      case LogLevel.ERROR:
156
        return colors.error;
21✔
157
      default:
158
        return colors.info;
×
159
    }
160
  }
161

162
  /**
163
   * Check if localStorage is available
164
   */
165
  private checkStorageAvailability(): boolean {
166
    try {
147✔
167
      if (typeof window === 'undefined' || !window.localStorage) {
147✔
168
        return false;
15✔
169
      }
170
      
171
      const testKey = '__logger_test__';
129✔
172
      window.localStorage.setItem(testKey, 'test');
129✔
173
      window.localStorage.removeItem(testKey);
87✔
174
      return true;
87✔
175
    } catch {
176
      return false;
45✔
177
    }
178
  }
179

180
  /**
181
   * Load configuration from localStorage
182
   */
183
  private loadConfigFromStorage(): void {
184
    if (!this.storageAvailable) return;
147✔
185

186
    const config = this.configManager.getConfig();
87✔
187
    if (!config.storage.enabled) return;
87✔
188

189
    try {
81✔
190
      const configKey = `${config.storage.keyPrefix}_config`;
81✔
191
      const enabledKey = `${config.storage.keyPrefix}_enabled`;
81✔
192

193
      const savedConfig = localStorage.getItem(configKey);
81✔
194
      const savedEnabled = localStorage.getItem(enabledKey);
78✔
195

196
      if (savedConfig) {
78✔
197
        const parsedConfig = JSON.parse(savedConfig);
6✔
198
        this.configManager.updateConfig(parsedConfig);
6✔
199
      }
200

201
      if (savedEnabled !== null) {
78✔
202
        const enabled = savedEnabled === 'true';
6✔
203
        this.configManager.updateConfig({ enabled });
6✔
204
      }
205
    } catch (error) {
206
      console.error('Error loading logger config from localStorage:', error);
3✔
207
    }
208
  }
209

210
  /**
211
   * Save configuration to localStorage
212
   */
213
  private saveConfigToStorage(): void {
214
    if (!this.storageAvailable) return;
48✔
215

216
    const config = this.configManager.getConfig();
36✔
217
    if (!config.storage.enabled) return;
36✔
218

219
    try {
30✔
220
      const configKey = `${config.storage.keyPrefix}_config`;
30✔
221
      const enabledKey = `${config.storage.keyPrefix}_enabled`;
30✔
222

223
      localStorage.setItem(configKey, JSON.stringify(config));
30✔
224
      localStorage.setItem(enabledKey, config.enabled.toString());
27✔
225
    } catch (error) {
226
      console.error('Error saving logger config to localStorage:', error);
3✔
227
    }
228
  }
229

230
  /**
231
   * Setup browser console controls
232
   */
233
  private setupBrowserControls(): void {
234
    const config = this.configManager.getConfig();
147✔
235
    if (!config.browserControls.enabled || typeof window === 'undefined') {
147✔
236
      return;
30✔
237
    }
238

239
    const win = window as unknown as WindowWithLogger;
117✔
240
    const namespace = config.browserControls.windowNamespace;
117✔
241

242
    // Expose the logger instance
243
    win[namespace] = this;
117✔
244

245
    // Helper functions
246
    const enableFuncName = `enable${this.capitalizeFirst(namespace.replace('__', ''))}Logging`;
117✔
247
    const disableFuncName = `disable${this.capitalizeFirst(namespace.replace('__', ''))}Logging`;
117✔
248
    const statusFuncName = `${namespace.replace('__', '')}LoggingStatus`;
117✔
249

250
    win[enableFuncName] = () => {
117✔
251
      this.enableAll();
3✔
252
      console.log(
3✔
253
        `%cLogging enabled!`,
254
        `color: ${config.colors.browser.info}; font-size: 14px; font-weight: bold`
255
      );
256
      return `Logging enabled. Use ${namespace} to access the logger API.`;
3✔
257
    };
258

259
    win[disableFuncName] = () => {
117✔
260
      this.disableAll();
3✔
261
      console.log(
3✔
262
        `%cLogging disabled!`,
263
        `color: ${config.colors.browser.warn}; font-size: 14px; font-weight: bold`
264
      );
265
      return 'Logging disabled.';
3✔
266
    };
267

268
    win[statusFuncName] = () => {
117✔
269
      const currentConfig = this.getConfig();
3✔
270
      const status = currentConfig.enabled ? 'enabled' : 'disabled';
3!
271
      console.log(
3✔
272
        `%cLogging is currently ${status}`,
273
        `color: ${config.colors.browser.info}; font-size: 14px;`
274
      );
275

276
      if (currentConfig.enabled) {
3✔
277
        console.log('Current configuration:', currentConfig);
3✔
278
      }
279

280
      return {
3✔
281
        enabled: currentConfig.enabled,
282
        config: currentConfig
283
      };
284
    };
285
  }
286

287
  /**
288
   * Capitalize first letter of string
289
   */
290
  private capitalizeFirst(str: string): string {
291
    return str.charAt(0).toUpperCase() + str.slice(1);
234✔
292
  }
293
}
294

295
/**
296
 * Factory function to create a browser logger instance
297
 */
298
export function createLogger(config?: PartialLoggerConfig): BrowserLogger {
27✔
299
  return new BrowserLogger(config);
×
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc