diff --git a/.gitignore b/.gitignore index 8d5b05d..5783508 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ node_modules config.py slides exports -log +logs nohup.out diff --git a/app/__init__.py b/app/__init__.py index c7b74ac..5783d57 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,6 @@ -from logging.config import dictConfig +import os +import logging +from logging.handlers import WatchedFileHandler from flask import Flask from flask_socketio import SocketIO from .models import db @@ -9,62 +11,22 @@ from .seed_db import seed_courses socketio = SocketIO(async_mode='eventlet', cors_allowed_origins="*") def create_app(): - # Configure logging BEFORE creating the application object - dictConfig({ - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'default': { - 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', - }, - 'plain': { - 'format': '%(message)s', - } - }, - 'handlers': { - # Console handler (keeps logs visible in terminal) - 'wsgi': { - 'class': 'logging.StreamHandler', - 'stream': 'ext://flask.logging.wsgi_errors_stream', - 'formatter': 'default' - }, - # File handler for Application Logs (slides.log) - 'slides_file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': 'slides.log', - 'maxBytes': 1024 * 1024 * 10, # 10 MB - 'backupCount': 5, - 'formatter': 'default', - 'level': 'INFO', - 'encoding': 'utf8' - }, - # File handler for Request Logs (requests.log) - 'requests_file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': 'requests.log', - 'maxBytes': 1024 * 1024 * 10, # 10 MB - 'backupCount': 5, - 'formatter': 'plain', # Requests already have timestamps/formatting - 'level': 'INFO', - 'encoding': 'utf8' - } - }, - 'root': { - 'level': 'INFO', - 'handlers': ['wsgi', 'slides_file'] - }, - 'loggers': { - # Isolate the Werkzeug request logger - 'werkzeug': { - 'level': 'INFO', - 'handlers': ['wsgi', 'requests_file'], - 'propagate': False # Prevent requests from leaking into slides.log - } - } - }) + # 1. Setup Log Directory + log_dir = os.path.join(os.getcwd(), 'logs') + if not os.path.exists(log_dir): + os.makedirs(log_dir) app = Flask(__name__, static_folder='static', static_url_path='/static') app.config.from_object('config.Config') + + file_handler = WatchedFileHandler(log_dir + '/app.log', mode='a', encoding='UTF-8') + file_handler.setFormatter(logging.Formatter( + '%(asctime)s %(levelname)s [%(module)s:%(lineno)d]: %(message)s' + )) + file_handler.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + app.logger.setLevel(logging.INFO) + db.init_app(app) with app.app_context(): diff --git a/app/routes.py b/app/routes.py index f542c02..037bf6a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -100,15 +100,16 @@ def inject_confused_button(response): def index(): token = get_token() if not token: + current_app.logger.debug("User has no token. Redirecting to set_token.") return redirect(url_for("main.set_token")) else: - # Use current_app.logger for debug info current_app.logger.debug(f"User {token.name} is viewing index with {len(token.decks)} decks unlocked.") return render_template('index.html', active_banner=get_active_banner(), decks=get_unlocked_decks(token), user_name=token.name, active_course_name=token.active_course.name, courses=get_all_courses()) def set_cookie(token): next_url = session.pop('next_url', None) if not next_url: + current_app.logger.debug("next_url not present redirecting to index") next_url = url_for("main.index") resp = make_response(redirect(next_url)) @@ -130,21 +131,22 @@ def set_token(): """ if request.method == 'POST': # Get form data - user_name = request.form.get('username') + username = request.form.get('username') # Simple validation - if not user_name: + if not username: + current_app.logger.debug("Login failed: No username supplied") flash('Username is required.', 'error') return render_template('set_token.html') - token = retrieve_token(user_name) + token = retrieve_token(username) if not token: - flash(f'The username "{user_name}" does not exist.', 'error') - current_app.logger.warning(f"Login failed: Username '{user_name}' not found.") + flash(f'The username "{username}" does not exist.', 'error') + current_app.logger.warning(f"Login failed: Username '{username}' not found.") return render_template('set_token.html') - current_app.logger.info(f"User logged in: {user_name}") + current_app.logger.info(f"User logged in: {username}") return set_cookie(token) return render_template('set_token.html') @@ -165,14 +167,17 @@ def register(): # Simple validation if not user_name: flash('Username is required.', 'error') + current_app.logger.debug("Username is required for registering") return render_template('register.html') if retrieve_token(user_name): flash(f'The username "{user_name}" is already taken.', 'error') + current_app.logger.debug(f"Username {user_name} is already registered") return render_template('register.html') if not acknowledge: flash('You must acknowledge the important information to proceed.', 'error') + current_app.logger.debug("User did not acknowledge register information") return render_template('register.html') # If all validation passes, "register" the user @@ -282,10 +287,6 @@ def access_get(access_code: str): @main.route('/confused', methods=['POST']) def confused(): - token = get_token() - user = token.name if token else "Anonymous" - current_app.logger.info(f"Confused button clicked by: {user}") - socketio.emit( "confused", { @@ -376,7 +377,7 @@ def serve_export(deck, course_folder, path): def admin(): active_course = get_course(flask_login.current_user.active_course_id) if active_course is None: - current_app.logger.error(f"Admin user {flask_login.current_user.id} has invalid active_course_id.") + current_app.logger.error(f"Admin user {flask_login.current_user.id} has no active_course_id.") raise Exception deck_form = DeckForm() password_form = PasswordForm() diff --git a/app/templates/admin.html b/app/templates/admin.html index 742ad49..046add0 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -171,7 +171,10 @@ {% for password in passwords %} {{ password.value }} - {{ password.expires_at }} + + {{ password.expires_at }} + {% if password.decks %}