adding the logs service
This commit is contained in:
parent
1591802c8e
commit
22800ab230
4 changed files with 126 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -57,6 +57,7 @@ cover/
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
|
logs/
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
|
||||||
10
api/views.py
10
api/views.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
|
|
@ -13,6 +14,8 @@ from .serializers import (
|
||||||
OrderSerializer, ReturnRequestSerializer, WalletSerializer
|
OrderSerializer, ReturnRequestSerializer, WalletSerializer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger('api')
|
||||||
|
|
||||||
def get_active_user(request):
|
def get_active_user(request):
|
||||||
if request.user and request.user.is_authenticated:
|
if request.user and request.user.is_authenticated:
|
||||||
return request.user
|
return request.user
|
||||||
|
|
@ -30,7 +33,9 @@ class RegisterView(APIView):
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
user = serializer.save()
|
user = serializer.save()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
logger.info('New supplier registered: username=%s email=%s', user.username, user.email)
|
||||||
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
|
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
|
||||||
|
logger.warning('Registration failed: %s', serializer.errors)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class LoginView(APIView):
|
class LoginView(APIView):
|
||||||
|
|
@ -40,7 +45,9 @@ class LoginView(APIView):
|
||||||
user = authenticate(username=username, password=password)
|
user = authenticate(username=username, password=password)
|
||||||
if user:
|
if user:
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
logger.info('Login successful: username=%s', username)
|
||||||
return Response(UserSerializer(user).data)
|
return Response(UserSerializer(user).data)
|
||||||
|
logger.warning('Login failed: username=%s', username)
|
||||||
return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class VerifyOtpView(APIView):
|
class VerifyOtpView(APIView):
|
||||||
|
|
@ -190,9 +197,11 @@ class WithdrawView(APIView):
|
||||||
try:
|
try:
|
||||||
amount = float(amount_str)
|
amount = float(amount_str)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
logger.error('Withdraw failed for user=%s: invalid amount value=%s', user.username, amount_str)
|
||||||
return Response({'error': 'Invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': 'Invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if amount <= 0 or amount > float(wallet.outstanding):
|
if amount <= 0 or amount > float(wallet.outstanding):
|
||||||
|
logger.warning('Withdraw rejected for user=%s: amount=%.2f outstanding=%.2f', user.username, amount, float(wallet.outstanding))
|
||||||
return Response({'error': 'Insufficient funds or invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': 'Insufficient funds or invalid amount'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
wallet.outstanding = float(wallet.outstanding) - amount
|
wallet.outstanding = float(wallet.outstanding) - amount
|
||||||
|
|
@ -207,4 +216,5 @@ class WithdrawView(APIView):
|
||||||
status='Transferred'
|
status='Transferred'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info('Withdraw successful: user=%s amount=%.2f tx_id=%s', user.username, amount, tx.tx_id)
|
||||||
return Response(WalletSerializer(wallet).data)
|
return Response(WalletSerializer(wallet).data)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/6.1/ref/settings/
|
https://docs.djangoproject.com/en/6.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
|
@ -139,3 +140,106 @@ REST_FRAMEWORK = {
|
||||||
'api.authentication.CsrfExemptSessionAuthentication',
|
'api.authentication.CsrfExemptSessionAuthentication',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# Writes to /app/logs/ (volume-mounted on host) and to stdout.
|
||||||
|
# Log files are also picked up by the CloudWatch Agent on the EC2 host.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
LOGS_DIR = BASE_DIR / 'logs'
|
||||||
|
os.makedirs(LOGS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
|
||||||
|
# ----- Formatters -----
|
||||||
|
'formatters': {
|
||||||
|
'verbose': {
|
||||||
|
'format': '[{asctime}] {levelname} {name} {process:d} {thread:d} | {message}',
|
||||||
|
'style': '{',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||||
|
},
|
||||||
|
'simple': {
|
||||||
|
'format': '[{asctime}] {levelname} | {message}',
|
||||||
|
'style': '{',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----- Filters -----
|
||||||
|
'filters': {
|
||||||
|
'require_debug_false': {
|
||||||
|
'()': 'django.utils.log.RequireDebugFalse',
|
||||||
|
},
|
||||||
|
'require_debug_true': {
|
||||||
|
'()': 'django.utils.log.RequireDebugTrue',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----- Handlers -----
|
||||||
|
'handlers': {
|
||||||
|
# Writes every log line to stdout (visible via `docker logs`)
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'simple',
|
||||||
|
},
|
||||||
|
# Rotating file for all application logs (INFO+)
|
||||||
|
'app_file': {
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': str(LOGS_DIR / 'app.log'),
|
||||||
|
'maxBytes': 10 * 1024 * 1024, # 10 MB
|
||||||
|
'backupCount': 7,
|
||||||
|
'formatter': 'verbose',
|
||||||
|
'encoding': 'utf-8',
|
||||||
|
},
|
||||||
|
# Dedicated rotating file for ERROR+ only
|
||||||
|
'error_file': {
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': str(LOGS_DIR / 'error.log'),
|
||||||
|
'maxBytes': 10 * 1024 * 1024, # 10 MB
|
||||||
|
'backupCount': 7,
|
||||||
|
'formatter': 'verbose',
|
||||||
|
'level': 'ERROR',
|
||||||
|
'encoding': 'utf-8',
|
||||||
|
},
|
||||||
|
# Django request/response log
|
||||||
|
'request_file': {
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': str(LOGS_DIR / 'requests.log'),
|
||||||
|
'maxBytes': 10 * 1024 * 1024, # 10 MB
|
||||||
|
'backupCount': 7,
|
||||||
|
'formatter': 'simple',
|
||||||
|
'encoding': 'utf-8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----- Loggers -----
|
||||||
|
'loggers': {
|
||||||
|
# Root logger – catches everything not matched below
|
||||||
|
'': {
|
||||||
|
'handlers': ['console', 'app_file', 'error_file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
# Your application code
|
||||||
|
'api': {
|
||||||
|
'handlers': ['console', 'app_file', 'error_file'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
# Django HTTP request log (4xx / 5xx)
|
||||||
|
'django.request': {
|
||||||
|
'handlers': ['console', 'request_file', 'error_file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
# Suppress noisy security middleware warnings in prod
|
||||||
|
'django.security': {
|
||||||
|
'handlers': ['error_file'],
|
||||||
|
'level': 'ERROR',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,19 @@ services:
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8080:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
|
- ./logs:/app/logs # persists log files on the host
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=1
|
- DEBUG=1
|
||||||
restart: always
|
restart: always
|
||||||
|
# Forward Gunicorn access + error logs to /app/logs
|
||||||
|
command: >
|
||||||
|
gunicorn config.wsgi:application
|
||||||
|
--bind 0.0.0.0:8000
|
||||||
|
--workers 2
|
||||||
|
--access-logfile /app/logs/gunicorn-access.log
|
||||||
|
--error-logfile /app/logs/gunicorn-error.log
|
||||||
|
--log-level info
|
||||||
|
--capture-output
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue