Skip to content

Commit dac8100

Browse files
committed
add comments and test coverage to logging module
1 parent df49e48 commit dac8100

File tree

2 files changed

+101
-9
lines changed

2 files changed

+101
-9
lines changed

libs/logging/src/index.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,37 @@ export { LogLevel } from "./log-level";
77
export { ConsoleLogService } from "./console-log.service";
88
export { SemanticLogger } from "./semantic-logger.abstraction";
99

10-
/** Creates a semantic logger.
11-
* @param context all logs emitted by the logger are extended with
12-
* these fields.
13-
* @remarks The `message`, `level`, `provider`, and `content` fields
14-
* are reserved for use by the semantic logging system.
10+
/**
11+
* Creates a semantic logger with a fixed context that is included in all log messages.
12+
*
13+
* @param context - Contextual metadata that will be included in every log entry
14+
* emitted by the returned logger. This is used to identify the source or scope
15+
* of log messages (e.g., `{ type: "ImportService" }` or `{ accountId: "123" }`).
16+
*
17+
* @returns A SemanticLogger instance that includes the provided context in all log output.
18+
*
19+
* @remarks
20+
* By convention, avoid using the following field names in the context object, as they
21+
* may conflict with fields added by the semantic logging implementation:
22+
* - `message` - The log message text
23+
* - `level` - The log level (debug, info, warn, error, panic)
24+
* - `provider` - The logging provider identifier
25+
* - `content` - Additional data passed to individual log calls
26+
*
27+
* Note: These field names are not enforced at compile-time or runtime, but using them
28+
* may result in unexpected behavior or field name collisions in log output.
29+
*
30+
* @example
31+
* ```typescript
32+
* // Create a logger for a service
33+
* const log = logProvider({ type: "ImportService" });
34+
*
35+
* // All logs from this logger will include { type: "ImportService" }
36+
* log.debug("Starting import");
37+
* // Output: { type: "ImportService", level: "debug", message: "Starting import" }
38+
*
39+
* log.info({ itemCount: 42 }, "Import complete");
40+
* // Output: { type: "ImportService", level: "info", content: { itemCount: 42 }, message: "Import complete" }
41+
* ```
1542
*/
1643
export type LogProvider = <Context extends object>(context: Jsonify<Context>) => SemanticLogger;

libs/logging/src/logging.spec.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,73 @@
1+
import { mock } from "jest-mock-extended";
2+
13
import * as lib from "./index";
4+
import { SemanticLogger } from "./index";
5+
6+
describe("logging module", () => {
7+
describe("public API", () => {
8+
it("should export LogService", () => {
9+
expect(lib.LogService).toBeDefined();
10+
});
11+
12+
it("should export LogLevel", () => {
13+
expect(lib.LogLevel).toBeDefined();
14+
});
15+
16+
it("should export ConsoleLogService", () => {
17+
expect(lib.ConsoleLogService).toBeDefined();
18+
});
19+
});
20+
21+
describe("SemanticLogger", () => {
22+
let logger: SemanticLogger;
23+
24+
beforeEach(() => {
25+
logger = mock<SemanticLogger>();
26+
});
27+
28+
describe("logging methods", () => {
29+
it("should accept a message string", () => {
30+
logger.debug("debug message");
31+
logger.info("info message");
32+
logger.warn("warn message");
33+
logger.error("error message");
34+
35+
expect(logger.debug).toHaveBeenCalledWith("debug message");
36+
expect(logger.info).toHaveBeenCalledWith("info message");
37+
expect(logger.warn).toHaveBeenCalledWith("warn message");
38+
expect(logger.error).toHaveBeenCalledWith("error message");
39+
});
40+
41+
it("should accept content object and optional message", () => {
42+
logger.debug({ step: 1 }, "processing step");
43+
logger.info({ count: 42 }, "items processed");
44+
logger.warn({ threshold: 100 }, "approaching limit");
45+
logger.error({ code: 500 }, "server error");
46+
47+
expect(logger.debug).toHaveBeenCalledWith({ step: 1 }, "processing step");
48+
expect(logger.info).toHaveBeenCalledWith({ count: 42 }, "items processed");
49+
expect(logger.warn).toHaveBeenCalledWith({ threshold: 100 }, "approaching limit");
50+
expect(logger.error).toHaveBeenCalledWith({ code: 500 }, "server error");
51+
});
52+
});
53+
54+
describe("panic", () => {
55+
beforeEach(() => {
56+
logger.panic = jest.fn((content: any, msg?: string) => {
57+
const errorMsg = msg || (typeof content === "string" ? content : "panic");
58+
throw new Error(errorMsg);
59+
}) as any;
60+
});
61+
62+
it("should throw when called with a message", () => {
63+
expect(() => logger.panic("critical error")).toThrow("critical error");
64+
});
265

3-
describe("logging", () => {
4-
// This test will fail until something is exported from index.ts
5-
it("should work", () => {
6-
expect(lib).toBeDefined();
66+
it("should throw when called with content and message", () => {
67+
expect(() => logger.panic({ reason: "invalid state" }, "system panic")).toThrow(
68+
"system panic",
69+
);
70+
});
71+
});
772
});
873
});

0 commit comments

Comments
 (0)