Skip to content

Commit 068c200

Browse files
committed
feature: add logging
1 parent 770e67b commit 068c200

15 files changed

Lines changed: 657 additions & 39 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.mongodb.samplemflix.config;
2+
3+
import jakarta.servlet.FilterChain;
4+
import jakarta.servlet.ServletException;
5+
import jakarta.servlet.http.HttpServletRequest;
6+
import jakarta.servlet.http.HttpServletResponse;
7+
import java.io.IOException;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import org.springframework.core.Ordered;
11+
import org.springframework.core.annotation.Order;
12+
import org.springframework.stereotype.Component;
13+
import org.springframework.web.filter.OncePerRequestFilter;
14+
15+
/**
16+
* HTTP Request Logging Filter
17+
*
18+
* <p>This filter logs all incoming HTTP requests with useful information
19+
* including method, URL, status code, and response time.
20+
* It helps with debugging and monitoring application traffic.
21+
*
22+
* <p>Log output format:
23+
* <pre>
24+
* INFO - GET /api/movies 200 - 45ms
25+
* WARN - GET /api/movies/invalid 400 - 2ms
26+
* ERROR - POST /api/movies 500 - 120ms
27+
* </pre>
28+
*
29+
* <p>The filter is ordered to run first in the filter chain to ensure
30+
* accurate timing measurements.
31+
*/
32+
@Component
33+
@Order(Ordered.HIGHEST_PRECEDENCE)
34+
public class RequestLoggingFilter extends OncePerRequestFilter {
35+
36+
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
37+
38+
@Override
39+
protected void doFilterInternal(
40+
HttpServletRequest request,
41+
HttpServletResponse response,
42+
FilterChain filterChain) throws ServletException, IOException {
43+
44+
// Record the start time
45+
long startTime = System.currentTimeMillis();
46+
47+
// Log incoming request at debug level
48+
logger.debug("Incoming request: {} {} from {}",
49+
request.getMethod(),
50+
request.getRequestURI(),
51+
request.getRemoteAddr());
52+
53+
try {
54+
// Continue with the filter chain
55+
filterChain.doFilter(request, response);
56+
} finally {
57+
// Calculate response time
58+
long responseTime = System.currentTimeMillis() - startTime;
59+
60+
// Log the completed request with appropriate level based on status code
61+
logRequest(request.getMethod(), request.getRequestURI(), response.getStatus(), responseTime);
62+
}
63+
}
64+
65+
/**
66+
* Logs the HTTP request with appropriate log level based on status code.
67+
*
68+
* <p>Log levels:
69+
* <ul>
70+
* <li>ERROR: 5xx server errors</li>
71+
* <li>WARN: 4xx client errors</li>
72+
* <li>INFO: 2xx and 3xx success/redirect</li>
73+
* </ul>
74+
*
75+
* @param method HTTP method (GET, POST, etc.)
76+
* @param uri Request URI
77+
* @param statusCode HTTP response status code
78+
* @param responseTime Response time in milliseconds
79+
*/
80+
private void logRequest(String method, String uri, int statusCode, long responseTime) {
81+
String message = String.format("%s %s %d - %dms", method, uri, statusCode, responseTime);
82+
83+
if (statusCode >= 500) {
84+
logger.error(message);
85+
} else if (statusCode >= 400) {
86+
logger.warn(message);
87+
} else {
88+
logger.info(message);
89+
}
90+
}
91+
92+
/**
93+
* Skip logging for static resources and health checks to reduce noise.
94+
*/
95+
@Override
96+
protected boolean shouldNotFilter(HttpServletRequest request) {
97+
String path = request.getRequestURI();
98+
return path.startsWith("/swagger-ui")
99+
|| path.startsWith("/api-docs")
100+
|| path.startsWith("/v3/api-docs")
101+
|| path.equals("/favicon.ico")
102+
|| path.startsWith("/actuator");
103+
}
104+
}
105+

mflix/server/java-spring/src/main/resources/application.properties

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,22 @@ voyage.api.key=${VOYAGE_API_KEY:}
1919
spring.application.name=sample-app-java-mflix
2020

2121
# Logging Configuration
22-
logging.level.com.mongodb.samplemflix=INFO
22+
# Log level can be overridden with LOG_LEVEL environment variable
23+
# Available levels: TRACE, DEBUG, INFO, WARN, ERROR
24+
logging.level.com.mongodb.samplemflix=${LOG_LEVEL:INFO}
2325
logging.level.org.mongodb.driver=WARN
2426
# Suppress connection pool maintenance warnings (these are usually harmless)
2527
logging.level.org.mongodb.driver.connection=ERROR
2628

