Skip to content

Commit 3de972e

Browse files
authored
Merge pull request #1 from mtzdev/dev
Implementação de logging usando Loguru
2 parents 0377e3a + ed771e2 commit 3de972e

10 files changed

Lines changed: 142 additions & 10 deletions

File tree

backend/.env-example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
DATABASE_URL="sqlite:///./db.sqlite3" # Conexão para o banco de dados
22
JWT_SECRET_KEY="SUA_SECRET_KEY" # Recomendo gerar uma chave usando: python -c "import secrets; print(secrets.token_hex(32))"
3-
JWT_EXPIRATION_TIME=30 # Tempo de expiração do token em minutos
3+
JWT_EXPIRATION_TIME=30 # Tempo de expiração do token em minutos
4+
WEBHOOK_LOG_INFO="" # Preencha com o seu link de webhook
5+
WEBHOOK_LOG_ERROR=""

backend/poetry.lock

Lines changed: 36 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ dependencies = [
1111
"passlib[argon2] (>=1.7.4,<2.0.0)",
1212
"pyjwt (>=2.10.1,<3.0.0)",
1313
"slowapi (>=0.1.9,<0.2.0)",
14-
"apscheduler (>=3.11.0,<4.0.0)"
14+
"apscheduler (>=3.11.0,<4.0.0)",
15+
"loguru (>=0.7.3,<0.8.0)"
1516
]
1617

1718
[tool.poetry]

backend/src/app.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
from fastapi.responses import JSONResponse
66
from slowapi.middleware import SlowAPIMiddleware
77
from slowapi.errors import RateLimitExceeded
8-
from src.utils import limiter
8+
from src.utils import limiter, get_user_ip
99
import uvicorn
10+
from src.logger import setup_discord_logging, logger
11+
12+
setup_discord_logging()
1013

1114
app = FastAPI(docs_url=None, redoc_url=None)
1215
app.state.limiter = limiter
@@ -22,6 +25,14 @@
2225
allow_headers=['*'],
2326
)
2427

