Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: better logging in the browser #7587

Merged
merged 2 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions packages/core/src/bindings/log/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import colors from "ansi-colors";
import { MESSAGE } from "triple-beam";
import { colorizer } from "../../log/Colorizer.js";
import {
type LogFormat,
combine,
formatLogMessage,
label,
printLogMessage,
timestamp,
} from "../../log/format.js";
import {
type LogConfig,
type LogContext,
type LogFactory,
type ZWaveLogInfo,
timestampFormatShort,
} from "../../log/shared.js";
import { type LogContainer, type ZWaveLogger } from "../../log/traits.js";

colors.enabled = true;

function createLoggerFormat(
channel: string,
colorize: boolean,
shortTimestamps: boolean,
): LogFormat {
const formats = [
label(channel),
shortTimestamps
? timestamp(timestampFormatShort)
: timestamp(),
formatLogMessage,
colorize ? colorizer(false) : undefined,
printLogMessage(shortTimestamps),
].filter((f) => f != undefined);
return combine(...formats);
}

class ConsoleLogContainer implements LogContainer {
#loggers = new Map<string, ZWaveLogger>();

updateConfiguration(_config: Partial<LogConfig>): void {
// noop
}
getConfiguration(): LogConfig {
return {
enabled: true,
level: "debug",
transports: [],
logToFile: false,
filename: "zwavejs.log",
forceConsole: false,
maxFiles: 0,
};
}
destroy(): void {
// noop
}
getLogger<TContext extends LogContext = LogContext<string>>(
label: string,
): ZWaveLogger<TContext> {
if (!this.#loggers.has(label)) {
const format = createLoggerFormat(label, true, false);
this.#loggers.set(label, {
log: (info: ZWaveLogInfo<LogContext>) => {
info = format.transform(info);
if (info.level === "error") {
console.error(info[MESSAGE]);
} else {
console.log(info[MESSAGE]);
}
},
});
}
return this.#loggers.get(label)!;
}
isLoglevelVisible(loglevel: string): boolean {
return loglevel !== "silly";
}
isNodeLoggingVisible(_nodeId: number): boolean {
return true;
}
}

export const log: LogFactory = (_config?: Partial<LogConfig>) =>
new ConsoleLogContainer();
75 changes: 67 additions & 8 deletions packages/core/src/bindings/log/node.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { getenv } from "@zwave-js/shared";
import { type Format } from "logform";
import path from "pathe";
import { configs } from "triple-beam";
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import type Transport from "winston-transport";
import type { ConsoleTransportInstance } from "winston/lib/winston/transports";
import { colorizer } from "../../log/Colorizer.js";
import {
createDefaultTransportFormat,
createLoggerFormat,
} from "../../log/shared.js";
combine,
formatLogMessage,
label,
printLogMessage,
timestamp,
} from "../../log/format.js";
import {
type LogConfig,
type LogContext,
type LogFactory,
nonUndefinedLogConfigKeys,
stringToNodeList,
} from "../../log/shared_safe.js";
timestampFormatShort,
} from "../../log/shared.js";
import { type LogContainer, type ZWaveLogger } from "../../log/traits.js";

const isTTY = process.stdout.isTTY;
Expand All @@ -36,9 +42,60 @@ function loglevelFromNumber(numLevel: number | undefined): string | undefined {
}
}

