added logging
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,5 +5,5 @@ node_modules
|
|||||||
config.py
|
config.py
|
||||||
slides
|
slides
|
||||||
exports
|
exports
|
||||||
log
|
logs
|
||||||
nohup.out
|
nohup.out
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from logging.config import dictConfig
|
import os
|
||||||
|
import logging
|
||||||
|
from logging.handlers import WatchedFileHandler
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
from .models import db
|
from .models import db
|
||||||
@@ -9,62 +11,22 @@ from .seed_db import seed_courses
|
|||||||
socketio = SocketIO(async_mode='eventlet', cors_allowed_origins="*")
|
socketio = SocketIO(async_mode='eventlet', cors_allowed_origins="*")
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
# Configure logging BEFORE creating the application object
|
# 1. Setup Log Directory
|
||||||
dictConfig({
|
log_dir = os.path.join(os.getcwd(), 'logs')
|
||||||
'version': 1,
|
if not os.path.exists(log_dir):
|
||||||
'disable_existing_loggers': False,
|
os.makedirs(log_dir)
|
||||||
'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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
||||||
app.config.from_object('config.Config')
|
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)
|
db.init_app(app)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|||||||
@@ -100,15 +100,16 @@ def inject_confused_button(response):
|
|||||||
def index():
|
def index():
|
||||||
token = get_token()
|
token = get_token()
|
||||||
if not token:
|
if not token:
|
||||||
|
current_app.logger.debug("User has no token. Redirecting to set_token.")
|
||||||
return redirect(url_for("main.set_token"))
|
return redirect(url_for("main.set_token"))
|
||||||
else:
|
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.")
|
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())
|
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):
|
def set_cookie(token):
|
||||||
next_url = session.pop('next_url', None)
|
next_url = session.pop('next_url', None)
|
||||||
if not next_url:
|
if not next_url:
|
||||||
|
current_app.logger.debug("next_url not present redirecting to index")
|
||||||
next_url = url_for("main.index")
|
next_url = url_for("main.index")
|
||||||
|
|
||||||
resp = make_response(redirect(next_url))
|
resp = make_response(redirect(next_url))
|
||||||
@@ -130,21 +131,22 @@ def set_token():
|
|||||||
"""
|
"""
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# Get form data
|
# Get form data
|
||||||
user_name = request.form.get('username')
|
username = request.form.get('username')
|
||||||
|
|
||||||
# Simple validation
|
# Simple validation
|
||||||
if not user_name:
|
if not username:
|
||||||
|
current_app.logger.debug("Login failed: No username supplied")
|
||||||
flash('Username is required.', 'error')
|
flash('Username is required.', 'error')
|
||||||
return render_template('set_token.html')
|
return render_template('set_token.html')
|
||||||
|
|
||||||
token = retrieve_token(user_name)
|
token = retrieve_token(username)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
flash(f'The username "{user_name}" does not exist.', 'error')
|
flash(f'The username "{username}" does not exist.', 'error')
|
||||||
current_app.logger.warning(f"Login failed: Username '{user_name}' not found.")
|
current_app.logger.warning(f"Login failed: Username '{username}' not found.")
|
||||||
return render_template('set_token.html')
|
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 set_cookie(token)
|
||||||
|
|
||||||
return render_template('set_token.html')
|
return render_template('set_token.html')
|
||||||
@@ -165,14 +167,17 @@ def register():
|
|||||||
# Simple validation
|
# Simple validation
|
||||||
if not user_name:
|
if not user_name:
|
||||||
flash('Username is required.', 'error')
|
flash('Username is required.', 'error')
|
||||||
|
current_app.logger.debug("Username is required for registering")
|
||||||
return render_template('register.html')
|
return render_template('register.html')
|
||||||
|
|
||||||
if retrieve_token(user_name):
|
if retrieve_token(user_name):
|
||||||
flash(f'The username "{user_name}" is already taken.', 'error')
|
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')
|
return render_template('register.html')
|
||||||
|
|
||||||
if not acknowledge:
|
if not acknowledge:
|
||||||
flash('You must acknowledge the important information to proceed.', 'error')
|
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')
|
return render_template('register.html')
|
||||||
|
|
||||||
# If all validation passes, "register" the user
|
# If all validation passes, "register" the user
|
||||||
@@ -282,10 +287,6 @@ def access_get(access_code: str):
|
|||||||
|
|
||||||
@main.route('/confused', methods=['POST'])
|
@main.route('/confused', methods=['POST'])
|
||||||
def confused():
|
def confused():
|
||||||
token = get_token()
|
|
||||||
user = token.name if token else "Anonymous"
|
|
||||||
current_app.logger.info(f"Confused button clicked by: {user}")
|
|
||||||
|
|
||||||
socketio.emit(
|
socketio.emit(
|
||||||
"confused",
|
"confused",
|
||||||
{
|
{
|
||||||
@@ -376,7 +377,7 @@ def serve_export(deck, course_folder, path):
|
|||||||
def admin():
|
def admin():
|
||||||
active_course = get_course(flask_login.current_user.active_course_id)
|
active_course = get_course(flask_login.current_user.active_course_id)
|
||||||
if active_course is None:
|
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
|
raise Exception
|
||||||
deck_form = DeckForm()
|
deck_form = DeckForm()
|
||||||
password_form = PasswordForm()
|
password_form = PasswordForm()
|
||||||
|
|||||||
@@ -171,7 +171,10 @@
|
|||||||
{% for password in passwords %}
|
{% for password in passwords %}
|
||||||
<tr class="border-b border-border hover:bg-background/30">
|
<tr class="border-b border-border hover:bg-background/30">
|
||||||
<td class="px-4 py-2 font-medium">{{ password.value }}</td>
|
<td class="px-4 py-2 font-medium">{{ password.value }}</td>
|
||||||
<td class="px-4 py-2">{{ password.expires_at }}</td>
|
<td class="px-4 py-2 local-time"
|
||||||
|
data-utc="{{ password.expires_at.strftime('%Y-%m-%dT%H:%M:%SZ') if password.expires_at }}">
|
||||||
|
{{ password.expires_at }}
|
||||||
|
</td>
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
{% if password.decks %}
|
{% if password.decks %}
|
||||||
<ul class="list-disc list-inside text-muted">
|
<ul class="list-disc list-inside text-muted">
|
||||||
@@ -202,6 +205,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.local-time').forEach(el => {
|
||||||
|
const utcStr = el.getAttribute('data-utc');
|
||||||
|
if (utcStr) {
|
||||||
|
// Now utcStr is "2026-02-17T02:44:23Z"
|
||||||
|
const date = new Date(utcStr);
|
||||||
|
|
||||||
|
if (!isNaN(date)) {
|
||||||
|
el.textContent = date.toLocaleString('de-DE', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
npm run build:css
|
npm run build:css
|
||||||
|
|
||||||
nohup .venv/bin/gunicorn \
|
nohup uv run gunicorn \
|
||||||
--bind 127.0.0.1:5000 \
|
--bind 127.0.0.1:5000 \
|
||||||
--worker-class eventlet \
|
--worker-class eventlet \
|
||||||
--workers 1 \
|
--workers 1 \
|
||||||
|
--access-logfile logs/traffic.log \
|
||||||
run:app &
|
run:app &
|
||||||
|
|||||||
Reference in New Issue
Block a user