Skip to content

Commit 54fe437

Browse files
authored
Add logger (#34)
* Refactor configuration and utility structure * Implement logging setup and improve error handling * Add response logging and adjust console log level to WARNING * Update expected JSON response in index route test * Disable validation for the entire codebase in super-linter configuration * Remove unused imports and streamline logging setup message
1 parent b0c3de2 commit 54fe437

File tree

7 files changed

+72
-36
lines changed

7 files changed

+72
-36
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Log files
2+
*.log
3+
14
# Ignore all pem files
25
*.pem
36

app.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33

44
from flask import Flask, jsonify, request
55
from flask_cors import CORS
6+
from werkzeug.exceptions import HTTPException
67

7-
from config.settings import Config, limiter
8+
from config.logging import setup_logging
9+
from config.ratelimit import limiter
10+
from config.settings import Config
811
from routes import register_routes
912
from utility.database import extract_error_message
1013

1114
app = Flask(__name__)
1215
app.config.from_object(Config)
1316
CORS(app)
1417
limiter.init_app(app)
18+
logger = setup_logging()
1519

1620
if app.config["TESTING"]:
1721
limiter.enabled = False
@@ -26,16 +30,13 @@ def home():
2630
register_routes(app)
2731

2832

29-
# Add a status field to all JSON responses
3033
@app.after_request
31-
def add_status(response):
32-
if response.is_json:
33-
original_data = response.get_json()
34-
new_response = {
35-
"success": response.status_code in range(200, 300),
36-
"data": original_data if original_data != [] else None,
37-
}
38-
response.set_data(jsonify(new_response).data)
34+
def log_response(response):
35+
sender = request.remote_addr
36+
method = request.method
37+
path = request.path
38+
status_code = response.status_code
39+
logger.info(f"{sender}: {method} {path} {status_code}")
3940
return response
4041

4142

@@ -47,6 +48,9 @@ def add_common_headers(response):
4748

4849
@app.errorhandler(429)
4950
def ratelimit_error(e):
51+
logger.warning(
52+
f"Rate limit exceeded: {request.method} {request.path} - {e.description}"
53+
)
5054
return (
5155
jsonify(
5256
error="Too many requests",
@@ -59,20 +63,11 @@ def ratelimit_error(e):
5963

6064
@app.errorhandler(Exception)
6165
def handle_exception(e):
62-
# If the app is in debug mode, return the full traceback
63-
if app.debug:
64-
return (
65-
jsonify(
66-
error="Internal Server Error",
67-
message=str(e),
68-
type=type(e).__name__,
69-
url=request.url,
70-
traceback=traceback.format_exc().splitlines(),
71-
),
72-
500,
73-
)
74-
75-
# Otherwise, return a more user-friendly error message
66+
if isinstance(e, HTTPException):
67+
return e
68+
69+
trace = traceback.format_exc().splitlines()[-1]
70+
logger.exception(f"An unhandled exception occurred: {trace}")
7671
error_message = extract_error_message(str(e))
7772
return jsonify(error="Internal Server Error", message=error_message), 500
7873

@@ -84,4 +79,5 @@ def handle_exception(e):
8479
)
8580
args = parser.parse_args()
8681

82+
logger.info("Starting Flask application...")
8783
app.run(host="0.0.0.0", port=5000, debug=args.debug)

config/logging.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import datetime
2+
import logging
3+
4+
5+
def setup_logging():
6+
logger = logging.getLogger(__name__)
7+
logger.setLevel(logging.DEBUG)
8+
9+
# Generate filename (YYYY-MM-DD-HH-MM-SS.log)
10+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
11+
log_filename = f"{timestamp}.log"
12+
13+
# Create file handler
14+
file_handler = logging.FileHandler(log_filename)
15+
file_handler.setLevel(logging.DEBUG)
16+
17+
# Create console handler
18+
console_handler = logging.StreamHandler()
19+
console_handler.setLevel(logging.WARNING)
20+
21+
# Create formatter
22+
formatter = logging.Formatter(
23+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
24+
datefmt="%Y-%m-%d %H:%M:%S",
25+
)
26+
27+
# Add formatter to handlers
28+
file_handler.setFormatter(formatter)
29+
console_handler.setFormatter(formatter)
30+
31+
# Add handlers to logger
32+
logger.addHandler(file_handler)
33+
logger.addHandler(console_handler)
34+
logger.info("Logging setup complete.")
35+
36+
return logger

config/ratelimit.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from flask_limiter import Limiter
2+
from flask_limiter.util import get_remote_address
3+
4+
limiter = Limiter(
5+
key_func=get_remote_address,
6+
default_limits=["1000 per day", "200 per hour", "30 per minute", "3 per second"],
7+
)

config/settings.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from datetime import timedelta
33

44
from dotenv import load_dotenv
5-
from flask_limiter import Limiter
6-
from flask_limiter.util import get_remote_address
75

86
# Load environment variables from `.env` file
97
load_dotenv()
108

119

10+
# Pagination configuration
11+
DEFAULT_PAGE_SIZE = 10
12+
13+
1214
class Config:
1315
# Database configuration
1416
MYSQL_HOST = os.getenv("DB_HOST", "host.docker.internal")
@@ -29,11 +31,3 @@ class Config:
2931

3032
if not os.path.exists(Config.IMAGES_FOLDER):
3133
os.makedirs(Config.IMAGES_FOLDER)
32-
33-
# Pagination configuration
34-
DEFAULT_PAGE_SIZE = 10
35-
36-
limiter = Limiter(
37-
key_func=get_remote_address,
38-
default_limits=["1000 per day", "200 per hour", "30 per minute", "3 per second"],
39-
)

routes/authentication.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from flask import Blueprint, jsonify, request
33
from pymysql import MySQLError
44

5-
from config.settings import limiter
5+
from config.ratelimit import limiter
66
from jwt_helper import (
77
TokenError,
88
extract_token_from_header,

tests/test_routes/test_index.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def test_index_route_status_code(client):
1919
def test_index_route_json(client):
2020
"""Ensure the index route returns the correct JSON response"""
2121
response = client.get("/")
22-
expected_response = {"data": {"message": "Hello there!"}, "success": True}
22+
expected_response = {"message": "Hello there!"}
2323

2424
assert response.is_json
2525
assert response.get_json() == expected_response

0 commit comments

Comments
 (0)