1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-03 18:23:57 +00:00

add comments and test coverage to logging module

This commit is contained in:
John Harrington
2025-10-05 15:24:17 -07:00
parent df49e482b9
commit dac8100bc6
2 changed files with 102 additions and 10 deletions

View File

@@ -7,10 +7,37 @@ export { LogLevel } from "./log-level";
export { ConsoleLogService } from "./console-log.service";
export { SemanticLogger } from "./semantic-logger.abstraction";
/** Creates a semantic logger.
* @param context all logs emitted by the logger are extended with
* these fields.
* @remarks The `message`, `level`, `provider`, and `content` fields
* are reserved for use by the semantic logging system.
/**
* Creates a semantic logger with a fixed context that is included in all log messages.
*
* @param context - Contextual metadata that will be included in every log entry
* emitted by the returned logger. This is used to identify the source or scope
* of log messages (e.g., `{ type: "ImportService" }` or `{ accountId: "123" }`).
*
* @returns A SemanticLogger instance that includes the provided context in all log output.
*
* @remarks
* By convention, avoid using the following field names in the context object, as they
* may conflict with fields added by the semantic logging implementation:
* - `message` - The log message text
* - `level` - The log level (debug, info, warn, error, panic)
* - `provider` - The logging provider identifier
* - `content` - Additional data passed to individual log calls
*
* Note: These field names are not enforced at compile-time or runtime, but using them
* may result in unexpected behavior or field name collisions in log output.
*
* @example
* ```typescript
* // Create a logger for a service
* const log = logProvider({ type: "ImportService" });
*
* // All logs from this logger will include { type: "ImportService" }
* log.debug("Starting import");
* // Output: { type: "ImportService", level: "debug", message: "Starting import" }
*
* log.info({ itemCount: 42 }, "Import complete");
* // Output: { type: "ImportService", level: "info", content: { itemCount: 42 }, message: "Import complete" }
* ```
*/
export type LogProvider = <Context extends object>(context: Jsonify<Context>) => SemanticLogger;

View File

@@ -1,8 +1,73 @@
import * as lib from "./index";
import { mock } from "jest-mock-extended";
describe("logging", () => {
// This test will fail until something is exported from index.ts
it("should work", () => {
expect(lib).toBeDefined();
import * as lib from "./index";
import { SemanticLogger } from "./index";
describe("logging module", () => {
describe("public API", () => {
it("should export LogService", () => {
expect(lib.LogService).toBeDefined();
});
it("should export LogLevel", () => {
expect(lib.LogLevel).toBeDefined();
});
it("should export ConsoleLogService", () => {
expect(lib.ConsoleLogService).toBeDefined();
});
});
describe("SemanticLogger", () => {
let logger: SemanticLogger;
beforeEach(() => {
logger = mock<SemanticLogger>();
});
describe("logging methods", () => {
it("should accept a message string", () => {
logger.debug("debug message");
logger.info("info message");
logger.warn("warn message");
logger.error("error message");
expect(logger.debug).toHaveBeenCalledWith("debug message");
expect(logger.info).toHaveBeenCalledWith("info message");
expect(logger.warn).toHaveBeenCalledWith("warn message");
expect(logger.error).toHaveBeenCalledWith("error message");
});
it("should accept content object and optional message", () => {
logger.debug({ step: 1 }, "processing step");
logger.info({ count: 42 }, "items processed");
logger.warn({ threshold: 100 }, "approaching limit");
logger.error({ code: 500 }, "server error");
expect(logger.debug).toHaveBeenCalledWith({ step: 1 }, "processing step");
expect(logger.info).toHaveBeenCalledWith({ count: 42 }, "items processed");
expect(logger.warn).toHaveBeenCalledWith({ threshold: 100 }, "approaching limit");
expect(logger.error).toHaveBeenCalledWith({ code: 500 }, "server error");
});
});
describe("panic", () => {
beforeEach(() => {
logger.panic = jest.fn((content: any, msg?: string) => {
const errorMsg = msg || (typeof content === "string" ? content : "panic");
throw new Error(errorMsg);
}) as any;
});
it("should throw when called with a message", () => {
expect(() => logger.panic("critical error")).toThrow("critical error");
});
it("should throw when called with content and message", () => {
expect(() => logger.panic({ reason: "invalid state" }, "system panic")).toThrow(
"system panic",
);
});
});
});
});