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:
parent
5b89bfce7c
commit
c1feaa4cb1
42 changed files with 3824 additions and 2422 deletions
36
packages/logger/package.json
Normal file
36
packages/logger/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@link-stack/logger",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared logging utility for Link Stack monorepo",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./dist/index.js",
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"pino": "^9.5.0",
|
||||
"pino-pretty": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@link-stack/eslint-config": "*",
|
||||
"@link-stack/typescript-config": "*",
|
||||
"@types/node": "^22.10.5",
|
||||
"eslint": "^9.17.0",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
56
packages/logger/src/config.ts
Normal file
56
packages/logger/src/config.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type { LoggerOptions } from 'pino';
|
||||
|
||||
export const getLogLevel = (): string => {
|
||||
return process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug');
|
||||
};
|
||||
|
||||
export const getPinoConfig = (): LoggerOptions => {
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const baseConfig: LoggerOptions = {
|
||||
level: getLogLevel(),
|
||||
formatters: {
|
||||
level: (label) => {
|
||||
return { level: label.toUpperCase() };
|
||||
},
|
||||
},
|
||||
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
||||
redact: {
|
||||
paths: [
|
||||
'password',
|
||||
'token',
|
||||
'secret',
|
||||
'api_key',
|
||||
'apiKey',
|
||||
'authorization',
|
||||
'cookie',
|
||||
'*.password',
|
||||
'*.token',
|
||||
'*.secret',
|
||||
'*.api_key',
|
||||
'*.apiKey',
|
||||
],
|
||||
censor: '[REDACTED]',
|
||||
},
|
||||
};
|
||||
|
||||
if (isDevelopment) {
|
||||
// In development, use pretty printing for better readability
|
||||
return {
|
||||
...baseConfig,
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'SYS:standard',
|
||||
ignore: 'pid,hostname',
|
||||
singleLine: false,
|
||||
messageFormat: '{msg}',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// In production, use JSON for structured logging
|
||||
return baseConfig;
|
||||
};
|
||||
35
packages/logger/src/index.ts
Normal file
35
packages/logger/src/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import pino, { Logger as PinoLogger } from 'pino';
|
||||
import { getPinoConfig } from './config';
|
||||
|
||||
export type Logger = PinoLogger;
|
||||
|
||||
// Create the default logger instance
|
||||
export const logger: Logger = pino(getPinoConfig());
|
||||
|
||||
// Factory function to create child loggers with context
|
||||
export const createLogger = (name: string, context?: Record<string, any>): Logger => {
|
||||
return logger.child({ name, ...context });
|
||||
};
|
||||
|
||||
// Export log levels for consistency
|
||||
export const LogLevel = {
|
||||
TRACE: 'trace',
|
||||
DEBUG: 'debug',
|
||||
INFO: 'info',
|
||||
WARN: 'warn',
|
||||
ERROR: 'error',
|
||||
FATAL: 'fatal',
|
||||
} as const;
|
||||
|
||||
export type LogLevelType = typeof LogLevel[keyof typeof LogLevel];
|
||||
|
||||
// Utility to check if a log level is enabled
|
||||
export const isLogLevelEnabled = (level: LogLevelType): boolean => {
|
||||
return logger.isLevelEnabled(level);
|
||||
};
|
||||
|
||||
// Re-export pino types that might be useful
|
||||
export type { LoggerOptions, DestinationStream } from 'pino';
|
||||
|
||||
// Default export for convenience
|
||||
export default logger;
|
||||
21
packages/logger/tsconfig.json
Normal file
21
packages/logger/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"lib": ["es2022"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { createLogger } from "@link-stack/logger";
|
||||
|
||||
const logger = createLogger('opensearch-common-opensearch');
|
||||
|
||||
/* Common */
|
||||
|
||||
|
|
@ -284,7 +287,7 @@ export const updateUserVisualization = async (
|
|||
});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error({ e });
|
||||
logger.error({ e });
|
||||
}
|
||||
|
||||
return id;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@link-stack/logger": "*",
|
||||
"@opensearch-project/opensearch": "^3.4.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ import { promises as fs } from "fs";
|
|||
import { glob } from "glob";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { createLogger } from "@link-stack/logger";
|
||||
|
||||
const logger = createLogger('zammad-addon-build');
|
||||
|
||||
const packageFile = async (actualPath: string): Promise<any> => {
|
||||
console.info(`Packaging: ${actualPath}`);
|
||||
logger.info({ actualPath }, 'Packaging file');
|
||||
const packagePath = actualPath.slice(4);
|
||||
const data = await fs.readFile(actualPath, "utf-8");
|
||||
const content = Buffer.from(data, "utf-8").toString("base64");
|
||||
|
|
@ -74,10 +77,10 @@ export const createZPM = async ({
|
|||
|
||||
for (const file of files) {
|
||||
await fs.unlink(file);
|
||||
console.info(`${file} was deleted`);
|
||||
logger.info({ file }, 'File was deleted');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error({ error: err }, 'Error removing old addon files');
|
||||
}
|
||||
await fs.writeFile(
|
||||
`../../docker/zammad/addons/${name}-v${version}.zpm`,
|
||||
|
|
@ -89,7 +92,7 @@ export const createZPM = async ({
|
|||
const main = async () => {
|
||||
const packageJSON = JSON.parse(await fs.readFile("./package.json", "utf-8"));
|
||||
const { name: fullName, displayName, version } = packageJSON;
|
||||
console.info(`Building addon ${displayName} v${version}`);
|
||||
logger.info({ displayName, version }, 'Building addon');
|
||||
const name = fullName.split("/").pop();
|
||||
await createZPM({ name, displayName, version });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"author": "",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@link-stack/logger": "*",
|
||||
"glob": "^11.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue