Skip to content

#59 server log #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion backend/app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# lib
import os
from datetime import timedelta

from config.logging_setup import setup_logger
from dotenv import load_dotenv

# flask lib
from flask import Flask, request
from flask_cors import CORS
from flask_jwt_extended import JWTManager

# blueprint
from routes.auth import auth_bp
from routes.dream.mine import my_dream_bp
from routes.dream.public import public_dream_bp
Expand All @@ -13,7 +19,8 @@
load_dotenv()

app = Flask(__name__)

# ログの出力設定
setup_logger(app)
# CORSのセットアップ
CORS(
app,
Expand Down Expand Up @@ -48,6 +55,7 @@ def test(): # 生存確認API
if request.method == "HEAD":
return "", 200
elif request.method == "GET":
app.logger.info("GET request")
return "Hello", 200


Expand Down
41 changes: 41 additions & 0 deletions backend/config/logging_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import logging

from flask import Flask, has_request_context, request
from rich.logging import RichHandler


# ログの出力にメソッドとパスを追記する
class RequestPathFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
if has_request_context():
record.request_path = f"{request.method} {request.path}"
else:
record.request_path = "-"

return True


# ロガーのセットアップ
def setup_logger(app: Flask):
# 既存ロガーの削除
for h in app.logger.handlers:
app.logger.removeHandler(h)

# 本番用ロガー
handler = RichHandler(
markup=True,
rich_tracebacks=True,
show_path=app.debug,
)

formatter = logging.Formatter("%(request_path)s - %(message)s")
handler.setFormatter(formatter)

handler.addFilter(RequestPathFilter())
# INFOレベル以上のみ
app.logger.setLevel(logging.INFO)
app.logger.addHandler(handler)

# 本番環境では標準ログを無効化
if not app.debug:
logging.getLogger("werkzeug").disabled = True
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Flask-JWT-Extended~=4.7.1
supabase==2.13.0
gunicorn==23.0.0
pydantic==2.10.6
rich==13.9.4
4 changes: 3 additions & 1 deletion backend/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Blueprint, jsonify, make_response, request
from flask import Blueprint, jsonify, make_response, request, current_app
from flask_jwt_extended import create_access_token
from models.db import get_supabase_client
from supabase import Client
Expand All @@ -22,6 +22,7 @@ def login():
user = response.user
# userを取得できなかった場合
if user is None:
current_app.logger.error("Credentials are incorrect")
return "Credentials are incorrect", 401

# ユーザーID から JWT を生成
Expand All @@ -42,6 +43,7 @@ def login_with_oauth():
credentials = request.get_json()
user_id = credentials["userId"]
if user_id is None:
current_app.logger.error("User ID is required")
return "User ID is required", 401

response = make_response("", 200)
Expand Down
53 changes: 35 additions & 18 deletions backend/routes/dream/mine.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Blueprint, jsonify, request
from flask import Blueprint, jsonify, request, current_app
from flask_jwt_extended import get_jwt_identity, jwt_required
from models.dream import Dream
from pydantic import ValidationError
Expand All @@ -7,46 +7,51 @@
my_dream_bp = Blueprint("my_dream", __name__)


# 自分が作成した夢を取得
@my_dream_bp.route("/dreams/mine", methods=["GET"])
@jwt_required()
def get_my_dreams():
user_id = get_jwt_identity()

try:
params = GetMyDreamsParams(**request.args)
except ValidationError:
except ValidationError as e:
current_app.logger.error("Invalid query parameters", exc_info=e)
return "Invalid query parameter", 400

dreams = Dream.get_all_by_user(user_id, params.sort_by)

try:
dreams = [MyDreamResponse(**dream.__dict__) for dream in dreams]
except ValidationError:
except ValidationError as e:
current_app.logger.error("Response validation failed", exc_info=e)
return "Response Validation Error", 500

return jsonify([dream.model_dump() for dream in dreams]), 200


# 夢を新規作成
@my_dream_bp.route("/dreams/mine", methods=["POST"])
@jwt_required()
def create_my_dream():

body = request.get_json()

try:
body = CreateMyDreamRequest(**body)
except ValidationError:
except ValidationError as e:
current_app.logger.error("Invalid request body", exc_info=e)
return "Invalid request body", 400

# JWTのヘッダからユーザーIDを取得
user_id = get_jwt_identity()

# 夢とハッシュタグのテーブルにデータを追加
created_dream = Dream.create_with_hashtags(user_id, body)
try:
created_dream = Dream.create_with_hashtags(user_id, body)
except Exception as e:
current_app.logger.exception("Failed to create dream")
return "Internal server error", 500

try:
created_dream = MyDreamResponse(**created_dream.__dict__)
except ValidationError:
except ValidationError as e:
current_app.logger.error("Response validation error", exc_info=e)
return "Response Validation Error", 500

return jsonify(created_dream.model_dump()), 201
Expand All @@ -55,23 +60,35 @@ def create_my_dream():
@my_dream_bp.route("/dreams/mine/<int:dream_id>", methods=["PATCH"])
@jwt_required()
def toggle_visibility(dream_id: int):
updated_dream = Dream.toggle_visibility(dream_id)
try:
updated_dream = Dream.toggle_visibility(dream_id)
except Exception as e:
current_app.logger.error.exception(f"Failed to toggle visibility for dream_id={dream_id}")
return "Internal Server Error", 500

if updated_dream is None:
current_app.logger.warning(f"Dream with id {dream_id} not found for visibility toggle")
return "", 404

try:
updated_dream = MyDreamResponse(**updated_dream.__dict__)
except ValidationError:
except ValidationError as e:
current_app.logger.error("Response validation error", exc_info=e)
return "Response Validation Error", 500

return jsonify(updated_dream.model_dump()), 200


# 夢を削除
@my_dream_bp.route("/dreams/mine/<int:dream_id>", methods=["DELETE"])
@jwt_required()
def delete_my_dream(dream_id: int):
if Dream.delete(dream_id):
return "", 204 # 削除成功
else:
return "", 404 # 夢が存在しない
try:
if Dream.delete(dream_id):
current_app.logger.info(f"Deleted dream {dream_id}")
return "", 204
else:
current_app.logger.warning(f"Attempted to delete non-existing dream {dream_id}")
return "", 404
except Exception as e:
current_app.logger.exception("Failed to delete dream")
return "Internal Server Error", 500
5 changes: 4 additions & 1 deletion backend/routes/dream/public.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Blueprint, jsonify
from flask import Blueprint, jsonify, current_app
from models.dream import Dream
from pydantic import ValidationError
from schemas.dream.public import PublicDreamResponse
Expand All @@ -15,6 +15,7 @@ def get_public_dreams():
PublicDreamResponse(**dream.__dict__) for dream in public_dreams
]
except ValidationError:
current_app.logger.exception("Validation error")
return "Response Validation Error", 500

