Files
websitedev/proto/src/lib/logger.ts
T
Do Siki 15529c7705
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
fix: contact length limits + logger/next.config nitpicks + dep updates
- /api/contact enforces per-field length caps (name/email/subject/message)
  before sanitization/persistence; over-long message returns 400 without
  touching Mongo (test added)
- logger: crypto-based request id (randomUUID) instead of Math.random().substr;
  defaultMeta.version prefers DEPLOY_VERSION over npm_package_version
- next.config: move Turbopack svg rule from deprecated experimental.turbo to
  top-level turbopack
- deps: bump next 15.5.2 → 15.5.23; npm audit fix (19 → 3, remaining are
  transitive sharp/libvips DoS advisories, not exploitable for static images)

Closes MITHOME-78, MITHOME-79
2026-08-22 12:22:27 +02:00

121 lines
3.8 KiB
TypeScript
Executable File

import winston from 'winston'
import LokiTransport from 'winston-loki'
import { randomUUID } from 'crypto'
const isDevelopment = process.env.NODE_ENV === 'development'
// Custom format for structured logging
const structuredFormat = winston.format.combine(
winston.format.timestamp({ format: 'ISO' }),
winston.format.errors({ stack: true }),
winston.format.json({
replacer: (_key, value) =>
typeof value === 'bigint' ? value.toString() : value,
})
)
// Console format for development
const consoleFormat = winston.format.combine(
winston.format.timestamp({ format: 'HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.colorize(),
winston.format.simple(),
winston.format.printf(({ timestamp, level, message, service, requestId, ...meta }) => {
const requestInfo = requestId ? `[${requestId}]` : ''
const serviceInfo = service ? `[${service}]` : '[mozdIT]'
const metaStr = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : ''
return `${timestamp} ${serviceInfo} ${level} ${requestInfo} ${message}${metaStr}`
})
)
// Transports configuration
const transports: winston.transport[] = [
// Loki transport for centralized logging
...(process.env.LOKI_HOST
? [
new LokiTransport({
host: process.env.LOKI_HOST,
labels: {
app: 'mozdit-web',
environment: process.env.NODE_ENV || 'development',
service: 'frontend'
},
basicAuth: process.env.LOKI_USERNAME && process.env.LOKI_PASSWORD
? `${process.env.LOKI_USERNAME}:${process.env.LOKI_PASSWORD}`
: undefined,
json: true,
format: winston.format.json(),
onConnectionError: (err: Error) => console.error('Loki connection error:', err)
})
]
: []),
// Console for development logging
new winston.transports.Console({
level: isDevelopment ? 'debug' : 'info',
format: isDevelopment ? consoleFormat : structuredFormat,
handleExceptions: true,
handleRejections: true
})
]
// Root logger configuration
export const logger = winston.createLogger({
level: isDevelopment ? 'debug' : 'info',
format: structuredFormat,
defaultMeta: {
service: 'mozdit-web',
version: process.env.DEPLOY_VERSION || process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development'
},
transports,
exceptionHandlers: transports,
rejectionHandlers: transports
})
// Specialized loggers for different components
export const createComponentLogger = (component: string) => {
return logger.child({ component })
}
export const requestLogger = logger.child({ component: 'request' })
export const apiLogger = logger.child({ component: 'api' })
export const dbLogger = logger.child({ component: 'database' })
export const authLogger = logger.child({ component: 'auth' })
export const errorLogger = logger.child({ component: 'error' })
// Request ID generator for correlation
export const generateRequestId = (): string =>
`${Date.now()}-${randomUUID().slice(0, 8)}`
// Helper function for timing operations
export const withTiming = async <T>(
operation: string,
fn: () => Promise<T>,
metadata: any = {}
): Promise<T> => {
const startTime = Date.now()
logger.debug(`Started ${operation}`, metadata)
try {
const result = await fn()
const duration = Date.now() - startTime
logger.debug(`Completed ${operation} in ${duration}ms`, { ...metadata, duration })
return result
} catch (error) {
const duration = Date.now() - startTime
logger.error(`Failed ${operation} in ${duration}ms`, { ...metadata, duration, error })
throw error
}
}
// Graceful shutdown
const gracefulShutdown = () => {
logger.info('Initiating graceful shutdown...')
logger.end()
}
process.on('SIGTERM', gracefulShutdown)
process.on('SIGINT', gracefulShutdown)
export default logger