29+
# Console logging pattern with colors and timestamps
30+
logging.pattern.console=%clr(%d{HH:mm:ss}){faint} %clr(%5p){highlight} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n
31+
32+
# File logging (optional - enabled when LOG_FILE is set)
33+
logging.file.name=${LOG_FILE:}
34+
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} %5p --- [%15.15t] %-40.40logger{39} : %m%n
35+
logging.logback.rollingpolicy.max-file-size=5MB
36+
logging.logback.rollingpolicy.max-history=5
37+
2738
# Jackson Configuration (JSON serialization)
2839
spring.jackson.default-property-inclusion=non_null
2940
spring.jackson.serialization.write-dates-as-timestamps=false

mflix/server/js-express/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ VOYAGE_API_KEY=your_voyage_api_key
1010
PORT=3001
1111
NODE_ENV=development
1212

13+
# Logging Configuration
14+
# Available levels: error, warn, info, http, debug
15+
# Default: debug (development), info (production), error (test)
16+
LOG_LEVEL=debug
1317

1418
# CORS Configuration
1519
# Allowed origin for cross-origin requests (frontend URL)

mflix/server/js-express/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"express": "^5.1.0",
2525
"mongodb": "^7.0.0",
2626
"swagger-jsdoc": "^6.2.8",
27-
"swagger-ui-express": "^5.0.1"
27+
"swagger-ui-express": "^5.0.1",
28+
"winston": "^3.19.0"
2829
},
2930
"devDependencies": {
3031
"@types/cors": "^2.8.17",

mflix/server/js-express/src/app.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
import { errorHandler } from "./utils/errorHandler";
1919
import moviesRouter from "./routes/movies";
2020
import { swaggerSpec } from "./config/swagger";
21+
import logger from "./utils/logger";
22+
import { requestLogger } from "./middleware/requestLogger";
2123

2224
// Load environment variables from .env file
2325
// This must be called before any other imports that use environment variables
@@ -46,6 +48,12 @@ app.use(
4648
app.use(express.json({ limit: "10mb" }));
4749
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
4850

51+
/**
52+
* Request Logging Middleware
53+
* Logs all incoming HTTP requests with method, URL, status code, and response time
54+
*/
55+
app.use(requestLogger);
56+
4957
/**
5058
* Swagger API Documentation
5159
* Provides interactive API documentation at /api-docs
@@ -119,25 +127,25 @@ app.use(errorHandler);
119127
*/
120128
async function startServer() {
121129
try {
122-
console.log("Starting MongoDB Sample MFlix API...");
130+
logger.info("Starting MongoDB Sample MFlix API...");
123131

124132
// Connect to MongoDB database
125-
console.log("Connecting to MongoDB...");
133+
logger.info("Connecting to MongoDB...");
126134
await connectToDatabase();
127-
console.log("Connected to MongoDB successfully");
135+
logger.info("Connected to MongoDB successfully");
128136

129137
// Verify that all required indexes and sample data exist
130-
console.log("Verifying requirements (indexes and sample data)...");
138+
logger.info("Verifying requirements (indexes and sample data)...");
131139
await verifyRequirements();
132-
console.log("All requirements verified successfully");
140+
logger.info("All requirements verified successfully");
133141

134142
// Start the Express server
135143
app.listen(PORT, () => {
136-
console.log(`Server running on port ${PORT}`);
137-
console.log(`API documentation available at http://localhost:${PORT}/api-docs`);
144+
logger.info(`Server running on port ${PORT}`);
145+
logger.info(`API documentation available at http://localhost:${PORT}/api-docs`);
138146
});
139147
} catch (error) {
140-
console.error("Failed to start server:", error);
148+
logger.error("Failed to start server:", error);
141149

142150
// Exit the process if we can't start properly
143151
// This ensures the application doesn't run in a broken state
@@ -150,13 +158,13 @@ async function startServer() {
150158
* Ensures the application shuts down cleanly when terminated
151159
*/
152160
process.on("SIGINT", () => {
153-
console.log("\nReceived SIGINT. Shutting down...");
161+
logger.info("Received SIGINT. Shutting down gracefully...");
154162
closeDatabaseConnection();
155163
process.exit(0);
156164
});
157165

158166
process.on("SIGTERM", () => {
159-
console.log("\nReceived SIGTERM. Shutting down...");
167+
logger.info("Received SIGTERM. Shutting down gracefully...");
160168
closeDatabaseConnection();
161169
process.exit(0);
162170
});

mflix/server/js-express/src/config/database.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { MongoClient, Db, Collection, Document } from "mongodb";
10+
import logger from "../utils/logger";
1011

1112
let client: MongoClient;
1213
let database: Db;
@@ -40,7 +41,7 @@ async function _connectToDatabase(): Promise<Db> {
4041
// Get reference to the sample_mflix database
4142
database = client.db("sample_mflix");
4243

43-
console.log(`Connected to database: ${database.databaseName}`);
44+
logger.debug(`Connected to database: ${database.databaseName}`);
4445

4546
return database;
4647
} catch (error) {
@@ -85,7 +86,7 @@ export function getCollection<T extends Document>(
8586
export async function closeDatabaseConnection(): Promise<void> {
8687
if (client) {
8788
await client.close();
88-
console.log("Database connection closed");
89+
logger.info("Database connection closed");
8990
}
9091
}
9192

@@ -100,9 +101,9 @@ export async function verifyRequirements(): Promise<void> {
100101

101102
// Check if the movies collection exists and has data
102103
await verifyMoviesCollection(db);
103-
console.log("All database requirements verified successfully");
104+
logger.debug("All database requirements verified successfully");
104105
} catch (error) {
105-
console.error("Requirements verification failed:", error);
106+
logger.error("Requirements verification failed:", error);
106107
throw error;
107108
}
108109
}
@@ -117,7 +118,7 @@ async function verifyMoviesCollection(db: Db): Promise<void> {
117118
const movieCount = await moviesCollection.estimatedDocumentCount();
118119

119120
if (movieCount === 0) {
120-
console.warn(
121+
logger.warn(
121122
"Movies collection is empty. Please ensure sample_mflix data is loaded."
122123
);
123124
}
@@ -131,8 +132,8 @@ async function verifyMoviesCollection(db: Db): Promise<void> {
131132
background: true,
132133
}
133134
);
134-
console.log("Text search index created for movies collection");
135+
logger.debug("Text search index created for movies collection");
135136
} catch (error) {
136-
console.error("Could not create text search index:", error);
137+
logger.error("Could not create text search index:", error);
137138
}
138139
}

mflix/server/js-express/src/controllers/movieController.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
createSuccessResponse,
2525
validateRequiredFields,
2626
} from "../utils/errorHandler";
27+
import logger from "../utils/logger";
2728
import {
2829
CreateMovieRequest,
2930
UpdateMovieRequest,
@@ -874,7 +875,7 @@ export async function vectorSearchMovies(req: Request, res: Response): Promise<v
874875
)
875876
);
876877
} catch (error) {
877-
console.error("Vector search error:", error);
878+
logger.error("Vector search error:", error);
878879

879880
// Handle Voyage AI authentication errors
880881
if (error instanceof VoyageAuthError) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Request Logging Middleware
3+
*
4+
* This middleware logs incoming HTTP requests with useful information
5+
* including method, URL, status code, and response time.
6+
* It helps with debugging and monitoring application traffic.
7+
*/
8+
9+
import { Request, Response, NextFunction } from "express";
10+
import logger, { logHttpRequest } from "../utils/logger";
11+
12+
/**
13+
* Express middleware that logs HTTP requests
14+
*
15+
* Logs the following information for each request:
16+
* - HTTP method (GET, POST, PUT, DELETE, etc.)
17+
* - Request URL
18+
* - Response status code
19+
* - Response time in milliseconds
20+
*
21+
* @param req - Express request object
22+
* @param res - Express response object
23+
* @param next - Express next function
24+
*/
25+
export function requestLogger(
26+
req: Request,
27+
res: Response,
28+
next: NextFunction
29+
): void {
30+
// Record the start time
31+
const startTime = Date.now();
32+
33+
// Log request details at debug level when request starts
34+
logger.debug(`Incoming request: ${req.method} ${req.url}`, {
35+
headers: {
36+
"user-agent": req.get("user-agent"),
37+
"content-type": req.get("content-type"),
38+
},
39+
query: Object.keys(req.query).length > 0 ? req.query : undefined,
40+
ip: req.ip,
41+
});
42+
43+
// Log when response finishes
44+
res.on("finish", () => {
45+
const responseTime = Date.now() - startTime;
46+
logHttpRequest(req.method, req.url, res.statusCode, responseTime);
47+
});
48+
49+
next();
50+
}

mflix/server/js-express/src/utils/errorHandler.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { Request, Response, NextFunction } from "express";
99
import { MongoError } from "mongodb";
1010
import { SuccessResponse, ErrorResponse } from "../types";
11+
import logger from "./logger";
1112

1213
/**
1314
* Custom ValidationError class for field validation errors
@@ -37,17 +38,13 @@ export function errorHandler(
3738
next: NextFunction
3839
): void {
3940
// Log the error for debugging purposes
40-
// In production, we recommend using a logging service
41-
// Suppress error logging during tests to keep test output clean
42-
if (process.env.NODE_ENV !== "test") {
43-
console.error("Error occurred:", {
44-
message: err.message,
45-
stack: err.stack,
46-
url: req.url,
47-
method: req.method,
48-
timestamp: new Date().toISOString(),
49-
});
50-
}
41+
// The logger automatically handles environment-specific behavior
42+
logger.error("Error occurred:", {
43+
message: err.message,
44+
stack: err.stack,
45+
url: req.url,
46+
method: req.method,
47+
});
5148

5249
// Determine the appropriate HTTP status code and error message
5350
const errorDetails = parseErrorDetails(err);

0 commit comments

Comments
 (0)