return jsonify([dream.model_dump() for dream in public_dreams]), 200
Expand All @@ -29,11 +30,13 @@ def increment_like_count(dream_id: int):
likes_count = dream.likes + 1
updated_dream = Dream.update_likes(dream_id=dream_id, likes=likes_count)
if updated_dream is None:
current_app.logger.exception("Failed to increment like count, no dreams")
return "", 404

try:
updated_dream = PublicDreamResponse(**updated_dream.__dict__)
except ValidationError:
current_app.logger.exception("Validation error")
return "Response Validation Error", 500

return jsonify(updated_dream.model_dump()), 200
6 changes: 5 additions & 1 deletion backend/routes/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Blueprint, jsonify, request
from flask import Blueprint, jsonify, request, current_app
from models.user import User
from pydantic import ValidationError
from schemas.user import CreateUserBody, UserResponse
Expand All @@ -12,19 +12,23 @@ def create_user():
try:
body = CreateUserBody(**body)
except ValidationError:
current_app.logger.error("Validation Error of request body")
return "Invalid request body", 400

user = User.get_user_by_email(body.email)
if user is not None:
current_app.logger.error("Email is already taken")
return "Email is already taken", 409

new_user = User.create_user(body.email, body.password)
if new_user is None:
current_app.logger.error("Failed to create user")
return "Failed to create user", 500

try:
new_user = UserResponse(**new_user.__dict__)
except ValidationError:
current_app.logger.error("Response Validation Error")
return "Response Validation Error", 500

return jsonify(new_user.model_dump()), 201
3 changes: 3 additions & 0 deletions backend/schemas/dream/mine.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
from typing import Optional
from flask import current_app

from pydantic import (
BaseModel,
Expand Down Expand Up @@ -48,6 +49,8 @@ class CreateMyDreamRequest(BaseModel):
@field_validator("content")
def validate_content(cls, content: str) -> str:
if len(content) == 0:
# 空白エラー
current_app.logger.error("Content is required")
raise ValidationError("Content is required")

return content