Skip to content

Commit

Permalink
YDA-5909: add request data to tech support info
Browse files Browse the repository at this point in the history
Add information about requests being handled to the tech support
info that is collected by the monitor thread.
  • Loading branch information
stsnel committed Oct 16, 2024
1 parent e27fe4b commit 849be91
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 7 deletions.
35 changes: 30 additions & 5 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
__license__ = 'GPLv3, see LICENSE'

import json
import threading
from os import path
from typing import Any, Dict, Optional

from flask import Flask, g, redirect, request, Response, send_from_directory, url_for
from flask import Flask, g, redirect, request, Response, send_from_directory, session, url_for
from flask_session import Session
from flask_wtf.csrf import CSRFProtect

Expand Down Expand Up @@ -128,10 +129,12 @@ def load_admin_setting() -> Dict[str, Any]:
Session(app)

# Start monitoring thread for extracting tech support information
with app.app_context():
# Monitor signal file can be set to empty to completely disable monitor thread.
if app.config.get("MONITOR_SIGNAL_FILE", "/var/www/yoda/show-tech.sig") != "":
monitor_thread = Monitor(app.config)
# Monitor signal file can be set to empty to completely disable monitor thread
monitor_enabled: bool = app.config.get("MONITOR_SIGNAL_FILE", "/var/www/yoda/show-tech.sig") != ""
monitor_data: Dict[int, Dict[str, Any]] = {}
if monitor_enabled:
with app.app_context():
monitor_thread: Monitor = Monitor(app.config, monitor_data)
monitor_thread.start()

# Register blueprints
Expand Down Expand Up @@ -203,6 +206,28 @@ def protect_pages() -> Optional[Response]:
return redirect(url_for('user_bp.gate', redirect_target=request.full_path))


@app.before_request
def add_monitor_data() -> None:
if monitor_enabled:
monitor_data[threading.get_ident()] = {"Username": session.get("login_username", None),
"Login time": session.get("login_time", None),
"Request method": request.method,
"Request endpoint": request.endpoint,
"Request arguments": request.args}


@app.after_request
def cleanup_monitor_data(response: Response) -> Response:
if monitor_enabled:
try:
thread_id: int = threading.get_ident()
monitor_data.pop(thread_id)
except KeyError:
# No need to do anything if thread monitor data is not present
pass
return response


@app.after_request
def add_security_headers(response: Response) -> Response:
"""Add security headers."""
Expand Down
11 changes: 10 additions & 1 deletion monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from datetime import datetime
from io import StringIO
from threading import Timer
from typing import Any, Dict

import flask
import humanize
Expand All @@ -21,9 +22,10 @@

class Monitor(Timer):

def __init__(self, config: flask.config.Config):
def __init__(self, config: flask.config.Config, monitor_data: Dict[int, Dict[str, Any]]):
self.interval = 1
self.config = config
self.monitor_data = monitor_data
Timer.__init__(self, self.interval, self.record_info_if_needed)

def get_signal_file(self) -> str:
Expand Down Expand Up @@ -78,6 +80,13 @@ def get_tshoot_info(self) -> StringIO:

for thread_id, stack in sys._current_frames().items():
output.write(f"Thread ID: {thread_id}\n")

thread_monitor_data = self.monitor_data.get(thread_id, {})
for monitor_variable in thread_monitor_data:
monitor_value = str(thread_monitor_data.get(monitor_variable))
output.write(f"{monitor_variable}: {monitor_value}\n")

output.write("Traceback:\n")
for filename, line_number, function_name, line in traceback.extract_stack(stack):
output.write(f" {filename}:{line_number} [{function_name}]\n")
output.write(f" {line}\n" if line else "")
Expand Down
2 changes: 1 addition & 1 deletion unit-tests/test_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
class MonitorTest(TestCase):
def test_can_get_tshoot_info(self) -> None:
config = {"YODA_VERSION": "test_version"}
monitor = Monitor(config)
monitor = Monitor(config, {})
tshoot_info = monitor.get_tshoot_info().getvalue()
self.assertIn("test_version", tshoot_info)
self.assertIn("Thread ID", tshoot_info)
2 changes: 2 additions & 0 deletions user/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import json
import secrets
from datetime import datetime
from typing import List
from uuid import uuid4

Expand Down Expand Up @@ -91,6 +92,7 @@ def login() -> Response:
return render_template('user/login.html', login_placeholder=get_login_placeholder())

session['login_username'] = username
session['login_time'] = datetime.now()
g.login_username = username

# Check if someone isn't trying to sneak past OIDC login.
Expand Down

0 comments on commit 849be91

Please sign in to comment.