diff --git a/CITATION.cff b/CITATION.cff index 63b9aebb..bb7bd9b9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -31,5 +31,5 @@ keywords: - web-ui - agent license: Apache-2.0 -version: 1.9.1 +version: 1.9.3 date-released: '2026-03-02' diff --git a/app/api/agent/[agentId]/backup/helpers.ts b/app/api/agent/[agentId]/backup/helpers.ts index 7c915ef2..4ecdf9b4 100644 --- a/app/api/agent/[agentId]/backup/helpers.ts +++ b/app/api/agent/[agentId]/backup/helpers.ts @@ -1,7 +1,10 @@ -import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; -import { db } from "@/db"; +import {NextResponse} from "next/server"; +import {eq} from "drizzle-orm"; +import {db} from "@/db"; import * as drizzleDb from "@/db"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/backup/helpers"}); export function withAgentCheck(handler: Function) { return async (request: Request, context: { params: Promise<{ agentId: string }> }) => { @@ -14,23 +17,22 @@ export function withAgentCheck(handler: Function) { if (!agent) { return NextResponse.json( - { error: "Agent not found" }, - { status: 404 } + {error: "Agent not found"}, + {status: 404} ); } - return handler(request, { ...context, agent }); + return handler(request, {...context, agent}); } catch (err) { - console.error("Error in agent middleware:", err); + log.error({error: err, name: "withAgentCheck"}, "Error in agent middleware"); return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } + {error: "Internal server error"}, + {status: 500} ); } }; } - export async function getDatabaseOrThrow(generatedId: string) { const database = await db.query.database.findFirst({ where: eq(drizzleDb.schemas.database.agentDatabaseId, generatedId), @@ -43,8 +45,8 @@ export async function getDatabaseOrThrow(generatedId: string) { if (!database) { throw NextResponse.json( - { error: "Database associated with generatedId not found" }, - { status: 404 } + {error: "Database associated with generatedId not found"}, + {status: 404} ); } diff --git a/app/api/agent/[agentId]/backup/route.ts b/app/api/agent/[agentId]/backup/route.ts index e5cc3931..b803ed1e 100644 --- a/app/api/agent/[agentId]/backup/route.ts +++ b/app/api/agent/[agentId]/backup/route.ts @@ -8,6 +8,9 @@ import {withUpdatedAt} from "@/db/utils"; import {eventEmitter} from "@/features/shared/event"; import {sendNotificationsBackupRestore} from "@/features/notifications/helpers"; import {EventKind} from "@/features/notifications/types"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/backup/route"}); export type BodyPost = { method: "manual" | "automatic" @@ -77,7 +80,6 @@ export const POST = withAgentCheck(async (request: Request, {params, agent}: { } } - eventEmitter.emit('modification', {update: true}); return NextResponse.json( @@ -88,7 +90,7 @@ export const POST = withAgentCheck(async (request: Request, {params, agent}: { {status: 200} ); } catch (error) { - console.error("Error in POST for INIT backup:", error); + log.error({error: error}, "Error in POST for INIT backup"); return NextResponse.json( {error: "Internal server error"}, {status: 500} @@ -102,7 +104,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: { }) => { try { const body: BodyPatch = await request.json(); - console.log("body", body); + log.debug({data: body}, "Body from PATH in backup route"); const status = body.status const backupId = body.backupId @@ -142,9 +144,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: { {status: 200} ); } catch (error) { - - - console.error("Error in PATCH backup:", error); + log.error({error: error}, "Error in PATCH backup") return NextResponse.json( {error: "Internal server error"}, {status: 500} diff --git a/app/api/agent/[agentId]/backup/upload/init/route.ts b/app/api/agent/[agentId]/backup/upload/init/route.ts index 3d0f7661..ef04fbcb 100644 --- a/app/api/agent/[agentId]/backup/upload/init/route.ts +++ b/app/api/agent/[agentId]/backup/upload/init/route.ts @@ -5,6 +5,9 @@ import {db} from "@/db"; import {getDatabaseOrThrow, withAgentCheck} from "../../helpers"; import {isUuidv4} from "@/utils/verify-uuid"; import {eventEmitter} from "@/features/shared/event"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/backup/upload/init"}); export type Body = { generatedId: string @@ -18,7 +21,7 @@ export const POST = withAgentCheck(async (request: Request, {params, agent}: { try { const body: Body = await request.json(); - console.log("body", body); + log.debug({data: body}, "Body for backup upload init"); const generatedId = body.generatedId; const storageChannelId = body.storageChannelId; @@ -66,7 +69,7 @@ export const POST = withAgentCheck(async (request: Request, {params, agent}: { {status: 200} ); } catch (error) { - console.error("Error in POST for INIT backup:", error); + log.error({error: error}, "Error in POST for INIT backup"); return NextResponse.json( {error: "Internal server error"}, {status: 500} diff --git a/app/api/agent/[agentId]/backup/upload/status/route.ts b/app/api/agent/[agentId]/backup/upload/status/route.ts index 55bbe0bc..881ac280 100644 --- a/app/api/agent/[agentId]/backup/upload/status/route.ts +++ b/app/api/agent/[agentId]/backup/upload/status/route.ts @@ -5,6 +5,9 @@ import {db as dbClient, db} from "@/db"; import {withUpdatedAt} from "@/db/utils"; import {getDatabaseOrThrow, withAgentCheck} from "../../helpers"; import {eventEmitter} from "@/features/shared/event"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/backup/upload/status"}); export type Body = { generatedId: string @@ -27,8 +30,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: { const backupStorageId = body.backupStorageId; const backupId = body.backupId; - - console.log("body", body); + log.debug({data: body}, "Body for backup upload status"); const database = await getDatabaseOrThrow(generatedId); @@ -85,7 +87,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: { {status: 200} ); } catch (error) { - console.error("Error in POST for INIT backup:", error); + log.error({error: error},"Error in POST for INIT backup"); return NextResponse.json( {error: "Internal server error"}, {status: 500} diff --git a/app/api/agent/[agentId]/restore/route.ts b/app/api/agent/[agentId]/restore/route.ts index 0fbc3dcc..e53660d8 100644 --- a/app/api/agent/[agentId]/restore/route.ts +++ b/app/api/agent/[agentId]/restore/route.ts @@ -4,7 +4,9 @@ import * as drizzleDb from "@/db"; import {db} from "@/db"; import {and, eq} from "drizzle-orm"; import {sendNotificationsBackupRestore} from "@/features/notifications/helpers"; -import {eventEmitter} from "@/features/shared/event"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/restore"}); export type BodyResultRestore = { generatedId: string @@ -70,10 +72,9 @@ export async function POST( message: "Restoration successfully updated" } - return Response.json(response, {status: 200}) } catch (error) { - console.error('Error in POST handler:', error); + log.error({error: error}, "Error in POST handler") return NextResponse.json( {error: 'Internal server error'}, {status: 500} diff --git a/app/api/agent/[agentId]/status/helpers.ts b/app/api/agent/[agentId]/status/helpers.ts index e3eee676..3e77507e 100644 --- a/app/api/agent/[agentId]/status/helpers.ts +++ b/app/api/agent/[agentId]/status/helpers.ts @@ -2,7 +2,7 @@ import {NextResponse} from "next/server"; import {Body} from "./route"; import {isUuidv4} from "@/utils/verify-uuid"; import {Agent} from "@/db/schema/08_agent"; -import {Database, DatabaseWith} from "@/db/schema/07_database"; +import {DatabaseWith} from "@/db/schema/07_database"; import * as drizzleDb from "@/db"; import {db, db as dbClient} from "@/db"; import {and, eq, inArray} from "drizzle-orm"; @@ -11,6 +11,9 @@ import {withUpdatedAt} from "@/db/utils"; import type {StorageInput} from "@/features/storages/types"; import {dispatchStorage} from "@/features/storages/dispatch"; import {Setting} from "@/db/schema/01_setting"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/agent/status/helpers"}); export async function handleDatabases(body: Body, agent: Agent, lastContact: Date, settings: Setting) { const databasesResponse = []; @@ -56,7 +59,7 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat } if (!dbmsEnumSchema.safeParse(db.dbms).success) { - console.log(`Database type not available: ${db.dbms}`); + log.error({name: "handleDatabases"},`Database type not available: ${db.dbms}`); continue; } @@ -85,7 +88,6 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat date: lastContact }) - const storages = await getDatabaseStorageChannels(databaseCreated.id) databasesResponse.push(formatDatabase(databaseCreated, backupAction, restoreAction, urlBackup, storages, null)); @@ -185,11 +187,11 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat .where(eq(drizzleDb.schemas.restoration.id, restoration.id)); const errorMessage = "Failed to get backup URL"; - console.error("Restoration failed: ", errorMessage); + log.error({error: errorMessage, name: "handleDatabases"}, "Restoration failed"); continue; } } catch (err) { - console.error("Restoration crashed unexpectedly:", err); + log.error({error: err, name: "handleDatabases"}, "Restoration crashed unexpectedly"); await dbClient .update(drizzleDb.schemas.restoration) .set({status: "failed"}) diff --git a/app/api/agent/[agentId]/status/route.ts b/app/api/agent/[agentId]/status/route.ts index 3bc42245..04a4fa86 100644 --- a/app/api/agent/[agentId]/status/route.ts +++ b/app/api/agent/[agentId]/status/route.ts @@ -6,8 +6,11 @@ import {EDbmsSchema} from "@/db/schema/types"; import {eq} from "drizzle-orm"; import {isUuidv4} from "@/utils/verify-uuid"; import {withUpdatedAt} from "@/db/utils"; -import {eventEmitter} from "@/features/shared/event"; -import {notFound} from "next/navigation"; +import {logger} from "@/lib/logger"; + + +const log = logger.child({module: "api/agent/status/route"}); + export type databaseAgent = { name: string, @@ -28,14 +31,14 @@ export async function POST( ) { try { const agentId = (await params).agentId - console.log(agentId) + log.debug(`Agent ID: ${agentId}`) const body: Body = await request.json(); const lastContact = new Date(); let message: string if (!isUuidv4(agentId)) { message = "agentId is not a valid uuid" - console.error(message) + log.error({error: message}, "An error occurred") return NextResponse.json( {error: "agentId is not a valid uuid"}, {status: 500} @@ -56,7 +59,6 @@ export async function POST( return NextResponse.json({error: "An error occured"}, {status: 404}) } - const databasesResponse = await handleDatabases(body, agent, lastContact, settings) await db @@ -85,10 +87,9 @@ export async function POST( databases: databasesResponse } - return Response.json(response) } catch (error) { - console.error('Error in POST handler:', error); + log.error({error: error}, "Error in POST handler") return NextResponse.json( {error: 'Internal server error'}, {status: 500} diff --git a/app/api/events/route.ts b/app/api/events/route.ts index 76f22a08..d173adf8 100644 --- a/app/api/events/route.ts +++ b/app/api/events/route.ts @@ -2,6 +2,9 @@ import {auth} from "@/lib/auth/auth"; import {headers} from "next/headers"; import {NextResponse} from "next/server"; import {eventEmitter} from "@/features/shared/event"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/events"}); export async function GET(request: Request) { @@ -16,9 +19,9 @@ export async function GET(request: Request) { return new Response( new ReadableStream({ start(controller) { - console.log('Stream started'); + log.info("Stream started"); const handleModification = (data: any) => { - console.log('Modification event triggered:', data); + log.info({data: data},"Modification event triggered"); controller.enqueue(`event: modification\n`); controller.enqueue(`data: ${JSON.stringify(data)}\n\n`); }; @@ -26,7 +29,7 @@ export async function GET(request: Request) { eventEmitter.on('modification', handleModification); request.signal.addEventListener('abort', () => { - console.log('Client disconnected'); + log.info("Client disconnected"); controller.close(); eventEmitter.off('modification', handleModification); }); diff --git a/app/api/files/backups/route.ts b/app/api/files/backups/route.ts index aad11901..6adf782c 100644 --- a/app/api/files/backups/route.ts +++ b/app/api/files/backups/route.ts @@ -3,6 +3,9 @@ import path from "path"; import type {StorageInput} from "@/features/storages/types"; import {dispatchStorage} from "@/features/storages/dispatch"; import {Readable} from "node:stream"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/files/backups"}); export async function GET( request: Request, @@ -30,7 +33,7 @@ export async function GET( } }; - console.debug(input); + log.debug({input: input}, "Dispatch Storage"); const result = await dispatchStorage(input, undefined, storageId); diff --git a/app/api/files/images/[fileName]/route.ts b/app/api/files/images/[fileName]/route.ts index f1464397..0cc3798d 100644 --- a/app/api/files/images/[fileName]/route.ts +++ b/app/api/files/images/[fileName]/route.ts @@ -1,12 +1,12 @@ import {NextResponse} from "next/server"; import {auth} from "@/lib/auth/auth"; import {headers} from "next/headers"; -import {db} from "@/db"; -import * as drizzleDb from "@/db"; -import {eq} from "drizzle-orm"; import {StorageInput} from "@/features/storages/types"; import {dispatchStorage} from "@/features/storages/dispatch"; import {Readable} from "node:stream"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "api/files/images"}); export async function GET( req: Request, @@ -56,7 +56,7 @@ export async function GET( const result = await dispatchStorage(input, undefined, storageId); if (!result.file || !(result.file instanceof Readable)) { - console.error(`An error occurred while getting file :`, result); + log.error({error: result}, `An error occurred while getting file`); return NextResponse.json( {error: "Invalid file payload"}, {status: 500} @@ -82,7 +82,7 @@ export async function GET( }); } catch (err) { - console.error("Error streaming image:", err); + log.error({error: err}, `Error streaming image`); return NextResponse.json({error: "Error fetching file"}, {status: 500}); } } \ No newline at end of file diff --git a/app/api/tus/hooks/route.ts b/app/api/tus/hooks/route.ts index c711270c..56b54b0e 100644 --- a/app/api/tus/hooks/route.ts +++ b/app/api/tus/hooks/route.ts @@ -2,7 +2,9 @@ import {NextResponse} from "next/server"; import fs from "fs"; import path from "path"; import {env} from "@/env.mjs"; +import {logger} from "@/lib/logger"; +const log = logger.child({module: "api/tus/hooks"}); export async function POST(request: Request) { try { @@ -13,7 +15,7 @@ export async function POST(request: Request) { const uploadOffset = headers["Upload-Offset"]?.[0]; const status = headers["X-Status"]?.[0]; - console.log(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`); + log.info(`Upload ID : ${event.Upload.ID} (${uploadOffset}/${uploadLength})`); if (status === "success") { if ( @@ -29,9 +31,7 @@ export async function POST(request: Request) { return NextResponse.json({error: "Missing X-File-Path"}, {status: 500}); } - - // const uploadDir = path.join(process.cwd(), "/private/uploads/"); - const uploadDir = path.join(env.PRIVATE_PATH, "/uploads/"); + const uploadDir = path.join(env.PRIVATE_PATH!, "/uploads/"); const oldFilePath = path.join(uploadDir, "tmp", id); const newFilePath = path.join(uploadDir, filePath); @@ -74,7 +74,7 @@ export async function POST(request: Request) { } return NextResponse.json({}); } catch (error) { - console.error("Hook error:", error); + log.error({error: error},"TUS Hook error"); return NextResponse.json({error: "Internal server error"}, {status: 500}); } } \ No newline at end of file diff --git a/package.json b/package.json index ddda1b41..0d43fad9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "portabase", - "version": "1.9.1", + "version": "1.9.3", "private": true, "scripts": { "dev": "next dev --turbopack -p 8887", @@ -78,6 +78,8 @@ "nodemailer": "^7.0.13", "npm-check-updates": "^18.3.1", "pg": "^8.20.0", + "pino": "^10.3.1", + "pino-pretty": "^13.1.3", "prettier": "^3.8.1", "react": "^19.2.4", "react-day-picker": "9.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56fc25db..40693f40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -197,6 +197,12 @@ importers: pg: specifier: ^8.20.0 version: 8.20.0 + pino: + specifier: ^10.3.1 + version: 10.3.1 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -2010,6 +2016,9 @@ packages: resolution: {integrity: sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==} engines: {node: ^20.9.0 || >=22.0.0, npm: '>=10.8.2'} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} @@ -3722,6 +3731,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + attr-accept@2.2.5: resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} engines: {node: '>=4'} @@ -4077,6 +4090,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} @@ -4266,6 +4282,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debounce@2.2.0: resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==} engines: {node: '>=18'} @@ -4830,6 +4849,9 @@ packages: fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} @@ -4854,6 +4876,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -5143,6 +5168,9 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -5448,6 +5476,10 @@ packages: jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6068,6 +6100,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -6252,6 +6288,20 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -6374,6 +6424,9 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -6426,6 +6479,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -6563,6 +6619,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} @@ -6668,6 +6728,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -6688,6 +6752,9 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -6802,6 +6869,9 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + sonner@2.0.3: resolution: {integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==} peerDependencies: @@ -6942,6 +7012,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strnum@2.2.2: resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} @@ -7044,6 +7118,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -8869,6 +8947,8 @@ snapshots: '@phun-ky/typeof@2.0.3': {} + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.58.2': dependencies: playwright: 1.58.2 @@ -11206,6 +11286,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + attr-accept@2.2.5: {} autoprefixer@10.4.21(postcss@8.5.8): @@ -11578,6 +11660,8 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + colors@1.4.0: {} commander@13.1.0: {} @@ -11770,6 +11854,8 @@ snapshots: date-fns@4.1.0: {} + dateformat@4.6.3: {} + debounce@2.2.0: {} debug@3.2.7: @@ -12507,6 +12593,8 @@ snapshots: fast-content-type-parse@3.0.0: {} + fast-copy@4.0.2: {} + fast-deep-equal@2.0.1: {} fast-deep-equal@3.1.3: {} @@ -12533,6 +12621,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fast-xml-builder@1.1.4: @@ -12838,6 +12928,8 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 + help-me@5.0.0: {} + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -13138,6 +13230,8 @@ snapshots: jose@6.2.2: {} + joycon@3.1.1: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -13676,6 +13770,8 @@ snapshots: ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -13899,6 +13995,42 @@ snapshots: pify@2.3.0: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + pirates@4.0.7: {} pkg-types@2.3.0: @@ -14003,6 +14135,8 @@ snapshots: prismjs@1.30.0: {} + process-warning@5.0.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -14076,6 +14210,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -14258,6 +14394,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + recharts-scale@0.4.5: dependencies: decimal.js-light: 2.5.1 @@ -14403,6 +14541,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} samlify@2.11.0: @@ -14429,6 +14569,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + secure-json-parse@4.1.0: {} + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -14643,6 +14785,10 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + sonner@2.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 @@ -14800,6 +14946,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strnum@2.2.2: {} styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.0.0): @@ -14927,6 +15075,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through2@4.0.2: dependencies: readable-stream: 3.6.2 diff --git a/src/db/index.ts b/src/db/index.ts index e7bdf7f4..275d38c2 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -17,12 +17,15 @@ import * as storagePolicy from "@/db/schema/13_storage-policy"; import * as backupStorage from "@/db/schema/14_storage-backup"; import * as healthcheckLog from "@/db/schema/15_healthcheck-log"; +const log = logger.child({module: "db"}); + import {Pool} from "pg"; // Do not delete import dotenv from "dotenv"; import {migrate} from "drizzle-orm/node-postgres/migrator"; +import {logger} from "@/lib/logger"; dotenv.config({ path: ".env", @@ -66,12 +69,12 @@ export async function makeMigration() { const database = drizzle({client: pool}); - console.log("Running migrations..."); + log.info("Running migrations..."); try { await migrate(database, {migrationsFolder: "./src/db/migrations"}); - console.log("Migrations applied successfully."); + log.info("Migrations applied successfully."); } catch (error) { - console.error("Error applying migrations:", error); + log.error({error: error}, "Error applying migrations:"); } } } diff --git a/src/db/services/healthcheck.ts b/src/db/services/healthcheck.ts index 748487cb..8bc8f371 100644 --- a/src/db/services/healthcheck.ts +++ b/src/db/services/healthcheck.ts @@ -3,6 +3,9 @@ import * as drizzleDb from "@/db"; import {and, eq, gte, isNotNull, lt} from "drizzle-orm"; import {dispatchNotification} from "@/features/notifications/dispatch"; import {EventPayload} from "@/features/notifications/types"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks/healthcheck"}); export async function getHealthLast12hLogs({id}: { id: string }) { const now = new Date() @@ -19,7 +22,6 @@ export async function getHealthLast12hLogs({id}: { id: string }) { ) } - export async function deleteHealthLogsOlderThan12h() { const now = new Date() const threshold = new Date(now.getTime() - 12 * 60 * 60 * 1000) @@ -31,7 +33,7 @@ export async function deleteHealthLogsOlderThan12h() { lt(drizzleDb.schemas.healthcheckLog.date, threshold) ) - console.log(`Number of logs found to delete: ${logsToDelete.length}`) + log.info({name: "deleteHealthLogsOlderThan12h"},`Number of logs found to delete: ${logsToDelete.length}`) await db .delete(drizzleDb.schemas.healthcheckLog) @@ -56,7 +58,7 @@ export async function checkAgentsHealthError() { } if (!settings.defaultNotificationChannelId) { - console.error("No default notification channel id found."); + log.error({name: "checkAgentsHealthError"},`No default notification channel id found.`) return } @@ -89,8 +91,7 @@ export async function checkAgentsHealthError() { error: "Agent is down", }, }; - - console.log("[Agent Healthcheck] :", payload); + log.info({name: "checkAgentsHealthError", payload: payload},`Agent Healthcheck Notification`) await dispatchNotification( payload, @@ -156,7 +157,6 @@ export async function checkDatabasesHealthError() { continue } - const promises = policiesToUse.map(alertPolicy => { const payload: EventPayload = { @@ -171,15 +171,13 @@ export async function checkDatabasesHealthError() { }, }; - console.log("[Database Healthcheck] :", payload); + log.info({name: "checkDatabasesHealthError", payload: payload},`Database Healthcheck Notification`) return dispatchNotification(payload, alertPolicy.id == null ? undefined : alertPolicy.id, alertPolicy.id ? undefined : alertPolicy.notificationChannelId, undefined); }); await Promise.all(promises); - } - } } } diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 00000000..29980f36 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,46 @@ +import pino, { type Logger } from "pino"; + +const isProd = process.env.NODE_ENV === "production"; + +function getLocalTimestamp() { + const date = new Date(); + + const formatted = new Intl.DateTimeFormat("en-US", { + timeZone: process.env.TZ, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit" + }).format(date).replace(" ", "T"); + + return `,"time":"${formatted}"`; +} + +export const logger: Logger = pino({ + level: isProd ? "info" : "debug", + base: null, + + ...(isProd + ? { + timestamp: getLocalTimestamp, + formatters: { + level(label) { + return { level: label.toUpperCase() }; + } + } + } + : { + timestamp: getLocalTimestamp, + transport: { + target: "pino-pretty", + options: { + colorize: true, + translateTime: "yyyy-mm-dd HH:MM:ss", + ignore: "pid,hostname", + levelFirst: true + } + } + }) +}); diff --git a/src/lib/tasks/cleaning/index.ts b/src/lib/tasks/cleaning/index.ts index 192f6d4a..65b25af2 100644 --- a/src/lib/tasks/cleaning/index.ts +++ b/src/lib/tasks/cleaning/index.ts @@ -2,6 +2,9 @@ import {db} from "@/db"; import {and, eq, isNotNull, isNull} from "drizzle-orm"; import * as drizzleDb from "@/db"; import {withUpdatedAt} from "@/db/utils"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks/cleaning"}); export const backupCleanTask = async () => { try { @@ -11,8 +14,7 @@ export const backupCleanTask = async () => { eq(drizzleDb.schemas.backup.status, "ongoing") ) }); - - console.log(`Backups to clean: ${backups.length}`); + log.info(`Backups to clean: ${backups.length}`); for (const backup of backups) { await db.update(drizzleDb.schemas.backup).set(withUpdatedAt({ @@ -22,7 +24,7 @@ export const backupCleanTask = async () => { } } catch (e: any) { - console.error("Backup cleanup failed:", e); + log.info({name: "backupCleanTask", error: e},`Backup cleanup failed`); throw e; } }; \ No newline at end of file diff --git a/src/lib/tasks/database/index.ts b/src/lib/tasks/database/index.ts index 6ee92a26..337bf469 100644 --- a/src/lib/tasks/database/index.ts +++ b/src/lib/tasks/database/index.ts @@ -5,7 +5,9 @@ import {enforceRetentionGFS} from "@/lib/tasks/database/retention-gsf"; import {retentionPolicy} from "@/db/schema/07_database"; import {isNull} from "drizzle-orm"; import * as drizzleDb from "@/db"; +import {logger} from "@/lib/logger"; +const log = logger.child({module: "tasks/database"}); export const retentionCleanTask = async () => { try { @@ -17,13 +19,13 @@ export const retentionCleanTask = async () => { }, }, }); - console.log(`Retention databases number: ${databases.length}`); + log.info(`Retention databases number: ${databases.length}`); for (const db of databases) { if (!db.retentionPolicy) continue; await enforceRetention(db.id, db.retentionPolicy); } } catch (e: any) { - console.error("Retention cleanup failed:", e); + log.error({error: e},"Retention cleanup failed"); throw e; } }; @@ -32,7 +34,9 @@ export async function enforceRetention( databaseId: string, policy: typeof retentionPolicy.$inferSelect ) { - console.log(`Retention started for ${databaseId}`); +; + log.info({name: "enforceRetention"},`Retention started for ${databaseId}`); + switch (policy.type) { case "count": await enforceRetentionCount(databaseId, policy.count ?? 7); diff --git a/src/lib/tasks/database/retention-count.ts b/src/lib/tasks/database/retention-count.ts index 2e5ca093..92aa44d1 100644 --- a/src/lib/tasks/database/retention-count.ts +++ b/src/lib/tasks/database/retention-count.ts @@ -2,10 +2,12 @@ import * as drizzleDb from "@/db"; import {db} from "@/db"; import {and, desc, eq, isNull} from "drizzle-orm"; import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete"; -import {toast} from "sonner"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks/database/retention-count"}); export async function enforceRetentionCount(databaseId: string, count: number) { - console.log(`[Retention Count] - ${databaseId} : started`); + log.info({ name: "enforceRetentionCount"}, `Retention count started for databaseId: ${databaseId}`); const backups = await db.query.backup.findMany({ where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)), orderBy: desc(drizzleDb.schemas.backup.createdAt), @@ -19,7 +21,7 @@ export async function enforceRetentionCount(databaseId: string, count: number) { }); const toDelete = backups.slice(count); - console.log(`[Retention Count] - ${databaseId} : ${toDelete.length} backups to delete`); + log.info({ name: "enforceRetentionCount"}, `Found ${toDelete.length} backups to delete for databaseId: ${databaseId}`); for (const b of toDelete) { const result = await deleteBackupCronAction({ @@ -29,9 +31,9 @@ export async function enforceRetentionCount(databaseId: string, count: number) { const inner = result?.data; if (inner?.success) { - console.log(`[Retention Count] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`); + log.info({ name: "enforceRetentionCount"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`); } else { - console.log(`[Retention Count] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`); + log.info({ name: "enforceRetentionCount"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`); } } } \ No newline at end of file diff --git a/src/lib/tasks/database/retention-days.ts b/src/lib/tasks/database/retention-days.ts index d553fb1e..198fa223 100644 --- a/src/lib/tasks/database/retention-days.ts +++ b/src/lib/tasks/database/retention-days.ts @@ -2,9 +2,13 @@ import {db} from "@/db"; import {eq, lt, and, desc, isNull} from "drizzle-orm"; import * as drizzleDb from "@/db"; import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks/database/retention-days"}); export async function enforceRetentionDays(databaseId: string, days: number) { - console.log(`Enforce Retention Days starting for ${databaseId}`); + log.info({ name: "enforceRetentionDays"}, `Enforce Retention Days starting for ${databaseId}`); + const cutoff = new Date(Date.now() - days * 86400000); const expiredBackups = await db.query.backup.findMany({ @@ -31,9 +35,9 @@ export async function enforceRetentionDays(databaseId: string, days: number) { const inner = result?.data; if (inner?.success) { - console.log(`[Retention Days] - (databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : successfully deleted`); + log.info({ name: "enforceRetentionDays"}, `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : successfully deleted`); } else { - console.log(`[Retention Days] - (databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : an error occurred - ${inner?.actionError?.message}`); + log.info({ name: "enforceRetentionDays"}, `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : an error occurred - ${inner?.actionError?.message}`); } } diff --git a/src/lib/tasks/database/retention-gsf.ts b/src/lib/tasks/database/retention-gsf.ts index c765c5b7..e3b0271c 100644 --- a/src/lib/tasks/database/retention-gsf.ts +++ b/src/lib/tasks/database/retention-gsf.ts @@ -3,6 +3,9 @@ import {subDays, subWeeks, subMonths, subYears, startOfWeek, startOfMonth, start import {eq, desc, isNull, and} from "drizzle-orm"; import * as drizzleDb from "@/db"; import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks/database/retention-gsf"}); export async function enforceRetentionGFS(databaseId: string, gfsSettings: { daily: number; @@ -10,7 +13,7 @@ export async function enforceRetentionGFS(databaseId: string, gfsSettings: { monthly: number; yearly: number; }) { - console.log(`Enforce Retention GFS starting for ${databaseId}`); + log.info({ name: "enforceRetentionGFS"}, `Retention GFS started for databaseId: ${databaseId}`); const backups = await db.query.backup.findMany({ where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)), @@ -69,9 +72,9 @@ export async function enforceRetentionGFS(databaseId: string, gfsSettings: { const inner = result?.data; if (inner?.success) { - console.log(`[Retention GFS] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`); + log.info({ name: "enforceRetentionGFS"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`); } else { - console.log(`[Retention GFS] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`); + log.info({ name: "enforceRetentionGFS"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`); } } } diff --git a/src/lib/tasks/index.ts b/src/lib/tasks/index.ts index 9f42002e..3200b056 100644 --- a/src/lib/tasks/index.ts +++ b/src/lib/tasks/index.ts @@ -7,41 +7,44 @@ import { checkDatabasesHealthError, deleteHealthLogsOlderThan12h } from "@/db/services/healthcheck"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "tasks"}); export const retentionJob = cron.schedule(env.RETENTION_CRON, async () => { try { - console.log("Retention Job : Starting task"); + log.info({ job: "cron", action: "start", name: "retentionJob" }, "Retention Job started"); await retentionCleanTask(); } catch (err) { - console.error(`[CRON] Error:`, err); + log.error({ job: "cron", name: "retentionJob", error: err }, "Retention Job Error"); } }); export const cleaningJob = cron.schedule("* * * * *", async () => { try { - console.log("Cleaning Job : Starting task"); + log.info({ job: "cron", action: "start", name: "cleaningJob" }, "Cleaning Job started"); await backupCleanTask(); } catch (err) { - console.error(`[CRON] Error:`, err); + log.error({ job: "cron", name: "cleaningJob", error: err }, "Cleaning Job Error"); } }); export const cleaningHealthcheckLogsJob = cron.schedule(env.CLEANING_HEALTHCHECK_LOGS_CRON, async () => { try { - console.log("Cleaning Healthcheck Logs Job : Starting task"); + log.info({ job: "cron", action: "start", name: "cleaningHealthcheckLogsJob" }, "Cleaning Health Logs Job started"); await deleteHealthLogsOlderThan12h(); } catch (err) { - console.error(`[CRON] Error:`, err); + log.error({ job: "cron", name: "cleaningHealthcheckLogsJob", error: err }, "Cleaning Health Logs Job Error"); } }); export const healthcheckAgentAndDatabaseJob = cron.schedule(env.HEALTHCHECK_CRON, async () => { try { - console.log("Healthcheck Job : Starting task"); + log.info({ job: "cron", action: "start", name: "healthcheckAgentAndDatabaseJob" }, "Healthcheck Jobs started"); await checkAgentsHealthError(); await checkDatabasesHealthError() } catch (err) { - console.error(`[CRON] Error:`, err); + log.error({ job: "cron", name: "healthcheckAgentAndDatabaseJob", error: err }, "Healthcheck Jobs Error"); } }); \ No newline at end of file diff --git a/src/utils/init.ts b/src/utils/init.ts index 90859059..b18de2a0 100644 --- a/src/utils/init.ts +++ b/src/utils/init.ts @@ -5,52 +5,45 @@ import * as drizzleDb from "@/db"; import {cleaningHealthcheckLogsJob, cleaningJob, healthcheckAgentAndDatabaseJob, retentionJob} from "@/lib/tasks"; import { generateRSAKeys, getOrCreateMasterKey } from "@/utils/rsa-keys"; import { StorageProviderKind } from "@/features/storages/types"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "init"}); export async function init() { consoleAscii(); - console.log("====Init Functions===="); + + log.info("====Init Functions===="); await getOrCreateMasterKey(); await generateRSAKeys(); await makeMigration(); await createDefaultOrganization(); await createSettingsIfNotExist(); - console.log("====Initialization completed===="); + log.info("====Initialization completed===="); await setupCronJobs(); - await setupCleaningJobs(); - await setupCleaningHealthLogsJobs(); - await setupHealthCheckJobs(); if ( (env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET) || (env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET) ) { - console.warn( - "[Deprecation Warning] You have set up OAuth credentials in your environment variables, but the format is now different. Please update your environment variables to use the new format. For example, if you were using AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET, you should now use AUTH_SOCIAL_GOOGLE_CLIENT and AUTH_SOCIAL_GOOGLE_SECRET. Please refer to the documentation for more details. (https://portabase.io/docs/dashboard/auth/oauth2/setup#dynamic-providers)", + log.warn( + { + deprecated: true, + provider: "oauth_env", + message: "You have set up OAuth credentials in your environment variables, but the format is now different. Please update your environment variables to use the new format. For example, if you were using AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET, you should now use AUTH_SOCIAL_GOOGLE_CLIENT and AUTH_SOCIAL_GOOGLE_SECRET. Please refer to the documentation for more details. (https://portabase.io/docs/dashboard/auth/oauth2/setup#dynamic-providers)" + }, + "Deprecated OAuth environment variables detected", ); } } async function setupCronJobs() { - console.log("==== Setting up Cron Jobs ===="); + + log.info("==== Setting up Cron Jobs ===="); retentionJob.start(); - console.log("==== Cron job started ===="); -} - -async function setupCleaningJobs() { - console.log("==== Setting up Cleaning Jobs ===="); cleaningJob.start(); - console.log("==== Cleaning job started ===="); -} - -async function setupCleaningHealthLogsJobs() { - console.log("==== Setting up Cleaning Healthcheck Logs Jobs ===="); cleaningHealthcheckLogsJob.start(); - console.log("==== Cleaning Healthcheck Logs job started ===="); -} -async function setupHealthCheckJobs() { - console.log("==== Setting up Healthcheck Jobs ===="); healthcheckAgentAndDatabaseJob.start(); - console.log("==== Cleaning Healthcheck job started ===="); + log.info("==== Cron jobs started ===="); } async function createSettingsIfNotExist() { @@ -129,7 +122,7 @@ async function createDefaultOrganization() { .limit(1); if (!existing) { - console.log("==== Creating default Organization... ===="); + log.info("==== Creating default Organization... ===="); await db .insert(drizzleDb.schemas.organization) .values(defaultOrganizationConf); diff --git a/src/utils/rsa-keys.ts b/src/utils/rsa-keys.ts index f3e5cdd3..12065ba1 100644 --- a/src/utils/rsa-keys.ts +++ b/src/utils/rsa-keys.ts @@ -4,6 +4,11 @@ import {generateKeyPair} from 'crypto'; import {promisify} from 'util'; import {randomBytes} from 'crypto'; import {env} from "@/env.mjs"; +import {logger} from "@/lib/logger"; + +const log = logger.child({module: "rsa-keys"}); + + const generateKeyPairAsync = promisify(generateKeyPair); @@ -14,17 +19,16 @@ const generateKeyPairAsync = promisify(generateKeyPair); * @param {string} [dir] path to directory * @returns {Promise<{privateKeyPath:string, publicKeyPath:string}>} */ -export async function generateRSAKeys(dir = path.join(env.PRIVATE_PATH, '/keys')) { +export async function generateRSAKeys(dir = path.join(env.PRIVATE_PATH!, '/keys')) { await fs.mkdir(dir, {recursive: true}); - const privateKeyPath = path.join(dir, 'server_private.pem'); const publicKeyPath = path.join(dir, 'server_public.pem'); try { await fs.access(privateKeyPath); await fs.access(publicKeyPath); - console.log('RSA keys already exist. Skipping generation.'); + log.info('RSA keys already exist. Skipping generation.'); return {privateKeyPath, publicKeyPath}; } catch { } @@ -49,13 +53,13 @@ export async function generateRSAKeys(dir = path.join(env.PRIVATE_PATH, '/keys') * @param {string} [filePath] Path to store the key * @returns {Promise} The master key */ -export async function getOrCreateMasterKey(filePath = path.join(env.PRIVATE_PATH, '/keys', 'master_key.bin')) { +export async function getOrCreateMasterKey(filePath = path.join(env.PRIVATE_PATH!, '/keys', 'master_key.bin')) { await fs.mkdir(path.dirname(filePath), {recursive: true}); try { const existing = await fs.readFile(filePath); - console.log('Master key already exists. Skipping generation.'); + log.info('Master key already exists. Skipping generation.'); return existing; } catch { // File does not exist, generate @@ -64,7 +68,8 @@ export async function getOrCreateMasterKey(filePath = path.join(env.PRIVATE_PATH const key = randomBytes(32); // 256-bit key await fs.writeFile(filePath, key, {mode: 0o600}); - console.log(`Master key generated at ${filePath}`); + log.info("Master key already exists. Skipping generation."); return key; -} \ No newline at end of file +} +