Skip to content

Commit c214b7f

Browse files
committed
feat: implement logger and assertion functions for improved error handling
1 parent ca83c0d commit c214b7f

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed

src/system/assert.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function assert(condition, message, context = "", data = null) {
2+
if (!condition) {
3+
globalThis.logger?.error(message, context, data);
4+
return false;
5+
}
6+
return true;
7+
}

src/system/logger.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* logger.error('error message');
3+
* logger.warn('warn message');
4+
* logger.info('info message');
5+
* logger.debug('debug message');
6+
*
7+
* @param {string} message - required
8+
* @param {context} string - optional default to ""
9+
* @param {data} any - optional default to null
10+
*
11+
* Date is set to current date and time
12+
*/
13+
14+
const LOG_LEVELS = {
15+
ERROR: 'error',
16+
WARN: 'warn',
17+
INFO: 'info',
18+
DEBUG: 'debug'
19+
}
20+
21+
const LOG_STRUCT = {
22+
message: "",
23+
context: "",
24+
data: null,
25+
date: null
26+
}
27+
28+
/**
29+
* @class Logger
30+
* @description Logger class to log messages
31+
* Messages are stored in a log array
32+
*/
33+
class Logger extends EventTarget {
34+
#items = [];
35+
36+
/**
37+
* Internal method to handle log creation.
38+
* @param {string} level - Log level (ERROR, WARN, INFO, DEBUG)
39+
* @param {string} message - Main log message
40+
* @param {string} context - Additional context info
41+
* @param {any} data - Optional data payload
42+
*/
43+
#log(level, message = "", context = "", data = null) {
44+
const entry = structuredClone(LOG_STRUCT);
45+
entry.message = message;
46+
entry.context = context;
47+
entry.data = data;
48+
entry.date = new Date().toISOString();
49+
entry.level = level;
50+
this.#items.push(entry);
51+
}
52+
53+
/**
54+
* Returns all log entries.
55+
* @returns {Array}
56+
*/
57+
get logs() {
58+
return this.#items;
59+
}
60+
61+
/**
62+
* Returns all error log entries.
63+
* @returns {Array}
64+
*/
65+
get errors() {
66+
return this.#items.filter(item => item.level === LOG_LEVELS.ERROR);
67+
}
68+
69+
/**
70+
* Logs an error message.
71+
* @param {string} message
72+
* @param {string} context
73+
* @param {any} data
74+
*/
75+
error(message, context, data) {
76+
this.#log(LOG_LEVELS.ERROR, message, context, data);
77+
this.dispatchEvent(new CustomEvent('error', { detail: { message, context, data } }));
78+
}
79+
80+
/**
81+
* Logs a warning message.
82+
* @param {string} message
83+
* @param {string} context
84+
* @param {any} data
85+
*/
86+
warn(message, context, data) {
87+
this.#log(LOG_LEVELS.WARN, message, context, data);
88+
}
89+
90+
/**
91+
* Logs an informational message.
92+
* @param {string} message
93+
* @param {string} context
94+
* @param {any} data
95+
*/
96+
info(message, context, data) {
97+
this.#log(LOG_LEVELS.INFO, message, context, data);
98+
}
99+
100+
/**
101+
* Logs a debug message.
102+
* @param {string} message
103+
* @param {string} context
104+
* @param {any} data
105+
*/
106+
debug(message, context, data) {
107+
this.#log(LOG_LEVELS.DEBUG, message, context, data);
108+
}
109+
110+
/**
111+
* Clears all log entries
112+
* @returns {void}
113+
*/
114+
clear() {
115+
this.#items = [];
116+
}
117+
118+
/**
119+
* Clears all error log entries
120+
* @returns {void}
121+
*/
122+
clearErrors() {
123+
this.#items = this.#items.filter(item => item.level !== LOG_LEVELS.ERROR);
124+
}
125+
126+
/**
127+
* Clears all log entries with the specified context
128+
* @param {string} context
129+
* @returns {void}
130+
*/
131+
clearContext(context) {
132+
this.#items = this.#items.filter(item => item.context !== context);
133+
}
134+
135+
/**
136+
* Clears all log entries with the specified context and data
137+
* @param {string} context
138+
* @param {any} data
139+
* @returns {void}
140+
*/
141+
clearData(context, data) {
142+
this.#items = this.#items.filter(item => item.context !== context && item.data !== data);
143+
}
144+
}
145+
146+
globalThis.logger = new Logger();

