Skip to content

Commit bcea57b

Browse files
Global http logging.
1 parent 47b61f4 commit bcea57b

File tree

1 file changed

+149
-21
lines changed

1 file changed

+149
-21
lines changed

src/middleware.ts

Lines changed: 149 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,159 @@
1-
// middleware.js
2-
import { NextResponse } from 'next/server'
1+
// src/middleware.ts
2+
import { NextResponse, NextRequest } from 'next/server'
33

4-
import { NextRequest } from 'next/server'
4+
/**
5+
* ANSI color codes for console output in Docker
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+
// Check various headers that might contain the real IP
69+
const xForwardedFor = request.headers.get('x-forwarded-for')
70+
const xRealIP = request.headers.get('x-real-ip')
71+
const cfConnectingIP = request.headers.get('cf-connecting-ip')
72+
73+
if (xForwardedFor) {
74+
// x-forwarded-for can contain multiple IPs, take the first one
75+
return xForwardedFor.split(',')[0].trim()
76+
}
77+
78+
if (xRealIP) return xRealIP
79+
if (cfConnectingIP) return cfConnectingIP
80+
81+
// Fallback to connection remote address (usually won't work in Docker)
82+
return 'unknown'
83+
}
584

685
/**
7-
* Middleware function to handle incoming requests and set the Content-Security-Policy header.
8-
*
9-
* The Content-Security-Policy header is configured to:
10-
* - Allow resources from the same origin ('self') by default.
11-
* - Allow scripts from the same origin ('self').
12-
* - Allow web workers from the same origin ('self') and https://new.codebuilder.org.
13-
* - Report CSP violations to https://new.codebuilder.org/csp-report.
86+
* Check if the request should be logged
87+
*/
88+
function shouldLog(pathname: string): boolean {
89+
// Skip logging for static assets and Next.js internal routes
90+
const skipPatterns = [
91+
'/_next/',
92+
'/favicon.ico',
93+
'/robots.txt',
94+
'/sitemap.xml',
95+
'/.well-known/',
96+
'/api/health',
97+
'/health',
98+
]
99+
100+
return !skipPatterns.some((pattern) => pathname.startsWith(pattern))
101+
}
102+
103+
/**
104+
* Enhanced middleware function with comprehensive HTTP access logging
14105
*/
15106
export function middleware(request: NextRequest): NextResponse {
107+
const startTime = Date.now()
108+
const { pathname, search } = request.nextUrl
109+
const method = request.method
110+
const userAgent = request.headers.get('user-agent') || 'unknown'
111+
const referer = request.headers.get('referer') || '-'
112+
const clientIP = getClientIP(request)
113+
114+
// Create response
16115
const response = NextResponse.next()
17116

18-
// response.headers.set(
19-
// 'Content-Security-Policy',
20-
// `
21-
// default-src 'self';
22-
// script-src 'self';
23-
// worker-src 'self' https://new.codebuilder.org;
24-
// report-uri https://new.codebuilder.org/csp-report;
25-
// `
26-
// .replace(/\s{2,}/g, ' ')
27-
// .trim()
28-
// )
117+
// Only log if this request should be logged
118+
if (shouldLog(pathname)) {
119+
// Add a custom header to track response time
120+
response.headers.set('x-request-start', startTime.toString())
121+
122+
// Log the request immediately (before processing)
123+
const timestamp = new Date().toISOString()
124+
const fullUrl = pathname + search
125+
126+
console.log(
127+
`${colors.gray}[${timestamp}]${colors.reset} ` +
128+
`${getColoredMethod(method)} ` +
129+
`${colors.cyan}${fullUrl}${colors.reset} ` +
130+
`${colors.gray}from ${clientIP}${colors.reset}`
131+
)
132+
133+
// Log additional details in development
134+
if (process.env.NODE_ENV === 'development') {
135+
console.log(`${colors.dim} └─ UA: ${userAgent}${colors.reset}`)
136+
if (referer !== '-') {
137+
console.log(`${colors.dim} └─ Referer: ${referer}${colors.reset}`)
138+
}
139+
}
140+
}
29141

30142
return response
31143
}
144+
145+
/**
146+
* Configure which paths the middleware should run on
147+
*/
148+
export const config = {
149+
matcher: [
150+
/*
151+
* Match all request paths except for the ones starting with:
152+
* - api (API routes)
153+
* - _next/static (static files)
154+
* - _next/image (image optimization files)
155+
* - favicon.ico (favicon file)
156+
*/
157+
'/((?!_next/static|_next/image|favicon.ico).*)',
158+
],
159+
}

0 commit comments

Comments
 (0)