Skip to content

Commit 47b61f4

Browse files
Added logger utility + global logging for http requests via middleware.
1 parent 069462c commit 47b61f4

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

src/lib/logger.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// src/lib/logger.ts
2+
import { NextRequest, NextResponse } from 'next/server'
3+
4+
/**
5+
* ANSI color codes for console output
6+
*/
7+
const colors = {
8+
reset: '\x1b[0m',
9+
bright: '\x1b[1m',
10+
dim: '\x1b[2m',
11+
red: '\x1b[31m',
12+
green: '\x1b[32m',
13+
yellow: '\x1b[33m',
14+
blue: '\x1b[34m',
15+
magenta: '\x1b[35m',
16+
cyan: '\x1b[36m',
17+
white: '\x1b[37m',
18+
gray: '\x1b[90m',
19+
}
20+
21+
/**
22+
* Get colored status code based on HTTP status
23+
*/
24+
function getColoredStatus(status: number): string {
25+
let color = colors.gray
26+
if (status >= 200 && status < 300) color = colors.green
27+
else if (status >= 300 && status < 400) color = colors.yellow
28+
else if (status >= 400 && status < 500) color = colors.red
29+
else if (status >= 500) color = colors.red + colors.bright
30+
31+
return `${color}${status}${colors.reset}`
32+
}
33+
34+
/**
35+
* Get colored HTTP method
36+
*/
37+
function getColoredMethod(method: string): string {
38+
const methodColors: Record<string, string> = {
39+
GET: colors.green,
40+
POST: colors.blue,
41+
PUT: colors.yellow,
42+
DELETE: colors.red,
43+
PATCH: colors.magenta,
44+
HEAD: colors.cyan,
45+
OPTIONS: colors.gray,
46+
}
47+
48+
const color = methodColors[method] || colors.white
49+
return `${color}${method.padEnd(7)}${colors.reset}`
50+
}
51+
52+
/**
53+
* Format duration with appropriate color
54+
*/
55+
function getColoredDuration(ms: number): string {
56+
let color = colors.green
57+
if (ms > 1000) color = colors.red
58+
else if (ms > 500) color = colors.yellow
59+
else if (ms > 100) color = colors.cyan
60+
61+
return `${color}${ms.toFixed(0)}ms${colors.reset}`
62+
}
63+
64+
/**
65+
* Get client IP address from various headers
66+
*/
67+
function getClientIP(request: NextRequest): string {
68+
const xForwardedFor = request.headers.get('x-forwarded-for')
69+
const xRealIP = request.headers.get('x-real-ip')
70+
const cfConnectingIP = request.headers.get('cf-connecting-ip')
71+
72+
if (xForwardedFor) {
73+
return xForwardedFor.split(',')[0].trim()
74+
}
75+
76+
if (xRealIP) return xRealIP
77+
if (cfConnectingIP) return cfConnectingIP
78+
79+
return 'unknown'
80+
}
81+
82+
/**
83+
* Log API route request and response
84+
*/
85+
export function logAPIRoute(
86+
request: NextRequest,
87+
response: NextResponse,
88+
startTime: number,
89+
additionalInfo?: string
90+
): void {
91+
const endTime = Date.now()
92+
const duration = endTime - startTime
93+
const status = response.status
94+
const method = request.method
95+
const url = request.url
96+
const clientIP = getClientIP(request)
97+
const timestamp = new Date().toISOString()
98+
99+
// Main log line
100+
console.log(
101+
`${colors.gray}[${timestamp}]${colors.reset} ` +
102+
`${getColoredMethod(method)} ` +
103+
`${colors.cyan}${url}${colors.reset} ` +
104+
`${getColoredStatus(status)} ` +
105+
`${getColoredDuration(duration)} ` +
106+
`${colors.gray}from ${clientIP}${colors.reset}` +
107+
(additionalInfo ? ` ${colors.dim}(${additionalInfo})${colors.reset}` : '')
108+
)
109+
}
110+
111+
/**
112+
* Higher-order function to wrap API route handlers with logging
113+
*/
114+
export function withLogging<T extends any[]>(
115+
handler: (request: NextRequest, ...args: T) => Promise<NextResponse>
116+
) {
117+
return async (request: NextRequest, ...args: T): Promise<NextResponse> => {
118+
const startTime = Date.now()
119+
120+
try {
121+
const response = await handler(request, ...args)
122+
logAPIRoute(request, response, startTime)
123+
return response
124+
} catch (error) {
125+
// Create error response
126+
const errorResponse = NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
127+
128+
logAPIRoute(request, errorResponse, startTime, `Error: ${error}`)
129+
throw error
130+
}
131+
}
132+
}
133+
134+
/**
135+
* Simple logger for general application logs
136+
*/
137+
export const logger = {
138+
info: (message: string, ...args: any[]) => {
139+
console.log(`${colors.blue}[INFO]${colors.reset} ${message}`, ...args)
140+
},
141+
142+
warn: (message: string, ...args: any[]) => {
143+
console.log(`${colors.yellow}[WARN]${colors.reset} ${message}`, ...args)
144+
},
145+
146+
error: (message: string, ...args: any[]) => {
147+
console.log(`${colors.red}[ERROR]${colors.reset} ${message}`, ...args)
148+
},
149+
150+
success: (message: string, ...args: any[]) => {
151+
console.log(`${colors.green}[SUCCESS]${colors.reset} ${message}`, ...args)
152+
},
153+
154+
debug: (message: string, ...args: any[]) => {
155+
if (process.env.NODE_ENV === 'development') {
156+
console.log(`${colors.gray}[DEBUG]${colors.reset} ${message}`, ...args)
157+
}
158+
},
159+
}

0 commit comments

Comments
 (0)