tests/src/system/assert.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { assert as customAssert } from '../../../src/system/assert.js';
2+
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
3+
4+
Deno.test('should return true when condition is true', () => {
5+
const logger = {
6+
error: () => {}
7+
};
8+
globalThis.logger = logger;
9+
const result = customAssert(true, 'This should not log an error');
10+
assertEquals(result, true);
11+
delete globalThis.logger;
12+
});
13+
14+
Deno.test('should return false and log error when condition is false', () => {
15+
const logger = {
16+
error: (message, context, data) => {
17+
assertEquals(message, 'This should log an error');
18+
assertEquals(context, 'context');
19+
assertEquals(data, { key: 'value' });
20+
}
21+
};
22+
globalThis.logger = logger;
23+
const result = customAssert(false, 'This should log an error', 'context', { key: 'value' });
24+
assertEquals(result, false);
25+
delete globalThis.logger;
26+
});
27+
28+
Deno.test('should return false and log error with default context and data', () => {
29+
const logger = {
30+
error: (message, context, data) => {
31+
assertEquals(message, 'This should log an error');
32+
assertEquals(context, '');
33+
assertEquals(data, null);
34+
}
35+
};
36+
globalThis.logger = logger;
37+
const result = customAssert(false, 'This should log an error');
38+
assertEquals(result, false);
39+
delete globalThis.logger;
40+
});
41+
42+
Deno.test('should not throw error if logger is not defined', () => {
43+
delete globalThis.logger;
44+
const result = customAssert(false, 'This should not throw');
45+
assertEquals(result, false);
46+
});
47+

tests/src/system/logger.test.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { assertEquals, assert } from "https://deno.land/[email protected]/testing/asserts.ts";
2+
import "./../../../src/system/logger.js";
3+
4+
Deno.test("Logger should log error messages", () => {
5+
logger.clear();
6+
logger.error("Test error message", "TestContext", { key: "value" });
7+
const logs = logger.logs;
8+
assertEquals(logs.length, 1);
9+
assertEquals(logs[0].message, "Test error message");
10+
assertEquals(logs[0].context, "TestContext");
11+
assertEquals(logs[0].data.key, "value");
12+
assertEquals(logs[0].level, "error");
13+
});
14+
15+
Deno.test("Logger should log warning messages", () => {
16+
logger.clear();
17+
logger.warn("Test warn message", "TestContext", { key: "value" });
18+
const logs = logger.logs;
19+
assertEquals(logs.length, 1);
20+
assertEquals(logs[0].message, "Test warn message");
21+
assertEquals(logs[0].context, "TestContext");
22+
assertEquals(logs[0].data.key, "value");
23+
assertEquals(logs[0].level, "warn");
24+
});
25+
26+
Deno.test("Logger should log info messages", () => {
27+
logger.clear();
28+
logger.info("Test info message", "TestContext", { key: "value" });
29+
const logs = logger.logs;
30+
assertEquals(logs.length, 1);
31+
assertEquals(logs[0].message, "Test info message");
32+
assertEquals(logs[0].context, "TestContext");
33+
assertEquals(logs[0].data.key, "value");
34+
assertEquals(logs[0].level, "info");
35+
});
36+
37+
Deno.test("Logger should log debug messages", () => {
38+
logger.clear();
39+
logger.debug("Test debug message", "TestContext", { key: "value" });
40+
const logs = logger.logs;
41+
assertEquals(logs.length, 1);
42+
assertEquals(logs[0].message, "Test debug message");
43+
assertEquals(logs[0].context, "TestContext");
44+
assertEquals(logs[0].data.key, "value");
45+
assertEquals(logs[0].level, "debug");
46+
});
47+
48+
Deno.test("Logger should clear all logs", () => {
49+
logger.clear();
50+
logger.error("Test error message");
51+
logger.warn("Test warn message");
52+
logger.info("Test info message");
53+
logger.debug("Test debug message");
54+
assertEquals(logger.logs.length, 4);
55+
logger.clear();
56+
assertEquals(logger.logs.length, 0);
57+
});
58+
59+
Deno.test("Logger should clear error logs", () => {
60+
logger.clear();
61+
logger.error("Test error message");
62+
logger.warn("Test warn message");
63+
logger.info("Test info message");
64+
logger.debug("Test debug message");
65+
assertEquals(logger.logs.length, 4);
66+
logger.clearErrors();
67+
assertEquals(logger.logs.length, 3);
68+
assert(logger.logs.every(log => log.level !== "error"));
69+
});
70+
71+
Deno.test("Logger should clear logs by context", () => {
72+
logger.clear();
73+
logger.error("Test error message", "Context1");
74+
logger.warn("Test warn message", "Context2");
75+
logger.info("Test info message", "Context1");
76+
logger.debug("Test debug message", "Context2");
77+
assertEquals(logger.logs.length, 4);
78+
logger.clearContext("Context1");
79+
assertEquals(logger.logs.length, 2);
80+
assert(logger.logs.every(log => log.context !== "Context1"));
81+
});
82+
83+
Deno.test("Logger should clear logs by context and data", () => {
84+
logger.clear();
85+
logger.error("Test error message", "Context1", { key: "value1" });
86+
logger.warn("Test warn message", "Context2", { key: "value2" });
87+
logger.info("Test info message", "Context1", { key: "value1" });
88+
logger.debug("Test debug message", "Context2", { key: "value2" });
89+
assertEquals(logger.logs.length, 4);
90+
logger.clearData("Context1", { key: "value1" });
91+
assertEquals(logger.logs.length, 2);
92+
assert(logger.logs.every(log => log.context !== "Context1" && log.data.key !== "value1"));
93+
});

0 commit comments

Comments
 (0)