From 22800ab230d79ff824d24055967c61286b37f2ef Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Sun, 9 Aug 2026 17:24:57 +0530 Subject: [PATCH] adding the logs service --- .gitignore | 1 + api/views.py | 10 +++++ config/settings.py | 104 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 12 +++++- 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 83972fa..bd203a9 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ cover/ # Django stuff: *.log +logs/ local_settings.py db.sqlite3 db.sqlite3-journal diff --git a/api/views.py b/api/views.py index ea53414..faee505 100644 --- a/api/views.py +++ b/api/views.py @@ -1,3 +1,4 @@ +import logging import random from datetime import date from django.contrib.auth import authenticate, login @@ -13,6 +14,8 @@ from .serializers import ( OrderSerializer, ReturnRequestSerializer, WalletSerializer ) +logger = logging.getLogger('api') + def get_active_user(request): if request.user and request.user.is_authenticated: return request.user @@ -30,7 +33,9 @@ class RegisterView(APIView): if serializer.is_valid(): user = serializer.save() 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) + logger.warning('Registration failed: %s', serializer.errors) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class LoginView(APIView): @@ -40,7 +45,9 @@ class LoginView(APIView): user = authenticate(username=username, password=password) if user: login(request, user) + logger.info('Login successful: username=%s', username) return Response(UserSerializer(user).data) + logger.warning('Login failed: username=%s', username) return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST) class VerifyOtpView(APIView): @@ -190,9 +197,11 @@ class WithdrawView(APIView): try: amount = float(amount_str) 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) 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) wallet.outstanding = float(wallet.outstanding) - amount @@ -207,4 +216,5 @@ class WithdrawView(APIView): status='Transferred' ) + logger.info('Withdraw successful: user=%s amount=%.2f tx_id=%s', user.username, amount, tx.tx_id) return Response(WalletSerializer(wallet).data) diff --git a/config/settings.py b/config/settings.py index f36e729..bc0c4fa 100644 --- a/config/settings.py +++ b/config/settings.py @@ -10,6 +10,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/6.1/ref/settings/ """ +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -139,3 +140,106 @@ REST_FRAMEWORK = { '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, + }, + }, +} diff --git a/docker-compose.yml b/docker-compose.yml index dc51b95..5f0aa46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,9 +4,19 @@ services: web: build: . ports: - - "8000:8000" + - "8080:8000" volumes: - .:/app + - ./logs:/app/logs # persists log files on the host environment: - DEBUG=1 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