refactor(logs): move to core package (#4586)

This commit is contained in:
Meier Lukas
2025-12-16 23:37:44 +01:00
committed by GitHub
parent d86af072bf
commit d348abfe4a
145 changed files with 971 additions and 708 deletions

View File

@@ -1,5 +1,5 @@
import { isFunction } from "@homarr/common";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { Integration } from "../integration";
import type { IIntegrationErrorHandler } from "./handler";
@@ -8,9 +8,7 @@ import { IntegrationError } from "./integration-error";
import { IntegrationUnknownError } from "./integration-unknown-error";
import { integrationJsonParseErrorHandler, integrationZodParseErrorHandler } from "./parse";
const localLogger = logger.child({
module: "HandleIntegrationErrors",
});
const logger = createLogger({ module: "handleIntegrationErrors" });
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any
type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T;
@@ -59,7 +57,7 @@ export const HandleIntegrationErrors = (errorHandlers: IIntegrationErrorHandler[
}
// If the error was handled and should be thrown again, throw it
localLogger.debug("Unhandled error in integration", {
logger.debug("Unhandled error in integration", {
error: error instanceof Error ? `${error.name}: ${error.message}` : undefined,
integrationName: this.publicIntegration.name,
});

View File

@@ -1,10 +1,11 @@
import superjson from "superjson";
import { decryptSecret, encryptSecret } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { createGetSetChannel } from "@homarr/redis";
const localLogger = logger.child({ module: "SessionStore" });
const logger = createLogger({ module: "sessionStore" });
export const createSessionStore = <TValue>(integration: { id: string }) => {
const channelName = `session-store:${integration.id}`;
@@ -12,26 +13,26 @@ export const createSessionStore = <TValue>(integration: { id: string }) => {
return {
async getAsync() {
localLogger.debug("Getting session from store", { store: channelName });
logger.debug("Getting session from store", { store: channelName });
const value = await channel.getAsync();
if (!value) return null;
try {
return superjson.parse<TValue>(decryptSecret(value));
} catch (error) {
localLogger.warn("Failed to load session", { store: channelName, error });
logger.warn("Failed to load session", { store: channelName, error });
return null;
}
},
async setAsync(value: TValue) {
localLogger.debug("Updating session in store", { store: channelName });
logger.debug("Updating session in store", { store: channelName });
try {
await channel.setAsync(encryptSecret(superjson.stringify(value)));
} catch (error) {
localLogger.error("Failed to save session", { store: channelName, error });
logger.error(new ErrorWithMetadata("Failed to save session", { store: channelName }, { cause: error }));
}
},
async clearAsync() {
localLogger.debug("Cleared session in store", { store: channelName });
logger.debug("Cleared session in store", { store: channelName });
await channel.removeAsync();
},
};

View File

@@ -7,7 +7,7 @@ import {
getTrustedCertificateHostnamesAsync,
} from "@homarr/certificates/server";
import { getPortFromUrl } from "@homarr/common";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationRequestErrorOfType } from "../errors/http/integration-request-error";
import { IntegrationRequestError } from "../errors/http/integration-request-error";
@@ -15,8 +15,8 @@ import { IntegrationError } from "../errors/integration-error";
import type { AnyTestConnectionError } from "./test-connection-error";
import { TestConnectionError } from "./test-connection-error";
const localLogger = logger.child({
module: "TestConnectionService",
const logger = createLogger({
module: "testConnectionService",
});
export type TestingResult =
@@ -36,7 +36,7 @@ export class TestConnectionService {
constructor(private url: URL) {}
public async handleAsync(testingCallbackAsync: AsyncTestingCallback) {
localLogger.debug("Testing connection", {
logger.debug("Testing connection", {
url: this.url.toString(),
});
@@ -72,14 +72,14 @@ export class TestConnectionService {
});
if (testingResult.success) {
localLogger.debug("Testing connection succeeded", {
logger.debug("Testing connection succeeded", {
url: this.url.toString(),
});
return testingResult;
}
localLogger.debug("Testing connection failed", {
logger.debug("Testing connection failed", {
url: this.url.toString(),
error: `${testingResult.error.name}: ${testingResult.error.message}`,
});
@@ -124,7 +124,7 @@ export class TestConnectionService {
const x509 = socket.getPeerX509Certificate();
socket.destroy();
localLogger.debug("Fetched certificate", {
logger.debug("Fetched certificate", {
url: this.url.toString(),
subject: x509?.subject,
issuer: x509?.issuer,

View File

@@ -1,7 +1,7 @@
import type { RequestInit, Response } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -15,7 +15,7 @@ import type {
} from "../interfaces/releases-providers/releases-providers-types";
import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas";
const localLogger = logger.child({ module: "CodebergIntegration" });
const logger = createLogger({ module: "codebergIntegration" });
export class CodebergIntegration extends Integration implements ReleasesProviderIntegration {
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
@@ -45,10 +45,9 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
private parseIdentifier(identifier: string) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${identifier} with Codeberg integration`,
{ identifier },
);
logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", {
identifier,
});
return null;
}
return { owner, name };
@@ -109,7 +108,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
});
if (!response.ok) {
localLogger.warn(`Failed to get details response for ${owner}/${name} with Codeberg integration`, {
logger.warn("Failed to get details", {
owner,
name,
error: response.statusText,
@@ -122,7 +121,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
const { data, success, error } = detailsResponseSchema.safeParse(responseJson);
if (!success) {
localLogger.warn(`Failed to parse details response for ${owner}/${name} with Codeberg integration`, {
logger.warn("Failed to parse details", {
owner,
name,
error,

View File

@@ -2,7 +2,7 @@ import type { fetch, RequestInit, Response } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -18,7 +18,7 @@ import type {
} from "../interfaces/releases-providers/releases-providers-types";
import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas";
const localLogger = logger.child({ module: "DockerHubIntegration" });
const logger = createLogger({ module: "dockerHubIntegration" });
export class DockerHubIntegration extends Integration implements ReleasesProviderIntegration {
private readonly sessionStore: SessionStore<string>;
@@ -35,7 +35,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
const storedSession = await this.sessionStore.getAsync();
if (storedSession) {
localLogger.debug("Using stored session for request", { integrationId: this.integration.id });
logger.debug("Using stored session for request", { integrationId: this.integration.id });
const response = await callback({
Authorization: `Bearer ${storedSession}`,
});
@@ -43,7 +43,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
return response;
}
localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id });
logger.debug("Session expired, getting new session", { integrationId: this.integration.id });
}
const accessToken = await this.getSessionAsync();
@@ -57,10 +57,10 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
const hasAuth = this.hasSecretValue("username") && this.hasSecretValue("personalAccessToken");
if (hasAuth) {
localLogger.debug("Testing DockerHub connection with authentication", { integrationId: this.integration.id });
logger.debug("Testing DockerHub connection with authentication", { integrationId: this.integration.id });
await this.getSessionAsync(input.fetchAsync);
} else {
localLogger.debug("Testing DockerHub connection without authentication", { integrationId: this.integration.id });
logger.debug("Testing DockerHub connection without authentication", { integrationId: this.integration.id });
const response = await input.fetchAsync(this.url("/v2/repositories/library"));
if (!response.ok) {
return TestConnectionError.StatusResult(response);
@@ -76,7 +76,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
if (!identifier.includes("/")) return { owner: "", name: identifier };
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(`Invalid identifier format. Expected 'owner/name' or 'name', for ${identifier} on DockerHub`, {
logger.warn("Invalid identifier format. Expected 'owner/name' or 'name', for identifier", {
identifier,
});
return null;
@@ -137,7 +137,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
});
if (!response.ok) {
localLogger.warn(`Failed to get details response for ${relativeUrl} with DockerHub integration`, {
logger.warn("Failed to get details response", {
relativeUrl,
error: response.statusText,
});
@@ -149,7 +149,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
const { data, success, error } = detailsResponseSchema.safeParse(responseJson);
if (!success) {
localLogger.warn(`Failed to parse details response for ${relativeUrl} with DockerHub integration`, {
logger.warn("Failed to parse details response", {
relativeUrl,
error,
});
@@ -183,7 +183,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
throw new ResponseError({ status: 401, url: response.url });
}
localLogger.info("Received session successfully", { integrationId: this.integration.id });
logger.info("Received session successfully", { integrationId: this.integration.id });
return result.access_token;
}

View File

@@ -3,7 +3,7 @@ import { Octokit, RequestError } from "octokit";
import type { fetch } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import { integrationOctokitHttpErrorHandler } from "../base/errors/http";
@@ -18,7 +18,7 @@ import type {
ReleaseResponse,
} from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
const logger = createLogger({ module: "githubContainerRegistryIntegration" });
@HandleIntegrationErrors([integrationOctokitHttpErrorHandler])
export class GitHubContainerRegistryIntegration extends Integration implements ReleasesProviderIntegration {
@@ -45,10 +45,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
private parseIdentifier(identifier: string) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${identifier} with GitHub Container Registry integration`,
{ identifier },
);
logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", { identifier });
return null;
}
return { owner, name };
@@ -91,7 +88,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
return { success: true, data: { ...details, ...latestRelease } };
} catch (error) {
const errorMessage = error instanceof RequestError ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, {
logger.warn("Failed to get releases", {
owner,
name,
error: errorMessage,
@@ -123,7 +120,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
forksCount: response.data.repository?.forks_count,
};
} catch (error) {
localLogger.warn(`Failed to get details for ${owner}\\${name} with GitHub Container Registry integration`, {
logger.warn("Failed to get details", {
owner,
name,
error: error instanceof RequestError ? error.message : String(error),

View File

@@ -3,7 +3,7 @@ import { Octokit, RequestError as OctokitRequestError } from "octokit";
import type { fetch } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import { integrationOctokitHttpErrorHandler } from "../base/errors/http";
@@ -18,7 +18,7 @@ import type {
ReleaseResponse,
} from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GithubIntegration" });
const logger = createLogger({ module: "githubIntegration" });
@HandleIntegrationErrors([integrationOctokitHttpErrorHandler])
export class GithubIntegration extends Integration implements ReleasesProviderIntegration {
@@ -45,7 +45,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
private parseIdentifier(identifier: string) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Github integration`, {
logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", {
identifier,
});
return null;
@@ -64,7 +64,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name });
if (releasesResponse.data.length === 0) {
localLogger.warn(`No releases found, for ${owner}/${name} with Github integration`, {
logger.warn("No releases found", {
identifier: `${owner}/${name}`,
});
return { success: false, error: { code: "noMatchingVersion" } };
@@ -91,7 +91,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
return { success: true, data: { ...details, ...latestRelease } };
} catch (error) {
const errorMessage = error instanceof OctokitRequestError ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, {
logger.warn("Failed to get releases", {
owner,
name,
error: errorMessage,
@@ -122,7 +122,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
forksCount: response.data.forks_count,
};
} catch (error) {
localLogger.warn(`Failed to get details for ${owner}\\${name} with Github integration`, {
logger.warn("Failed to get details", {
owner,
name,
error: error instanceof OctokitRequestError ? error.message : String(error),

View File

@@ -4,7 +4,7 @@ import type { FormattedResponse, RequestOptions, ResourceOptions } from "@gitbea
import { Gitlab } from "@gitbeaker/rest";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -18,7 +18,7 @@ import type {
ReleaseResponse,
} from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GitlabIntegration" });
const logger = createLogger({ module: "gitlabIntegration" });
export class GitlabIntegration extends Integration implements ReleasesProviderIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
@@ -48,7 +48,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
});
if (releasesResponse instanceof Error) {
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
logger.warn("No releases found", {
identifier,
error: releasesResponse.message,
});
@@ -78,7 +78,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
return { success: true, data: { ...details, ...latestRelease } };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
logger.warn("Failed to get releases", {
identifier,
error: errorMessage,
});
@@ -91,7 +91,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
const response = await api.Projects.show(identifier);
if (response instanceof Error) {
localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, {
logger.warn("Failed to get details", {
identifier,
error: response.message,
});
@@ -100,7 +100,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
}
if (!response.web_url) {
localLogger.warn(`No web URL found for ${identifier} with Gitlab integration`, {
logger.warn("No web URL found", {
identifier,
});
return undefined;
@@ -117,7 +117,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
forksCount: response.forks_count,
};
} catch (error) {
localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, {
logger.warn("Failed to get details", {
identifier,
error: error instanceof Error ? error.message : String(error),
});

View File

@@ -2,7 +2,8 @@ import z from "zod";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -13,13 +14,15 @@ import type { ISmartHomeIntegration } from "../interfaces/smart-home/smart-home-
import type { CalendarEvent } from "../types";
import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homeassistant-types";
const logger = createLogger({ module: "homeAssistantIntegration" });
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration {
public async getEntityStateAsync(entityId: string) {
try {
const response = await this.getAsync(`/api/states/${entityId}`);
const body = await response.json();
if (!response.ok) {
logger.warn(`Response did not indicate success`);
logger.warn("Response did not indicate success");
return {
success: false as const,
error: "Response did not indicate success",
@@ -27,7 +30,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
}
return entityStateSchema.safeParseAsync(body);
} catch (err) {
logger.error(`Failed to fetch from ${this.url("/")}: ${err as string}`);
logger.error(new ErrorWithMetadata("Failed to fetch entity state", { entityId }, { cause: err }));
return {
success: false as const,
error: err,
@@ -43,7 +46,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
return response.ok;
} catch (err) {
logger.error(`Failed to fetch from '${this.url("/")}': ${err as string}`);
logger.error(new ErrorWithMetadata("Failed to trigger automation", { entityId }, { cause: err }));
return false;
}
}
@@ -62,7 +65,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
return response.ok;
} catch (err) {
logger.error(`Failed to fetch from '${this.url("/")}': ${err as string}`);
logger.error(new ErrorWithMetadata("Failed to toggle entity", { entityId }, { cause: err }));
return false;
}
}

View File

@@ -1,5 +1,5 @@
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -9,7 +9,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
import { releasesResponseSchema } from "./linuxserverio-schemas";
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
const logger = createLogger({ module: "linuxServerIOIntegration" });
export class LinuxServerIOIntegration extends Integration implements ReleasesProviderIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
@@ -27,10 +27,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
private parseIdentifier(identifier: string) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${identifier} with LinuxServerIO integration`,
{ identifier },
);
logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { identifier });
return null;
}
return { owner, name };
@@ -53,7 +50,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
if (!release) {
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
logger.warn("Repository not found on provider", {
name,
});
return { success: false, error: { code: "noMatchingVersion" } };

View File

@@ -1,7 +1,7 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { Integration } from "../../base/integration";
import type { IntegrationTestingInput } from "../../base/integration";
@@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
const logger = createLogger({ module: "lidarrIntegration" });
export class LidarrIntegration extends Integration implements ICalendarIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(this.url("/api"), {

View File

@@ -1,7 +1,7 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../../base/integration";
import { Integration } from "../../base/integration";
@@ -12,6 +12,8 @@ import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/cale
import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
const logger = createLogger({ module: "radarrIntegration" });
export class RadarrIntegration extends Integration implements ICalendarIntegration {
/**
* Gets the events in the Radarr calendar between two dates.

View File

@@ -1,7 +1,7 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { Integration } from "../../base/integration";
import type { IntegrationTestingInput } from "../../base/integration";
@@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
const logger = createLogger({ module: "readarrIntegration" });
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(this.url("/api"), {

View File

@@ -1,7 +1,7 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { Integration } from "../../base/integration";
import type { IntegrationTestingInput } from "../../base/integration";
@@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
const logger = createLogger({ module: "sonarrIntegration" });
export class SonarrIntegration extends Integration implements ICalendarIntegration {
/**
* Gets the events in the Sonarr calendar between two dates.

View File

@@ -7,7 +7,7 @@ import * as ical from "node-ical";
import { DAVClient } from "tsdav";
import { createHttpsAgentAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import { integrationTsdavHttpErrorHandler } from "../base/errors/http";
@@ -17,6 +17,8 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
const logger = createLogger({ module: "nextcloudIntegration" });
dayjs.extend(utc);
dayjs.extend(timezone);

View File

@@ -2,7 +2,7 @@ import type { Headers, HeadersInit, fetch as undiciFetch, Response as UndiciResp
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -13,7 +13,7 @@ import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-mo
import type { SystemHealthMonitoring } from "../types";
import { cpuTempSchema, fileSystemSchema, smartSchema, systemInformationSchema } from "./openmediavault-types";
const localLogger = logger.child({ module: "OpenMediaVaultIntegration" });
const logger = createLogger({ module: "openMediaVaultIntegration" });
type SessionStoreValue =
| { type: "header"; sessionId: string }
@@ -151,13 +151,13 @@ export class OpenMediaVaultIntegration extends Integration implements ISystemHea
const storedSession = await this.sessionStore.getAsync();
if (storedSession) {
localLogger.debug("Using stored session for request", { integrationId: this.integration.id });
logger.debug("Using stored session for request", { integrationId: this.integration.id });
const response = await callback(storedSession);
if (response.status !== 401) {
return response;
}
localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id });
logger.debug("Session expired, getting new session", { integrationId: this.integration.id });
}
const session = await this.getSessionAsync();

View File

@@ -1,7 +1,8 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -21,6 +22,8 @@ import {
UpstreamMediaRequestStatus,
} from "../interfaces/media-requests/media-request-types";
const logger = createLogger({ module: "overseerrIntegration" });
interface OverseerrSearchResult {
id: number;
name: string;
@@ -236,7 +239,7 @@ export class OverseerrIntegration
}
public async approveRequestAsync(requestId: number): Promise<void> {
logger.info(`Approving media request id='${requestId}' integration='${this.integration.name}'`);
logger.info("Approving media request", { requestId, integration: this.integration.name });
await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/approve`), {
method: "POST",
headers: {
@@ -245,16 +248,22 @@ export class OverseerrIntegration
}).then((response) => {
if (!response.ok) {
logger.error(
`Failed to approve media request id='${requestId}' integration='${this.integration.name}' reason='${response.status} ${response.statusText}' url='${response.url}'`,
new ErrorWithMetadata("Failed to approve media request", {
requestId,
integration: this.integration.name,
reason: `${response.status} ${response.statusText}`,
url: response.url,
}),
);
}
logger.info(`Successfully approved media request id='${requestId}' integration='${this.integration.name}'`);
logger.info("Successfully approved media request", { requestId, integration: this.integration.name });
});
}
public async declineRequestAsync(requestId: number): Promise<void> {
logger.info(`Declining media request id='${requestId}' integration='${this.integration.name}'`);
logger.info("Declining media request", { requestId, integration: this.integration.name });
await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/decline`), {
method: "POST",
headers: {
@@ -263,11 +272,16 @@ export class OverseerrIntegration
}).then((response) => {
if (!response.ok) {
logger.error(
`Failed to decline media request id='${requestId}' integration='${this.integration.name}' reason='${response.status} ${response.statusText}' url='${response.url}'`,
new ErrorWithMetadata("Failed to decline media request", {
requestId,
integration: this.integration.name,
reason: `${response.status} ${response.statusText}`,
url: response.url,
}),
);
}
logger.info(`Successfully declined media request id='${requestId}' integration='${this.integration.name}'`);
logger.info("Successfully declined media request", { requestId, integration: this.integration.name });
});
}

View File

@@ -3,7 +3,7 @@ import type { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationInput, IntegrationTestingInput } from "../../base/integration";
import { Integration } from "../../base/integration";
@@ -14,7 +14,7 @@ import type { DnsHoleSummaryIntegration } from "../../interfaces/dns-hole-summar
import type { DnsHoleSummary } from "../../types";
import { dnsBlockingGetSchema, sessionResponseSchema, statsSummaryGetSchema } from "./pi-hole-schemas-v6";
const localLogger = logger.child({ module: "PiHoleIntegrationV6" });
const logger = createLogger({ module: "piHoleIntegration", version: "v6" });
export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIntegration {
private readonly sessionStore: SessionStore<{ sid: string | null }>;
@@ -126,13 +126,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
const storedSession = await this.sessionStore.getAsync();
if (storedSession) {
localLogger.debug("Using stored session for request", { integrationId: this.integration.id });
logger.debug("Using stored session for request", { integrationId: this.integration.id });
const response = await callback(storedSession.sid);
if (response.status !== 401) {
return response;
}
localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id });
logger.debug("Session expired, getting new session", { integrationId: this.integration.id });
}
const sessionId = await this.getSessionAsync();
@@ -171,7 +171,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
);
}
localLogger.info("Received session id successfully", { integrationId: this.integration.id });
logger.info("Received session id successfully", { integrationId: this.integration.id });
return result.session.sid;
}
@@ -185,7 +185,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync,
) {
if (!sessionId) {
localLogger.debug("No session id to clear");
logger.debug("No session id to clear");
return;
}
@@ -197,7 +197,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
});
if (!response.ok) {
localLogger.warn("Failed to clear session", { statusCode: response.status, content: await response.text() });
logger.warn("Failed to clear session", { statusCode: response.status, content: await response.text() });
}
logger.debug("Cleared session successfully");

View File

@@ -3,8 +3,8 @@ import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ParseError } from "@homarr/common/server";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ImageProxy } from "@homarr/image-proxy";
import { logger } from "@homarr/log";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -15,6 +15,8 @@ import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-se
import type { IMediaReleasesIntegration, MediaRelease } from "../types";
import type { PlexResponse } from "./interface";
const logger = createLogger({ module: "plexIntegration" });
export class PlexIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
public async getCurrentSessionsAsync(_options: CurrentSessionsInput): Promise<StreamSession[]> {
const token = super.getSecretValue("apiKey");

View File

@@ -2,7 +2,7 @@ import type { Proxmox } from "proxmox-api";
import proxmoxApi from "proxmox-api";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import type { IntegrationTestingInput } from "../base/integration";
@@ -19,6 +19,8 @@ import type {
StorageResource,
} from "./proxmox-types";
const logger = createLogger({ module: "proxmoxIntegration" });
@HandleIntegrationErrors([new ProxmoxApiErrorHandler()])
export class ProxmoxIntegration extends Integration implements IClusterHealthMonitoringIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
@@ -31,9 +33,13 @@ export class ProxmoxIntegration extends Integration implements IClusterHealthMon
const proxmox = this.getPromoxApi();
const resources = await proxmox.cluster.resources.$get();
logger.info(
`Found ${resources.length} resources in Proxmox cluster node=${resources.filter((resource) => resource.type === "node").length} lxc=${resources.filter((resource) => resource.type === "lxc").length} qemu=${resources.filter((resource) => resource.type === "qemu").length} storage=${resources.filter((resource) => resource.type === "storage").length}`,
);
logger.info("Found resources in Proxmox cluster", {
total: resources.length,
node: resources.filter((resource) => resource.type === "node").length,
lxc: resources.filter((resource) => resource.type === "lxc").length,
qemu: resources.filter((resource) => resource.type === "qemu").length,
storage: resources.filter((resource) => resource.type === "storage").length,
});
const mappedResources = resources.map(mapResource).filter((resource) => resource !== null);
return {

View File

@@ -1,7 +1,7 @@
import type { RequestInit, Response } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -15,7 +15,7 @@ import type {
} from "../interfaces/releases-providers/releases-providers-types";
import { releasesResponseSchema } from "./quay-schemas";
const localLogger = logger.child({ module: "QuayIntegration" });
const logger = createLogger({ module: "quayIntegration" });
export class QuayIntegration extends Integration implements ReleasesProviderIntegration {
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
@@ -45,7 +45,7 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
private parseIdentifier(identifier: string) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Quay integration`, {
logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", {
identifier,
});
return null;

View File

@@ -3,7 +3,7 @@ import z from "zod";
import { createId } from "@homarr/common";
import { RequestError, ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
@@ -11,7 +11,7 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration";
import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types";
const localLogger = logger.child({ module: "TrueNasIntegration" });
const logger = createLogger({ module: "trueNasIntegration" });
const NETWORK_MULTIPLIER = 100;
@@ -45,14 +45,14 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
* @see https://www.truenas.com/docs/api/scale_websocket_api.html
*/
private async connectWebSocketAsync(): Promise<WebSocket> {
localLogger.debug("Connecting to websocket server", {
logger.debug("Connecting to websocket server", {
url: this.wsUrl(),
});
const webSocket = new WebSocket(this.wsUrl());
return new Promise((resolve, reject) => {
webSocket.onopen = () => {
localLogger.debug("Connected to websocket server", {
logger.debug("Connected to websocket server", {
url: this.wsUrl(),
});
resolve(webSocket);
@@ -97,7 +97,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#websocket_protocol
*/
private async authenticateWebSocketAsync(webSocket?: WebSocket): Promise<void> {
localLogger.debug("Authenticating with username and password", {
logger.debug("Authenticating with username and password", {
url: this.wsUrl(),
});
const response = await this.requestAsync(
@@ -107,7 +107,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
);
const result = await z.boolean().parseAsync(response);
if (!result) throw new ResponseError({ status: 401 });
localLogger.debug("Authenticated successfully with username and password", {
logger.debug("Authenticated successfully with username and password", {
url: this.wsUrl(),
});
}
@@ -117,7 +117,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#reporting
*/
private async getReportingAsync(): Promise<ReportingItem[]> {
localLogger.debug("Retrieving reporting data", {
logger.debug("Retrieving reporting data", {
url: this.wsUrl(),
});
@@ -141,7 +141,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
]);
const result = await z.array(reportingItemSchema).parseAsync(response);
localLogger.debug("Retrieved reporting data", {
logger.debug("Retrieved reporting data", {
url: this.wsUrl(),
count: result.length,
});
@@ -153,7 +153,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
* @see https://www.truenas.com/docs/core/13.0/api/core_websocket_api.html#interface
*/
private async getNetworkInterfacesAsync(): Promise<z.infer<typeof networkInterfaceSchema>> {
localLogger.debug("Retrieving available network-interfaces", {
logger.debug("Retrieving available network-interfaces", {
url: this.wsUrl(),
});
@@ -163,7 +163,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
]);
const result = await networkInterfaceSchema.parseAsync(response);
localLogger.debug("Retrieved available network-interfaces", {
logger.debug("Retrieved available network-interfaces", {
url: this.wsUrl(),
count: result.length,
});
@@ -177,7 +177,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
private async getReportingNetdataAsync(): Promise<z.infer<typeof reportingNetDataSchema>> {
const networkInterfaces = await this.getNetworkInterfacesAsync();
localLogger.debug("Retrieving reporting network data", {
logger.debug("Retrieving reporting network data", {
url: this.wsUrl(),
});
@@ -193,7 +193,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
]);
const result = await reportingNetDataSchema.parseAsync(response);
localLogger.debug("Retrieved reporting-network-data", {
logger.debug("Retrieved reporting-network-data", {
url: this.wsUrl(),
count: result.length,
});
@@ -205,14 +205,14 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#system
*/
private async getSystemInformationAsync(): Promise<z.infer<typeof systemInfoSchema>> {
localLogger.debug("Retrieving system-information", {
logger.debug("Retrieving system-information", {
url: this.wsUrl(),
});
const response = await this.requestAsync("system.info");
const result = await systemInfoSchema.parseAsync(response);
localLogger.debug("Retrieved system-information", {
logger.debug("Retrieved system-information", {
url: this.wsUrl(),
});
return result;
@@ -262,7 +262,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
private async requestAsync(method: string, params: unknown[] = [], webSocketOverride?: WebSocket) {
let webSocket = webSocketOverride ?? this.webSocket;
if (!webSocket || webSocket.readyState !== WebSocket.OPEN) {
localLogger.debug("Connecting to websocket", {
logger.debug("Connecting to websocket", {
url: this.wsUrl(),
});
// We can only land here with static webSocket
@@ -282,7 +282,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
clearTimeout(timeoutId);
webSocket.removeEventListener("message", handler);
localLogger.debug("Received method response", {
logger.debug("Received method response", {
id,
method,
url: this.wsUrl(),
@@ -305,7 +305,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
webSocket.addEventListener("message", handler);
localLogger.debug("Sending method request", {
logger.debug("Sending method request", {
id,
method,
url: this.wsUrl(),