Spaces:
Running
Running
| import pino from "pino"; | |
| import { getEnv } from "../config/env"; | |
| // PII fields that will be redacted in logs | |
| const PII_FIELDS = ["email", "full_name", "first_name", "last_name", "phone", "linkedin_url"]; | |
| function redactPii(obj: Record<string, unknown>): Record<string, unknown> { | |
| const result: Record<string, unknown> = {}; | |
| for (const [key, value] of Object.entries(obj)) { | |
| if (PII_FIELDS.includes(key) && typeof value === "string") { | |
| // Show first 3 chars + *** e.g. "joh***" | |
| result[key] = value.length > 3 ? `${value.slice(0, 3)}***` : "***"; | |
| } else if (value && typeof value === "object" && !Array.isArray(value)) { | |
| result[key] = redactPii(value as Record<string, unknown>); | |
| } else { | |
| result[key] = value; | |
| } | |
| } | |
| return result; | |
| } | |
| const env = getEnv(); | |
| export const logger = pino({ | |
| level: env.LOG_LEVEL, | |
| transport: | |
| env.NODE_ENV === "development" | |
| ? { target: "pino-pretty", options: { colorize: true } } | |
| : undefined, | |
| serializers: { | |
| // Auto-redact PII in any "contact" or "data" field | |
| contact: (val: Record<string, unknown>) => redactPii(val), | |
| data: (val: Record<string, unknown>) => redactPii(val), | |
| }, | |
| }); | |
| // Convenience method for audit-safe logging | |
| export function auditLog(action: string, entity: string, details: Record<string, unknown>) { | |
| logger.info({ action, entity, details: redactPii(details) }, `[AUDIT] ${action}`); | |
| } | |