feat: Add centralized logging system with @link-stack/logger package

- Create new @link-stack/logger package wrapping Pino for structured logging
- Replace all console.log/error/warn statements across the monorepo
- Configure environment-aware logging (pretty-print in dev, JSON in prod)
- Add automatic redaction of sensitive fields (passwords, tokens, etc.)
- Remove dead commented-out logger file from bridge-worker
- Follow Pino's standard argument order (context object first, message second)
- Support log levels via LOG_LEVEL environment variable
- Export TypeScript types for better IDE support

This provides consistent, structured logging across all applications
and packages, making debugging easier and production logs more parseable.
This commit is contained in:
Darren Clarke 2025-08-20 11:37:39 +02:00
parent 5b89bfce7c
commit c1feaa4cb1
42 changed files with 3824 additions and 2422 deletions

View file

@ -1,6 +1,9 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { Home } from "./_components/Home";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-page');
// import { getServerSession } from "app/_lib/authentication";
// import { Home } from "@link-stack/leafcutter-ui";
// import { getUserVisualizations } from "@link-stack/opensearch-common";
@ -28,7 +31,7 @@ export default async function Page() {
try {
visualizations = await getUserVisualizations(email ?? "none", 20);
} catch (e) {
console.error(e.meta);
logger.error({ meta: e.meta }, "Error metadata");
}
return (

View file

@ -1,6 +1,9 @@
"use server";
import { executeREST } from "app/_lib/zammad";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-groups');
export const getGroupsAction = async () => {
try {
@ -15,7 +18,7 @@ export const getGroupsAction = async () => {
return formattedGroups;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};

View file

@ -3,6 +3,9 @@
import { executeGraphQL, executeREST } from "app/_lib/zammad";
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
import { getTicketsByOverviewQuery } from "app/_graphql/getTicketsByOverviewQuery";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-overviews');
const overviewLookup = {
Assigned: "My Assigned Tickets",
@ -36,7 +39,7 @@ export const getOverviewTicketCountsAction = async () => {
return counts;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {};
}
};
@ -91,7 +94,7 @@ export const getOverviewTicketsAction = async (name: string) => {
return { tickets: sortedTickets };
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return { tickets, message: e.message ?? "" };
}
};

View file

@ -1,6 +1,9 @@
"use server";
import { executeGraphQL } from "app/_lib/zammad";
import { searchQuery } from "@/app/_graphql/searchQuery";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-search');
export const searchAllAction = async (query: string, limit: number) => {
try {
@ -11,7 +14,7 @@ export const searchAllAction = async (query: string, limit: number) => {
return result?.search;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};

View file

@ -6,6 +6,9 @@ import { createTicketMutation } from "app/_graphql/createTicketMutation";
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
import { updateTagsMutation } from "app/_graphql/updateTagsMutation";
import { executeGraphQL, executeREST } from "app/_lib/zammad";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-tickets');
export const createTicketAction = async (
currentState: any,
@ -35,7 +38,7 @@ export const createTicketAction = async (
success: true,
};
} catch (e: any) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {
success: false,
values: {},
@ -62,7 +65,7 @@ export const createTicketArticleAction = async (
success: true,
};
} catch (e: any) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {
success: false,
message: e?.message ?? "Unknown error",
@ -115,7 +118,7 @@ export const updateTicketAction = async (
success: true,
};
} catch (e: any) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {
success: false,
message: e?.message ?? "Unknown error",
@ -132,7 +135,7 @@ export const getTicketAction = async (id: string) => {
return ticketData?.ticket;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {};
}
};
@ -146,7 +149,7 @@ export const getTicketArticlesAction = async (id: string) => {
return ticketData?.ticketArticles;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return {};
}
};
@ -164,7 +167,7 @@ export const getTicketStatesAction = async () => {
})) ?? [];
return formattedStates;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};
@ -177,7 +180,7 @@ export const getTagsAction = async () => {
return tags;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};
@ -196,7 +199,7 @@ export const getTicketPrioritiesAction = async () => {
return formattedPriorities;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};

View file

@ -1,6 +1,9 @@
"use server";
import { executeREST } from "app/_lib/zammad";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-users');
export const getAgentsAction = async (groupID: number) => {
try {
@ -21,7 +24,7 @@ export const getAgentsAction = async (groupID: number) => {
return formattedAgents;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};
@ -42,7 +45,7 @@ export const getCustomersAction = async () => {
return formattedCustomers;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};
@ -61,7 +64,7 @@ export const getUsersAction = async () => {
return formattedUsers;
} catch (e) {
console.error(e.message);
logger.error({ error: e }, "Error occurred");
return [];
}
};

View file

@ -12,6 +12,9 @@ import Credentials from "next-auth/providers/credentials";
import Apple from "next-auth/providers/apple";
import { Redis } from "ioredis";
import AzureADProvider from "next-auth/providers/azure-ad";
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-authentication');
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
@ -48,7 +51,7 @@ const getUserRoles = async (email: string) => {
});
return roles.filter((role: string) => role !== null);
} catch (e) {
console.error({ e });
logger.error({ error: e, email }, 'Failed to get user roles');
return [];
}
};

View file

@ -1,3 +1,7 @@
import { createLogger } from "@link-stack/logger";
const logger = createLogger('link-utils');
export const fetchLeafcutter = async (url: string, options: any) => {
/*
@ -13,7 +17,7 @@ export const fetchLeafcutter = async (url: string, options: any) => {
const json = await res.json();
return json;
} catch (error) {
console.error({ error });
logger.error({ error }, "Error occurred");
return null;
}
};