28+
@app.middleware("http")
29+
async def capture_exceptions(request: Request, call_next):
30+
try:
31+
return await call_next(request)
32+
except Exception as exc:
33+
logger.critical(f"`{request.method} {request.url.path}` Erro não tratado!\n- IP: {get_user_ip(request)} - Session ID: {request.cookies.get('session_id')}\n```{exc}```")
34+
return JSONResponse(status_code=500, content={"detail": "Internal Server Error"})
35+
2536
@app.exception_handler(RateLimitExceeded)
2637
async def rate_limit_exception_handler(request: Request, exc: RateLimitExceeded):
2738
return JSONResponse(
@@ -34,4 +45,5 @@ async def rate_limit_exception_handler(request: Request, exc: RateLimitExceeded)
3445
scheduler.start()
3546

3647
if __name__ == '__main__':
37-
uvicorn.run(app, host='0.0.0.0', port=80, reload=False)
48+
uvicorn.run(app, host='0.0.0.0', port=80, reload=False)
49+
logger.info("API iniciada!")

backend/src/logger.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import asyncio
2+
from datetime import datetime
3+
import threading
4+
import httpx
5+
from loguru import logger
6+
from src.settings import WEBHOOK_INFO, WEBHOOK_ERROR
7+
8+
def send_webhook(message: str, webhook_url: str):
9+
def runner():
10+
async def task():
11+
try:
12+
async with httpx.AsyncClient(timeout=5) as client:
13+
await client.post(webhook_url, json={"content": message})
14+
except Exception as e:
15+
logger.error(f"[Log Error]: {e}")
16+
17+
asyncio.run(task())
18+
19+
threading.Thread(target=runner, daemon=True).start()
20+
21+
def info_sink(message):
22+
send_webhook(f"**[{message.record['level'].name}]** {message.record['message']}\n-# **{datetime.now().strftime('%d/%m/%Y - %H:%M:%S')}**", WEBHOOK_INFO)
23+
24+
def error_sink(message):
25+
send_webhook(f"**[{message.record['level'].name}]** {message.record['message']}\n-# **{datetime.now().strftime('%d/%m/%Y - %H:%M:%S')}**", WEBHOOK_ERROR)
26+
27+
def setup_discord_logging():
28+
logger.remove()
29+
logger.add(info_sink, level="INFO", filter=lambda r: r["level"].name in ("INFO", "WARNING"))
30+
logger.add(error_sink, level="ERROR", filter=lambda r: r["level"].name in ("ERROR", "CRITICAL"))

backend/src/routes/auth.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from src.db.models import User, RefreshToken
55
from src.security import get_user, verify_password, generate_password_hash, generate_jwt_token, clear_auth_cookie, generate_session_id, invalidate_all_sessions
66
from src.schemas import LoginRequestSchema, RegisterRequestSchema, LoginResponseSchema, UsernameUpdateSchema, EmailUpdateSchema, PasswordUpdateSchema
7-
from src.utils import limiter
7+
from src.utils import limiter, get_user_ip
88
from sqlalchemy.orm import Session
9+
from src.logger import logger
910

1011
router = APIRouter()
1112

@@ -14,13 +15,16 @@
1415
def login(login: LoginRequestSchema, request: Request, response: Response, db: Session = Depends(get_db)):
1516
user = db.query(User).filter(User.email == login.email).first()
1617
if not user:
18+
logger.warning(f"`/auth/login`: Tentativa de login com email inexistente\n```Email: {login.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
1719
raise HTTPException(status_code=401, detail='E-mail ou senha estão inválidos. Por favor, tente novamente.')
1820

1921
if not verify_password(login.password, user.password):
22+
logger.warning(f"`/auth/login`: Tentativa de login com senha inválida\n```Email: {login.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
2023
raise HTTPException(status_code=401, detail='E-mail ou senha estão inválidos. Por favor, tente novamente.')
2124

2225
session_id = generate_session_id(response)
2326
token = generate_jwt_token(user.id, user.username, session_id, request, response, login.remember, db)
27+
logger.info(f"`/auth/login`: Login realizado com sucesso\n```Email: {login.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
2428
return {
2529
'access_token': token['access_token'],
2630
'refresh_token': token['refresh_token'],
@@ -35,12 +39,15 @@ def login(login: LoginRequestSchema, request: Request, response: Response, db: S
3539
@limiter.limit("3/minute;15/day")
3640
def register(register: RegisterRequestSchema, request: Request, response: Response, db: Session = Depends(get_db)):
3741
if register.password != register.confirm_password:
42+
logger.warning(f"`/auth/register`: Tentativa de registro com senhas diferentes\n```Username: {register.username} - Email: {register.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
3843
raise HTTPException(status_code=400, detail='As senhas não coincidem. Verifique se as senhas estão iguais.')
3944

4045
if db.query(User).filter(User.username == register.username).first():
46+
logger.warning(f"`/auth/register`: Tentativa de registro com username já existente\n```Username: {register.username} - Email: {register.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
4147
raise HTTPException(status_code=409, detail='Nome de usuário já registrado. Por favor, tente outro nome de usuário.')
4248

4349
if db.query(User).filter(User.email == register.email).first():
50+
logger.warning(f"`/auth/register`: Tentativa de registro com e-mail já existente\n```Username: {register.username} - Email: {register.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
4451
raise HTTPException(status_code=409, detail='E-mail já registrado. Por favor, tente outro e-mail.')
4552

4653
user = User(username=register.username, email=register.email, password=generate_password_hash(register.password))
@@ -50,6 +57,7 @@ def register(register: RegisterRequestSchema, request: Request, response: Respon
5057

5158
session_id = generate_session_id(response)
5259
token = generate_jwt_token(user.id, user.username, session_id, request, response, True, db)
60+
logger.info(f"`/auth/register`: Registro realizado com sucesso\n```Username: {register.username} - Email: {register.email}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
5361
return {
5462
'access_token': token['access_token'],
5563
'refresh_token': token['refresh_token'],
@@ -66,6 +74,7 @@ def logout(response: Response, request: Request, db: Session = Depends(get_db)):
6674
if session_id:
6775
db.query(RefreshToken).filter(RefreshToken.session_id == session_id).update({'is_active': False})
6876
db.commit()
77+
logger.info(f"`/auth/logout`: Logout realizado com sucesso\n```Session ID: {session_id}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
6978

7079
clear_auth_cookie(response)
7180
return {'message': 'Successfully logged out'}
@@ -82,6 +91,7 @@ def get_current_user(request: Request, response: Response, db: Session = Depends
8291
def update_username(new: UsernameUpdateSchema, request: Request, response: Response, db: Session = Depends(get_db)):
8392
user = get_user(request, response, db)
8493
if not user:
94+
logger.warning(f"`/auth/me/username`: Tentativa com token inválido\n```IP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
8595
raise HTTPException(status_code=401, detail='Invalid token or user not found')
8696

8797
if db.query(User).filter(User.username == new.username).first():
@@ -90,13 +100,15 @@ def update_username(new: UsernameUpdateSchema, request: Request, response: Respo
90100
user_db = db.query(User).filter(User.id == user['id']).first()
91101
user_db.username = new.username
92102
db.commit()
103+
logger.info(f"`/auth/me/username`: Username atualizado com sucesso\n```Username: {user['username']} -> {new.username}\nSession ID: {request.cookies.get('session_id')}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
93104
return {'message': 'Username updated successfully'}
94105

95106
@router.patch('/me/email')
96107
@limiter.limit("3/day")
97108
def update_email(new: EmailUpdateSchema, request: Request, response: Response, db: Session = Depends(get_db)):
98109
user = get_user(request, response, db)
99110
if not user:
111+
logger.warning(f"`/auth/me/email`: Tentativa com token inválido\n```IP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
100112
raise HTTPException(status_code=401, detail='Invalid token or user not found')
101113

102114
if db.query(User).filter(User.email == new.email).first():
@@ -106,13 +118,15 @@ def update_email(new: EmailUpdateSchema, request: Request, response: Response, d
106118
user_db.email = new.email
107119
db.commit()
108120
invalidate_all_sessions(user['id'], request.cookies.get('session_id'), True, db)
121+
logger.info(f"`/auth/me/email`: E-mail atualizado com sucesso\n```Email: {user['email']} -> {new.email}\nSession ID: {request.cookies.get('session_id')}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
109122
return {'message': 'E-mail updated successfully'}
110123

111124
@router.patch('/me/password')
112125
@limiter.limit("3/day")
113126
def update_password(new: PasswordUpdateSchema, request: Request, response: Response, db: Session = Depends(get_db)):
114127
user = get_user(request, response, db)
115128
if not user:
129+
logger.warning(f"`/auth/me/password`: Tentativa com token inválido\n```IP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
116130
raise HTTPException(status_code=401, detail='Invalid token or user not found')
117131

118132
user_db = db.query(User).filter(User.id == user['id']).first()
@@ -122,4 +136,5 @@ def update_password(new: PasswordUpdateSchema, request: Request, response: Respo
122136
user_db.password = generate_password_hash(new.password)
123137
db.commit()
124138
invalidate_all_sessions(user['id'], request.cookies.get('session_id'), True, db)
139+
logger.info(f"`/auth/me/password`: Senha atualizada com sucesso\n```Email: {user['email']} - Session ID: {request.cookies.get('session_id')}\nIP: {get_user_ip(request)} | User-Agent: {request.headers.get('User-Agent')}```")
125140
return {'message': 'Password updated successfully'}

0 commit comments

Comments
 (0)