mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-10 13:09:37 +01:00
[PM-16792] add semantic logger facade (#13255)
This commit is contained in:
parent
00b19cf577
commit
f9e2c20243
199
libs/common/src/tools/log/default-semantic-logger.spec.ts
Normal file
199
libs/common/src/tools/log/default-semantic-logger.spec.ts
Normal file
@ -0,0 +1,199 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { LogLevelType } from "../../platform/enums";
|
||||
|
||||
import { DefaultSemanticLogger } from "./default-semantic-logger";
|
||||
|
||||
const logger = mock<LogService>();
|
||||
|
||||
describe("DefaultSemanticLogger", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("debug", () => {
|
||||
it("writes structural log messages to console.log", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.debug("this is a debug message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, {
|
||||
message: "this is a debug message",
|
||||
level: LogLevelType.Debug,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.log", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.debug({ example: "this is content" });
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, {
|
||||
content: { example: "this is content" },
|
||||
level: LogLevelType.Debug,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.log with a message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.info({ example: "this is content" }, "this is a message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
|
||||
content: { example: "this is content" },
|
||||
message: "this is a message",
|
||||
level: LogLevelType.Info,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("info", () => {
|
||||
it("writes structural log messages to console.log", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.info("this is an info message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
|
||||
message: "this is an info message",
|
||||
level: LogLevelType.Info,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.log", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.info({ example: "this is content" });
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
|
||||
content: { example: "this is content" },
|
||||
level: LogLevelType.Info,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.log with a message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.info({ example: "this is content" }, "this is a message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
|
||||
content: { example: "this is content" },
|
||||
message: "this is a message",
|
||||
level: LogLevelType.Info,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("warn", () => {
|
||||
it("writes structural log messages to console.warn", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.warn("this is a warning message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
|
||||
message: "this is a warning message",
|
||||
level: LogLevelType.Warning,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.warn", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.warn({ example: "this is content" });
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
|
||||
content: { example: "this is content" },
|
||||
level: LogLevelType.Warning,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.warn with a message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.warn({ example: "this is content" }, "this is a message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
|
||||
content: { example: "this is content" },
|
||||
message: "this is a message",
|
||||
level: LogLevelType.Warning,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("error", () => {
|
||||
it("writes structural log messages to console.error", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.error("this is an error message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
message: "this is an error message",
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.error", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.error({ example: "this is content" });
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
content: { example: "this is content" },
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural content to console.error with a message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
log.error({ example: "this is content" }, "this is a message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
content: { example: "this is content" },
|
||||
message: "this is a message",
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("panic", () => {
|
||||
it("writes structural log messages to console.error before throwing the message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
expect(() => log.panic("this is an error message")).toThrow("this is an error message");
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
message: "this is an error message",
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural log messages to console.error with a message before throwing the message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
expect(() => log.panic({ example: "this is content" }, "this is an error message")).toThrow(
|
||||
"this is an error message",
|
||||
);
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
content: { example: "this is content" },
|
||||
message: "this is an error message",
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes structural log messages to console.error with a content before throwing the message", () => {
|
||||
const log = new DefaultSemanticLogger(logger, {});
|
||||
|
||||
expect(() => log.panic("this is content", "this is an error message")).toThrow(
|
||||
"this is an error message",
|
||||
);
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
|
||||
content: "this is content",
|
||||
message: "this is an error message",
|
||||
level: LogLevelType.Error,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
65
libs/common/src/tools/log/default-semantic-logger.ts
Normal file
65
libs/common/src/tools/log/default-semantic-logger.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { LogLevelType } from "../../platform/enums";
|
||||
|
||||
import { SemanticLogger } from "./semantic-logger.abstraction";
|
||||
|
||||
/** Sends semantic logs to the console.
|
||||
* @remarks the behavior of this logger is based on `LogService`; it
|
||||
* replaces dynamic messages (`%s`) with a JSON-formatted semantic log.
|
||||
*/
|
||||
export class DefaultSemanticLogger<Context extends object> implements SemanticLogger {
|
||||
/** Instantiates a console semantic logger
|
||||
* @param context a static payload that is cloned when the logger
|
||||
* logs a message. The `messages`, `level`, and `content` fields
|
||||
* are reserved for use by loggers.
|
||||
*/
|
||||
constructor(
|
||||
private logger: LogService,
|
||||
context: Jsonify<Context>,
|
||||
) {
|
||||
this.context = context && typeof context === "object" ? context : {};
|
||||
}
|
||||
|
||||
readonly context: object;
|
||||
|
||||
debug<T>(content: Jsonify<T>, message?: string): void {
|
||||
this.log(content, LogLevelType.Debug, message);
|
||||
}
|
||||
|
||||
info<T>(content: Jsonify<T>, message?: string): void {
|
||||
this.log(content, LogLevelType.Info, message);
|
||||
}
|
||||
|
||||
warn<T>(content: Jsonify<T>, message?: string): void {
|
||||
this.log(content, LogLevelType.Warning, message);
|
||||
}
|
||||
|
||||
error<T>(content: Jsonify<T>, message?: string): void {
|
||||
this.log(content, LogLevelType.Error, message);
|
||||
}
|
||||
|
||||
panic<T>(content: Jsonify<T>, message?: string): never {
|
||||
this.log(content, LogLevelType.Error, message);
|
||||
const panicMessage =
|
||||
message ?? (typeof content === "string" ? content : "a fatal error occurred");
|
||||
throw new Error(panicMessage);
|
||||
}
|
||||
|
||||
private log<T>(content: Jsonify<T>, level: LogLevelType, message?: string) {
|
||||
const log = {
|
||||
...this.context,
|
||||
message,
|
||||
content: content ?? undefined,
|
||||
level,
|
||||
};
|
||||
|
||||
if (typeof content === "string" && !message) {
|
||||
log.message = content;
|
||||
delete log.content;
|
||||
}
|
||||
|
||||
this.logger.write(level, log);
|
||||
}
|
||||
}
|
18
libs/common/src/tools/log/disabled-semantic-logger.ts
Normal file
18
libs/common/src/tools/log/disabled-semantic-logger.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SemanticLogger } from "./semantic-logger.abstraction";
|
||||
|
||||
/** Disables semantic logs. Still panics. */
|
||||
export class DisabledSemanticLogger implements SemanticLogger {
|
||||
debug<T>(_content: Jsonify<T>, _message?: string): void {}
|
||||
|
||||
info<T>(_content: Jsonify<T>, _message?: string): void {}
|
||||
|
||||
warn<T>(_content: Jsonify<T>, _message?: string): void {}
|
||||
|
||||
error<T>(_content: Jsonify<T>, _message?: string): void {}
|
||||
|
||||
panic<T>(_content: Jsonify<T>, message?: string): never {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
30
libs/common/src/tools/log/factory.ts
Normal file
30
libs/common/src/tools/log/factory.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
|
||||
import { DefaultSemanticLogger } from "./default-semantic-logger";
|
||||
import { DisabledSemanticLogger } from "./disabled-semantic-logger";
|
||||
import { SemanticLogger } from "./semantic-logger.abstraction";
|
||||
|
||||
/** Instantiates a semantic logger that emits nothing when a message
|
||||
* is logged.
|
||||
* @param _context a static payload that is cloned when the logger
|
||||
* logs a message. The `messages`, `level`, and `content` fields
|
||||
* are reserved for use by loggers.
|
||||
*/
|
||||
export function disabledSemanticLoggerProvider<Context extends object>(
|
||||
_context: Jsonify<Context>,
|
||||
): SemanticLogger {
|
||||
return new DisabledSemanticLogger();
|
||||
}
|
||||
|
||||
/** Instantiates a semantic logger that emits logs to the console.
|
||||
* @param context a static payload that is cloned when the logger
|
||||
* logs a message. The `messages`, `level`, and `content` fields
|
||||
* are reserved for use by loggers.
|
||||
* @param settings specializes how the semantic logger functions.
|
||||
* If this is omitted, the logger suppresses debug messages.
|
||||
*/
|
||||
export function consoleSemanticLoggerProvider(logger: LogService): SemanticLogger {
|
||||
return new DefaultSemanticLogger(logger, {});
|
||||
}
|
2
libs/common/src/tools/log/index.ts
Normal file
2
libs/common/src/tools/log/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { disabledSemanticLoggerProvider, consoleSemanticLoggerProvider } from "./factory";
|
||||
export { SemanticLogger } from "./semantic-logger.abstraction";
|
94
libs/common/src/tools/log/semantic-logger.abstraction.ts
Normal file
94
libs/common/src/tools/log/semantic-logger.abstraction.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
/** Semantic/structural logging component */
|
||||
export interface SemanticLogger {
|
||||
/** Logs a message at debug priority.
|
||||
* Debug messages are used for diagnostics, and are typically disabled
|
||||
* in production builds.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
debug(message: string): void;
|
||||
|
||||
/** Logs the content at debug priority.
|
||||
* Debug messages are used for diagnostics, and are typically disabled
|
||||
* in production builds.
|
||||
* @param content - JSON content included in the log's `content` field.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
debug<T>(content: Jsonify<T>, message?: string): void;
|
||||
|
||||
/** combined signature for overloaded methods */
|
||||
debug<T>(content: Jsonify<T> | string, message?: string): void;
|
||||
|
||||
/** Logs a message at informational priority.
|
||||
* Information messages are used for status reports.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
info(message: string): void;
|
||||
|
||||
/** Logs the content at informational priority.
|
||||
* Information messages are used for status reports.
|
||||
* @param content - JSON content included in the log's `content` field.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
info<T>(content: Jsonify<T>, message?: string): void;
|
||||
|
||||
/** combined signature for overloaded methods */
|
||||
info<T>(content: Jsonify<T> | string, message?: string): void;
|
||||
|
||||
/** Logs a message at warn priority.
|
||||
* Warn messages are used to indicate a operation that may affect system
|
||||
* stability occurred.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
warn(message: string): void;
|
||||
|
||||
/** Logs the content at warn priority.
|
||||
* Warn messages are used to indicate a operation that may affect system
|
||||
* stability occurred.
|
||||
* @param content - JSON content included in the log's `content` field.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
warn<T>(content: Jsonify<T>, message?: string): void;
|
||||
|
||||
/** combined signature for overloaded methods */
|
||||
warn<T>(content: Jsonify<T> | string, message?: string): void;
|
||||
|
||||
/** Logs a message at error priority.
|
||||
* Error messages are used to indicate a operation that affects system
|
||||
* stability occurred and the system was able to recover.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
error(message: string): void;
|
||||
|
||||
/** Logs the content at debug priority.
|
||||
* Error messages are used to indicate a operation that affects system
|
||||
* stability occurred and the system was able to recover.
|
||||
* @param content - JSON content included in the log's `content` field.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
error<T>(content: Jsonify<T>, message?: string): void;
|
||||
|
||||
/** combined signature for overloaded methods */
|
||||
error<T>(content: Jsonify<T> | string, message?: string): void;
|
||||
|
||||
/** Logs a message at panic priority and throws an error.
|
||||
* Panic messages are used to indicate a operation that affects system
|
||||
* stability occurred and the system cannot recover. Panic messages
|
||||
* log an error and throw an `Error`.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
panic(message: string): never;
|
||||
|
||||
/** Logs the content at debug priority and throws an error.
|
||||
* Panic messages are used to indicate a operation that affects system
|
||||
* stability occurred and the system cannot recover. Panic messages
|
||||
* log an error and throw an `Error`.
|
||||
* @param content - JSON content included in the log's `content` field.
|
||||
* @param message - a message to record in the log's `message` field.
|
||||
*/
|
||||
panic<T>(content: Jsonify<T>, message?: string): never;
|
||||
|
||||
/** combined signature for overloaded methods */
|
||||
panic<T>(content: Jsonify<T> | string, message?: string): never;
|
||||
}
|
Loading…
Reference in New Issue
Block a user