class ZWaveLogContainer<TContext extends LogContext> extends winston.Container
implements LogContainer
{
/** Creates the common logger format for all loggers under a given channel */
export function createLoggerFormat(channel: string): Format {
return combine(
// add the channel as a label
label(channel),
// default to short timestamps
timestamp(),
) as unknown as Format;
}

/** The common logger format for built-in transports */
export function createDefaultTransportFormat(
colorize: boolean,
shortTimestamps: boolean,
): Format {
const formats = [
// overwrite the default timestamp format if necessary
shortTimestamps
? timestamp(timestampFormatShort)
: undefined,
formatLogMessage,
colorize ? colorizer() : undefined,
printLogMessage(shortTimestamps),
].filter((f) => f != undefined);
return combine(...formats) as unknown as Format;
}

/** Unsilences the console transport of a logger and returns the original value */
export function unsilence(logger: winston.Logger): boolean {
const consoleTransport = logger.transports.find(
(t) => (t as any).name === "console",
);
if (consoleTransport) {
const ret = !!consoleTransport.silent;
consoleTransport.silent = false;
return ret;
}
return false;
}

/** Restores the console transport of a logger to its original silence state */
export function restoreSilence(
logger: winston.Logger,
original: boolean,
): void {
const consoleTransport = logger.transports.find(
(t) => (t as any).name === "console",
);
if (consoleTransport) {
consoleTransport.silent = original;
}
}

class ZWaveLogContainer extends winston.Container implements LogContainer {
private fileTransport: DailyRotateFile | undefined;
private consoleTransport: ConsoleTransportInstance | undefined;
private loglevelVisibleCache = new Map<string, boolean>();
Expand All @@ -59,7 +116,9 @@ class ZWaveLogContainer<TContext extends LogContext> extends winston.Container
this.updateConfiguration(config);
}

public getLogger(label: string): ZWaveLogger<TContext> {
public getLogger<TContext extends LogContext>(
label: string,
): ZWaveLogger<TContext> {
if (!this.has(label)) {
this.add(label, {
transports: this.getAllTransports(),
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export * from "./log/Controller.definitions.js";
export * from "./log/Controller.js";
export * from "./log/ZWaveLoggerBase.js";
export * from "./log/shared.js";
export * from "./log/shared_safe.js";
export type * from "./log/traits.js";
export * from "./qr/index.js";
export * from "./reflection/decorators.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index_browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export * from "./fsm/FSM.js";
export * from "./log/Controller.definitions.js";
export * from "./log/Controller.js";
export * from "./log/ZWaveLoggerBase.js";
export * from "./log/shared_safe.js";
export * from "./log/shared.js";
export type * from "./log/traits.js";
export * from "./qr/index.js";
export * from "./reflection/decorators.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index_safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export * from "./definitions/index.js";
export * from "./dsk/index.js";
export * from "./error/ZWaveError.js";
export * from "./log/shared_safe.js";
export * from "./log/shared.js";
// eslint-disable-next-line @zwave-js/no-forbidden-imports -- FIXME: We know this import is safe, but the lint rule doesn't
export * from "./qr/index.js";
export * from "./registries/DeviceClasses.js";
Expand Down
94 changes: 45 additions & 49 deletions packages/core/src/log/Colorizer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import colors from "ansi-colors";
import { type TransformFunction, format } from "logform";
import winston from "winston";
import type { ZWaveLogInfo } from "./shared_safe.js";
const defaultColors = winston.config.npm.colors;
import { configs } from "triple-beam";
import { type LogFormat } from "./format.js";

// This is a placeholder
interface ColorizerOptions {
__foo?: undefined;
}
const defaultColors = configs.npm.colors;

const primaryAndInlineTagRegex = /\[([^\]]+)\]/g;

Expand All @@ -29,44 +24,45 @@ function colorizeTextAndTags(
);
}

export const colorizer = format(
((
info: ZWaveLogInfo,
_opts: ColorizerOptions,
) => {
const levelColorKey =
defaultColors[info.level as keyof typeof defaultColors] as string;
const textColor = (colors as any)[levelColorKey];
const bgColor = (colors as any)[getBgColorName(levelColorKey)];
// Colorize all segments separately
if (typeof info.message === "string") {
info.message = colorizeTextAndTags(
info.message,
textColor,
bgColor,
);
} else {
info.message = info.message.map((msg) =>
colorizeTextAndTags(msg, textColor, bgColor)
);
}
info.direction = colors.white(info.direction);
if (info.label) {
info.label = colors.gray.inverse(info.label);
}
if (info.timestamp) {
info.timestamp = colors.gray(info.timestamp);
}
if (info.primaryTags) {
info.primaryTags = colorizeTextAndTags(
info.primaryTags,
textColor,
bgColor,
);
}
if (info.secondaryTags) {
info.secondaryTags = colors.gray(info.secondaryTags);
}
return info;
}) as unknown as TransformFunction,
);
export function colorizer(bg: boolean = true): LogFormat {
return {
transform: (info) => {
const levelColorKey =
defaultColors[info.level as keyof typeof defaultColors];
const textColor = (colors as any)[levelColorKey];
const bgColor = bg
? (colors as any)[getBgColorName(levelColorKey)]
: ((txt: string) => txt);
// Colorize all segments separately
if (typeof info.message === "string") {
info.message = colorizeTextAndTags(
info.message,
textColor,
bgColor,
);
} else {
info.message = info.message.map((msg) =>
colorizeTextAndTags(msg, textColor, bgColor)
);
}
info.direction = colors.white(info.direction);
if (info.label) {
info.label = colors.gray.inverse(info.label);
}
if (info.timestamp) {
info.timestamp = colors.gray(info.timestamp);
}
if (info.primaryTags) {
info.primaryTags = colorizeTextAndTags(
info.primaryTags,
textColor,
bgColor,
);
}
if (info.secondaryTags) {
info.secondaryTags = colors.gray(info.secondaryTags);
}
return info;
},
};
}
2 changes: 1 addition & 1 deletion packages/core/src/log/Controller.definitions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type InterviewStage, type ValueID } from "../index_browser.js";
import type { DataDirection, LogContext } from "./shared_safe.js";
import type { DataDirection, LogContext } from "./shared.js";

export const CONTROLLER_LABEL = "CNTRLR";
export const CONTROLLER_LOGLEVEL = "info";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/log/Controller.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node";
import { createDefaultTransportFormat } from "@zwave-js/core/bindings/log/node";
import { beforeEach, test as baseTest } from "vitest";
import { CommandClasses } from "../definitions/CommandClasses.js";
import { InterviewStage } from "../definitions/InterviewStage.js";
Expand All @@ -8,7 +9,6 @@ import {
assertMessage,
} from "../test/SpyTransport.js";
import { ControllerLogger } from "./Controller.js";
import { createDefaultTransportFormat } from "./shared.js";

// Extend the test conte

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/log/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import {
VALUE_LOGLEVEL,
} from "./Controller.definitions.js";
import { ZWaveLoggerBase } from "./ZWaveLoggerBase.js";
import { tagify } from "./shared_safe.js";
import { getDirectionPrefix, getNodeTag } from "./shared_safe.js";
import { tagify } from "./shared.js";
import { getDirectionPrefix, getNodeTag } from "./shared.js";
import { type LogContainer } from "./traits.js";

export class ControllerLogger extends ZWaveLoggerBase<ControllerLogContext>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/log/ZWaveLoggerBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type LogContext } from "./shared_safe.js";
import { type LogContext } from "./shared.js";
import type { LogContainer, ZWaveLogger } from "./traits.js";

export class ZWaveLoggerBase<TContext extends LogContext = LogContext> {
Expand Down
Loading
Loading