Skip to content

Commit 99bbc51

Browse files
committed
refactor(core): Logger abstraction added
1 parent eacefe1 commit 99bbc51

13 files changed

Lines changed: 214 additions & 63 deletions

File tree

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { HawkLocalStorage } from './storages/hawk-local-storage';
2+
export { createBrowserLogger } from './utils/logger';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Logger, LogType } from '@hawk.so/core';
2+
3+
/**
4+
* Creates a browser console logger with Hawk branding and styled output.
5+
*
6+
* The logger outputs to `window.console` with a dark label badge
7+
* containing the Hawk version. Messages are formatted with CSS
8+
* styling for better visibility in browser developer tools.
9+
*
10+
* @param version - Version string to display in log messages.
11+
* @param style - Optional CSS style for the message text (default: 'color: inherit').
12+
* @returns {Logger} Logger function implementation for browser environments.
13+
*
14+
* @example
15+
* ```TypeScript
16+
* import { createBrowserLogger } from '@hawk.so/browser';
17+
* import { setLogger } from '@hawk.so/core';
18+
*
19+
* const logger = createBrowserLogger('3.2.0');
20+
* setLogger(logger);
21+
*
22+
* // Custom styling
23+
* const styledLogger = createBrowserLogger('3.2.0', 'color: blue; font-weight: bold');
24+
* ```
25+
*/
26+
export function createBrowserLogger(version: string, style = 'color: inherit'): Logger {
27+
return (msg: string, type: LogType = 'log', args?: unknown): void => {
28+
if (!('console' in window)) {
29+
return;
30+
}
31+
32+
const editorLabelText = `Hawk (${version})`;
33+
const editorLabelStyle = `line-height: 1em;
34+
color: #fff;
35+
display: inline-block;
36+
line-height: 1em;
37+
background-color: rgba(0,0,0,.7);
38+
padding: 3px 5px;
39+
border-radius: 3px;
40+
margin-right: 2px`;
41+
42+
try {
43+
switch (type) {
44+
case 'time':
45+
case 'timeEnd':
46+
console[type](`( ${editorLabelText} ) ${msg}`);
47+
break;
48+
case 'log':
49+
case 'warn':
50+
case 'error':
51+
case 'info':
52+
if (args !== undefined) {
53+
console[type](`%c${editorLabelText}%c ${msg} %o`, editorLabelStyle, style, args);
54+
} else {
55+
console[type](`%c${editorLabelText}%c ${msg}`, editorLabelStyle, style);
56+
}
57+
break;
58+
}
59+
} catch (ignored) {}
60+
};
61+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
2+
import { createBrowserLogger } from '../src';
3+
4+
describe('createBrowserLogger', () => {
5+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
6+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
7+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
8+
9+
beforeEach(() => {
10+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
11+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
12+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
13+
});
14+
15+
afterEach(() => {
16+
vi.restoreAllMocks();
17+
});
18+
19+
it('should log message with default type', () => {
20+
const logger = createBrowserLogger('1.0.0');
21+
22+
logger('Test message');
23+
24+
expect(consoleLogSpy).toHaveBeenCalledWith(
25+
'%cHawk (1.0.0)%c Test message',
26+
expect.stringContaining('background-color'),
27+
'color: inherit'
28+
);
29+
});
30+
31+
it('should log message with specified type', () => {
32+
const logger = createBrowserLogger('2.0.0');
33+
34+
logger('Warning message', 'warn');
35+
36+
expect(consoleWarnSpy).toHaveBeenCalledWith(
37+
'%cHawk (2.0.0)%c Warning message',
38+
expect.stringContaining('background-color'),
39+
'color: inherit'
40+
);
41+
});
42+
43+
it('should log error with args', () => {
44+
const logger = createBrowserLogger('3.0.0');
45+
const errorObj = new Error('Test error');
46+
47+
logger('Error occurred', 'error', errorObj);
48+
49+
expect(consoleErrorSpy).toHaveBeenCalledWith(
50+
'%cHawk (3.0.0)%c Error occurred %o',
51+
expect.stringContaining('background-color'),
52+
'color: inherit',
53+
errorObj
54+
);
55+
});
56+
57+
it('should handle time/timeEnd types', () => {
58+
const consoleTimeSpy = vi.spyOn(console, 'time').mockImplementation(() => {});
59+
const logger = createBrowserLogger('4.0.0');
60+
61+
logger('Timer started', 'time');
62+
63+
expect(consoleTimeSpy).toHaveBeenCalledWith(
64+
expect.stringContaining('Hawk (4.0.0)')
65+
);
66+
67+
consoleTimeSpy.mockRestore();
68+
});
69+
70+
it('should not throw when console method is unavailable', () => {
71+
const logger = createBrowserLogger('5.0.0');
72+
73+
expect(() => {
74+
// @ts-expect-error - testing invalid type
75+
logger('Test', 'invalidType');
76+
}).not.toThrow();
77+
});
78+
});

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export type { HawkStorage } from './storages/hawk-storage';
22
export type { UserManager } from './users/user-manager';
33
export { StorageUserManager } from './users/storage-user-manager';
4+
export type { Logger, LogType } from './utils/logger';
5+
export { setLogger, log } from './utils/logger';

packages/core/src/utils/logger.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Log level type for categorizing log messages.
3+
*
4+
* Includes standard console methods supported in both browser and Node.js:
5+
* - Standard levels: `log`, `warn`, `error`, `info`
6+
* - Performance timing: `time`, `timeEnd`
7+
*/
8+
export type LogType = 'log' | 'warn' | 'error' | 'info' | 'time' | 'timeEnd';
9+
10+
/**
11+
* Logger function interface for environment-specific logging implementations.
12+
*
13+
* Implementations should handle message formatting, output styling,
14+
* and platform-specific logging mechanisms (e.g., console, file, network).
15+
*
16+
* @param msg - The message to log.
17+
* @param type - Log level/severity (default: 'log').
18+
* @param args - Additional data to include with the log message.
19+
*/
20+
export interface Logger {
21+
(msg: string, type?: LogType, args?: unknown): void;
22+
}
23+
24+
/**
25+
* Global logger instance, set by environment-specific packages.
26+
*/
27+
let loggerInstance: Logger | null = null;
28+
29+
/**
30+
* Registers the environment-specific logger implementation.
31+
*
32+
* This should be called once during application initialization
33+
* by the environment-specific package.
34+
*
35+
* @param logger - Logger implementation to use globally.
36+
*/
37+
export function setLogger(logger: Logger): void {
38+
loggerInstance = logger;
39+
}
40+
41+
/**
42+
* Logs a message using the registered logger implementation.
43+
*
44+
* If no logger has been registered via {@link setLogger}, this is a no-op.
45+
*
46+
* @param msg - Message to log.
47+
* @param type - Log level (default: 'log').
48+
* @param args - Additional arguments to log.
49+
*/
50+
export function log(msg: string, type?: LogType, args?: unknown): void {
51+
if (loggerInstance) {
52+
loggerInstance(msg, type, args);
53+
}
54+
}

packages/javascript/src/addons/breadcrumbs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { Breadcrumb, BreadcrumbLevel, BreadcrumbType, Json, JsonNode } from '@hawk.so/types';
55
import Sanitizer from '../modules/sanitizer';
66
import { buildElementSelector } from '../utils/selector';
7-
import log from '../utils/log';
7+
import { log } from '@hawk.so/core';
88
import { isValidBreadcrumb } from '../utils/validation';
99

1010
/**

packages/javascript/src/catcher.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Socket from './modules/socket';
22
import Sanitizer from './modules/sanitizer';
3-
import log from './utils/log';
43
import StackParser from './modules/stackParser';
54
import type { CatcherMessage, HawkInitialSettings, BreadcrumbsAPI, Transport } from './types';
65
import { VueIntegration } from './integrations/vue';
@@ -19,8 +18,8 @@ import { ConsoleCatcher } from './addons/consoleCatcher';
1918
import { BreadcrumbManager } from './addons/breadcrumbs';
2019
import { validateUser, validateContext, isValidEventPayload } from './utils/validation';
2120
import type { UserManager } from '@hawk.so/core';
22-
import { StorageUserManager } from '@hawk.so/core';
23-
import { HawkLocalStorage } from '@hawk.so/browser';
21+
import { StorageUserManager, setLogger, log } from '@hawk.so/core';
22+
import { HawkLocalStorage, createBrowserLogger } from '@hawk.so/browser';
2423
import { id } from './utils/id';
2524

2625
/**
@@ -120,6 +119,8 @@ export default class Catcher {
120119
* @param {HawkInitialSettings|string} settings - If settings is a string, it means an Integration Token
121120
*/
122121
constructor(settings: HawkInitialSettings | string) {
122+
setLogger(createBrowserLogger(VERSION));
123+
123124
if (typeof settings === 'string') {
124125
settings = {
125126
token: settings,

packages/javascript/src/modules/fetchTimer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import log from '../utils/log';
1+
import { log } from '@hawk.so/core';
22

33
/**
44
* Sends AJAX request and wait for some time.

packages/javascript/src/modules/socket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import log from '../utils/log';
1+
import { log } from '@hawk.so/core';
22
import type { CatcherMessage } from '@/types';
33
import type { Transport } from '../types/transport';
44

packages/javascript/src/utils/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import log from './log';
1+
import { log } from '@hawk.so/core';
22

33
/**
44
* Symbol to mark error as processed by Hawk

0 commit comments

Comments
 (0)