Files
cfw-autumn/server/src/errors/logger.ts
John Yeo 98ba9f9841 feat: Add CTE utilities and enhance platform users endpoint
## CTE Utils System
- Created comprehensive CTE (Common Table Expression) builder utilities
- Supports declarative query building with automatic relation handling
- Smart strategy selection between JOIN+GROUP BY and subquery patterns
- Handles nested relations with automatic optimization
- Type-safe query construction with Drizzle ORM

## Platform Users Enhancements
- Refactored to use new CTE utils for cleaner query building
- Added analytics middleware to Hono pipeline
- Improved query param handling with queryStringArray helper
- Changed timestamps from ISO strings to milliseconds for consistency
- Extracted cleanOrgSlug utility function

## Other Improvements
- Added analytics middleware for Hono routes
- Enhanced base middleware with better context handling
- Fixed template literal linting in initHono.ts
- Updated logger for better structured logging
- Added test files for CTE development

## File Changes
- New: server/src/db/cteUtils/ - Complete CTE builder system
- New: server/src/honoMiddlewares/analyticsMiddleware.ts
- Modified: handleListPlatformUsers.ts - Refactored with CTE utils
- Modified: platformModels.ts - Updated types and validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 17:48:10 +01:00

153 lines
3.3 KiB
TypeScript

import pino from "pino";
import { Writable } from "stream";
// Custom log formatter for Bun compatibility
const createDevLogStream = () => {
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
dim: "\x1b[2m",
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
cyan: "\x1b[36m",
white: "\x1b[37m",
gray: "\x1b[90m",
bgRed: "\x1b[41m",
};
const levelColors: Record<number | string, string> = {
// Numeric levels
10: colors.gray, // trace
20: colors.blue, // debug
30: colors.green, // info
40: colors.yellow, // warn
50: colors.red, // error
60: colors.bgRed, // fatal
// String levels
TRACE: colors.gray,
DEBUG: colors.blue,
INFO: colors.green,
WARN: colors.yellow,
ERROR: colors.red,
FATAL: colors.bgRed,
};
const levelNames: Record<number | string, string> = {
// Numeric levels
10: "TRACE",
20: "DEBUG",
30: "INFO",
40: "WARN",
50: "ERROR",
60: "FATAL",
// String levels (pass through)
TRACE: "TRACE",
DEBUG: "DEBUG",
INFO: "INFO",
WARN: "WARN",
ERROR: "ERROR",
FATAL: "FATAL",
};
return new Writable({
write(chunk, encoding, callback) {
try {
const log = JSON.parse(chunk.toString());
const timestamp = new Date(log.time)
.toISOString()
.replace("T", " ")
.replace("Z", "");
const level = log.level;
const levelColor = levelColors[level] || colors.white;
const levelName =
levelNames[level] || (typeof level === "string" ? level : "UNKNOWN");
// Format the message
let message = log.msg || "";
// Add any additional fields (excluding standard pino fields)
const excludeFields = [
"time",
"level",
"msg",
"pid",
"hostname",
"res",
"statusCode",
"worker",
"context",
"req",
"data",
"body",
"query",
];
const additionalFields = Object.keys(log)
.filter((key) => !excludeFields.includes(key))
.reduce((acc, key) => {
acc[key] = log[key];
return acc;
}, {} as any);
if (Object.keys(additionalFields).length > 0) {
message += ` ${JSON.stringify(additionalFields, null, 2)}`;
}
// Format the final log line
const formattedLog = `${colors.gray}${timestamp}${colors.reset} ${levelColor}${colors.bright}${levelName}${colors.reset} ${message}\n`;
process.stdout.write(formattedLog);
callback();
} catch (_error) {
// Fallback for malformed JSON
process.stdout.write(chunk);
callback();
}
},
});
};
export const initLogger = () => {
// Create separate streams for console and HyperDX
const streams: pino.StreamEntry[] = [];
if (process.env.NODE_ENV === "development") {
streams.push({
level: process.env.NODE_ENV === "development" ? "debug" : "info",
stream: createDevLogStream(),
});
}
if (process.env.AXIOM_TOKEN) {
streams.push({
level: "info",
stream: pino.transport({
target: "@axiomhq/pino",
options: {
dataset: "express",
token: process.env.AXIOM_TOKEN,
},
}),
});
}
const logger = pino(
{
level: process.env.NODE_ENV === "development" ? "debug" : "info",
formatters: {
level: (label: any) => {
return {
level: label.toUpperCase(),
};
},
},
},
// Use multistream to send logs to multiple destinations
pino.multistream(streams),
);
return logger;
};