refactor(logs): move to core package (#4586)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { DnsCacheManager } from "dns-caching";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { env } from "../env";
|
||||
|
||||
@@ -12,6 +12,8 @@ declare global {
|
||||
};
|
||||
}
|
||||
|
||||
const logger = createLogger({ module: "dns" });
|
||||
|
||||
// Initialize global.homarr if not present
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
global.homarr ??= {};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import { RequestError } from "../request-error";
|
||||
import { ResponseError } from "../response-error";
|
||||
@@ -9,11 +7,15 @@ import { matchErrorCode } from "./fetch-http-error-handler";
|
||||
import { HttpErrorHandler } from "./http-error-handler";
|
||||
|
||||
export class AxiosHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor() {
|
||||
super("axios");
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
if (!(error instanceof AxiosError)) return undefined;
|
||||
if (error.code === undefined) return undefined;
|
||||
|
||||
logger.debug("Received Axios request error", {
|
||||
this.logRequestError({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
});
|
||||
@@ -28,8 +30,7 @@ export class AxiosHttpErrorHandler extends HttpErrorHandler {
|
||||
handleResponseError(error: unknown): ResponseError | undefined {
|
||||
if (!(error instanceof AxiosError)) return undefined;
|
||||
if (error.response === undefined) return undefined;
|
||||
|
||||
logger.debug("Received Axios response error", {
|
||||
this.logResponseError({
|
||||
status: error.response.status,
|
||||
url: error.response.config.url,
|
||||
message: error.message,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { objectEntries } from "../../../object";
|
||||
import type { Modify } from "../../../types";
|
||||
import type { AnyRequestError, AnyRequestErrorInput, RequestErrorCode, RequestErrorReason } from "../request-error";
|
||||
@@ -9,13 +7,13 @@ import { HttpErrorHandler } from "./http-error-handler";
|
||||
|
||||
export class FetchHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor(private type = "undici") {
|
||||
super();
|
||||
super(type);
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
if (!isTypeErrorWithCode(error)) return undefined;
|
||||
|
||||
logger.debug(`Received ${this.type} request error`, {
|
||||
this.logRequestError({
|
||||
code: error.cause.code,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import type { Logger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import type { ResponseError } from "../response-error";
|
||||
|
||||
export abstract class HttpErrorHandler {
|
||||
protected logger: Logger;
|
||||
|
||||
constructor(type: string) {
|
||||
this.logger = createLogger({ module: "httpErrorHandler", type });
|
||||
}
|
||||
|
||||
protected logRequestError<T extends { code: string }>(metadata: T) {
|
||||
this.logger.debug("Received request error", metadata);
|
||||
}
|
||||
|
||||
protected logResponseError<T extends { status: number; url: string | undefined }>(metadata: T) {
|
||||
this.logger.debug("Received response error", metadata);
|
||||
}
|
||||
|
||||
abstract handleRequestError(error: unknown): AnyRequestError | undefined;
|
||||
abstract handleResponseError(error: unknown): ResponseError | undefined;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { FetchError } from "node-fetch";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { RequestError } from "../request-error";
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import type { ResponseError } from "../response-error";
|
||||
@@ -15,14 +13,14 @@ import { HttpErrorHandler } from "./http-error-handler";
|
||||
*/
|
||||
export class NodeFetchHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor(private type = "node-fetch") {
|
||||
super();
|
||||
super(type);
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
if (!(error instanceof FetchError)) return undefined;
|
||||
if (error.code === undefined) return undefined;
|
||||
|
||||
logger.debug(`Received ${this.type} request error`, {
|
||||
this.logRequestError({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,10 @@ import { ResponseError } from "../response-error";
|
||||
import { HttpErrorHandler } from "./http-error-handler";
|
||||
|
||||
export class OctokitHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor() {
|
||||
super("octokit");
|
||||
}
|
||||
|
||||
/**
|
||||
* I wasn't able to get a request error triggered. Therefore we ignore them for now
|
||||
* and just forward them as unknown errors
|
||||
@@ -16,6 +20,11 @@ export class OctokitHttpErrorHandler extends HttpErrorHandler {
|
||||
handleResponseError(error: unknown): ResponseError | undefined {
|
||||
if (!(error instanceof OctokitRequestError)) return undefined;
|
||||
|
||||
this.logResponseError({
|
||||
status: error.status,
|
||||
url: error.response?.url,
|
||||
});
|
||||
|
||||
return new ResponseError({
|
||||
status: error.status,
|
||||
url: error.response?.url,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { FetchError } from "ofetch";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import { ResponseError } from "../response-error";
|
||||
import { FetchHttpErrorHandler } from "./fetch-http-error-handler";
|
||||
@@ -14,6 +12,10 @@ import { HttpErrorHandler } from "./http-error-handler";
|
||||
* It is for example used within the ctrl packages like qbittorrent, deluge, transmission, etc.
|
||||
*/
|
||||
export class OFetchHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor() {
|
||||
super("ofetch");
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
if (!(error instanceof FetchError)) return undefined;
|
||||
if (!(error.cause instanceof TypeError)) return undefined;
|
||||
@@ -28,7 +30,7 @@ export class OFetchHttpErrorHandler extends HttpErrorHandler {
|
||||
if (!(error instanceof FetchError)) return undefined;
|
||||
if (error.response === undefined) return undefined;
|
||||
|
||||
logger.debug("Received ofetch response error", {
|
||||
this.logResponseError({
|
||||
status: error.response.status,
|
||||
url: error.response.url,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import { ResponseError } from "../response-error";
|
||||
import { HttpErrorHandler } from "./http-error-handler";
|
||||
import { NodeFetchHttpErrorHandler } from "./node-fetch-http-error-handler";
|
||||
|
||||
export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor() {
|
||||
super("tsdav");
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
return new NodeFetchHttpErrorHandler("tsdav").handleRequestError(error);
|
||||
}
|
||||
@@ -16,8 +18,9 @@ export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
||||
// https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86
|
||||
if (error.message !== "Invalid credentials") return undefined;
|
||||
|
||||
logger.debug("Received tsdav response error", {
|
||||
this.logResponseError({
|
||||
status: 401,
|
||||
url: undefined,
|
||||
});
|
||||
|
||||
return new ResponseError({ status: 401, url: "?" });
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { ParseError } from "../parse-error";
|
||||
import { ParseErrorHandler } from "./parse-error-handler";
|
||||
|
||||
export class JsonParseErrorHandler extends ParseErrorHandler {
|
||||
constructor() {
|
||||
super("json");
|
||||
}
|
||||
|
||||
handleParseError(error: unknown): ParseError | undefined {
|
||||
if (!(error instanceof SyntaxError)) return undefined;
|
||||
|
||||
logger.debug("Received JSON parse error", {
|
||||
this.logParseError({
|
||||
message: error.message,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import type { Logger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { ParseError } from "../parse-error";
|
||||
|
||||
export abstract class ParseErrorHandler {
|
||||
protected logger: Logger;
|
||||
constructor(type: string) {
|
||||
this.logger = createLogger({ module: "parseErrorHandler", type });
|
||||
}
|
||||
|
||||
protected logParseError(metadata?: Record<string, unknown>) {
|
||||
this.logger.debug("Received parse error", metadata);
|
||||
}
|
||||
|
||||
abstract handleParseError(error: unknown): ParseError | undefined;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { ZodError } from "zod/v4";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { ParseError } from "../parse-error";
|
||||
import { ParseErrorHandler } from "./parse-error-handler";
|
||||
|
||||
export class ZodParseErrorHandler extends ParseErrorHandler {
|
||||
constructor() {
|
||||
super("zod");
|
||||
}
|
||||
|
||||
handleParseError(error: unknown): ParseError | undefined {
|
||||
if (!(error instanceof ZodError)) return undefined;
|
||||
|
||||
@@ -17,7 +19,7 @@ export class ZodParseErrorHandler extends ParseErrorHandler {
|
||||
prefix: null,
|
||||
}).toString();
|
||||
|
||||
logger.debug("Received Zod parse error");
|
||||
this.logParseError();
|
||||
|
||||
return new ParseError(message, { cause: error });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { Agent } from "undici";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
// The below import statement initializes dns-caching
|
||||
import "./dns";
|
||||
|
||||
const logger = createLogger({ module: "fetchAgent" });
|
||||
|
||||
export class LoggingAgent extends Agent {
|
||||
constructor(...props: ConstructorParameters<typeof Agent>) {
|
||||
super(...props);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import * as logs from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { LoggingAgent } from "../fetch-agent";
|
||||
|
||||
@@ -16,24 +16,36 @@ vi.mock("undici", () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/logs", async () => {
|
||||
const actual: typeof logs = await vi.importActual("@homarr/core/infrastructure/logs");
|
||||
return {
|
||||
...actual,
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const REDACTED = "REDACTED";
|
||||
|
||||
const loggerMock = logs.createLogger({ module: "test" });
|
||||
|
||||
describe("LoggingAgent should log all requests", () => {
|
||||
test("should log all requests", () => {
|
||||
// Arrange
|
||||
const infoLogSpy = vi.spyOn(logger, "debug");
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path: "/", method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(infoLogSpy).toHaveBeenCalledWith("Dispatching request https://homarr.dev/ (0 headers)");
|
||||
expect(debugSpy).toHaveBeenCalledWith("Dispatching request https://homarr.dev/ (0 headers)");
|
||||
});
|
||||
|
||||
test("should show amount of headers", () => {
|
||||
// Arrange
|
||||
const infoLogSpy = vi.spyOn(logger, "debug");
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
|
||||
// Act
|
||||
@@ -51,7 +63,7 @@ describe("LoggingAgent should log all requests", () => {
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining("(2 headers)"));
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining("(2 headers)"));
|
||||
});
|
||||
|
||||
test.each([
|
||||
@@ -69,14 +81,14 @@ describe("LoggingAgent should log all requests", () => {
|
||||
[`/${"a".repeat(32)}/?param=123`, `/${REDACTED}/?param=123`],
|
||||
])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => {
|
||||
// Arrange
|
||||
const infoLogSpy = vi.spyOn(logger, "debug");
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${expected} `));
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${expected} `));
|
||||
});
|
||||
test.each([
|
||||
["empty", "/?empty"],
|
||||
@@ -88,13 +100,13 @@ describe("LoggingAgent should log all requests", () => {
|
||||
["date times", "/?datetime=2022-01-01T00:00:00.000Z"],
|
||||
])("should not redact values that are %s", (_reason, path) => {
|
||||
// Arrange
|
||||
const infoLogSpy = vi.spyOn(logger, "debug");
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${path} `));
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${path} `));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user