-
Notifications
You must be signed in to change notification settings - Fork 1
Client logs #447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Client logs #447
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| from datetime import datetime | ||
| from datetime import datetime | ||
| from app.root_logger import get_root_logger | ||
| from ast import literal_eval | ||
|
|
||
| import inspect | ||
| from flask import Blueprint, request | ||
|
|
||
| from app.lti_session_passback.auth_checkers import check_admin | ||
|
|
@@ -11,7 +11,6 @@ | |
| logger = get_root_logger() | ||
|
|
||
|
|
||
|
|
||
| @api_logs.route('/api/logs/', methods=['GET']) | ||
| def get_logs() -> (dict, int): | ||
| """ | ||
|
|
@@ -26,13 +25,15 @@ def get_logs() -> (dict, int): | |
| try: | ||
| limit = request.args.get('limit', default=None, type=int) | ||
| except Exception as e: | ||
| logger.info('Limit value {} is invalid.\n{}'.format(request.args.get('limit'), e)) | ||
| logger.info('Limit value {} is invalid.\n{}'.format( | ||
| request.args.get('limit'), e)) | ||
| limit = None | ||
|
|
||
| try: | ||
| offset = request.args.get('offset', default=None, type=int) | ||
| except Exception as e: | ||
| logger.info('Offset value {} is invalid.\n{}'.format(request.args.get('offset', default=None), e)) | ||
| logger.info('Offset value {} is invalid.\n{}'.format( | ||
| request.args.get('offset', default=None), e)) | ||
| offset = None | ||
|
|
||
| raw_filters = request.args.get('filter', default=None) | ||
|
|
@@ -42,7 +43,8 @@ def get_logs() -> (dict, int): | |
| if not isinstance(filters, dict): | ||
| filters = None | ||
| except Exception as e: | ||
| logger.info('Filter value {} is invalid.\n{}'.format(raw_filters, e)) | ||
| logger.info( | ||
| 'Filter value {} is invalid.\n{}'.format(raw_filters, e)) | ||
| filters = None | ||
| else: | ||
| filters = raw_filters | ||
|
|
@@ -52,18 +54,22 @@ def get_logs() -> (dict, int): | |
| try: | ||
| ordering = literal_eval(raw_ordering) | ||
| if not isinstance(ordering, list) or not all(map(lambda x: x[1] in [-1, 1], ordering)): | ||
| logger.info('Ordering value {} is invalid.'.format(raw_ordering)) | ||
| logger.info( | ||
| 'Ordering value {} is invalid.'.format(raw_ordering)) | ||
| ordering = None | ||
| except Exception as e: | ||
| logger.info('Ordering value {} is invalid.\n{}'.format(request.args.get('ordering', default=None), e)) | ||
| logger.info('Ordering value {} is invalid.\n{}'.format( | ||
| request.args.get('ordering', default=None), e)) | ||
| ordering = None | ||
| else: | ||
| ordering = raw_ordering | ||
|
|
||
| try: | ||
| logs = LogsDBManager().get_logs_filtered(filters=filters, limit=limit, offset=offset, ordering=ordering) | ||
| logs = LogsDBManager().get_logs_filtered( | ||
| filters=filters, limit=limit, offset=offset, ordering=ordering) | ||
| except Exception as e: | ||
| message = 'Incorrect get_logs_filtered execution, {}: {}.'.format(e.__class__, e) | ||
| message = 'Incorrect get_logs_filtered execution, {}: {}.'.format( | ||
| e.__class__, e) | ||
| logger.warning(message) | ||
| return {'message': message}, 404 | ||
|
|
||
|
|
@@ -84,3 +90,59 @@ def get_logs() -> (dict, int): | |
| logs_json['logs'][str(_id)] = current_log_json | ||
| logs_json['message'] = 'OK' | ||
| return logs_json, 200 | ||
|
|
||
|
|
||
| @api_logs.route('/logs', methods=['POST']) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Добавьте вывод логов в консоль (также как выводятся логи от бэкенда) - это позволит просматривать логи из всех источников в одном месте (и более доступном, чем БД) Возможно стоит даже доработать логгер бэкенда (вынесите в отдельную задачу и назначьте на себя) - чтобы он своим вызовом печатал в консоль и сохранял в БД (без лишних вызовов и прочего) |
||
| def create_log(): | ||
| """ | ||
| Endpoint to receive client logs. | ||
| Expected JSON: | ||
| { | ||
| "timestamp": "...", | ||
| "message": "...", | ||
| "trainingId": "..." | ||
| } | ||
| """ | ||
| # logger.info("Received client log") | ||
| frame = inspect.currentframe() # кадр | ||
| caller = frame.f_back # кадр вызывающей функции | ||
| pathname = caller.f_code.co_filename # путь к файлу вызвывающей функции | ||
| filename = pathname.split('/')[-1] # имя файла | ||
| funcName = caller.f_code.co_name # имя функции | ||
| lineno = caller.f_lineno # номер строки | ||
|
Comment on lines
+107
to
+112
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если мы сохраняем логи с клиента - какой смысл в этих данных от бэкенд-файла? |
||
| try: | ||
| data = request.get_json(force=True) | ||
| if not data: | ||
| return {"message": "Invalid json"}, 400 | ||
|
|
||
| timestamp = data.get("timestamp") | ||
| message = data.get("message") | ||
| training_id = data.get("trainingId") | ||
|
|
||
| if message is None: | ||
| return {"message": "message field is required"}, 400 | ||
|
|
||
| if timestamp: | ||
| timestamp = datetime.fromisoformat( | ||
| timestamp.replace("Z", "+00:00")) | ||
| else: | ||
| timestamp = datetime.now() | ||
|
|
||
| LogsDBManager().add_log( | ||
| timestamp=timestamp, | ||
| serviceName="client", | ||
| levelname="INFO", | ||
| levelno=20, | ||
| message=message, | ||
| pathname=pathname, | ||
| filename=filename, | ||
| funcName=funcName, | ||
| lineno=lineno, | ||
| trainingId=training_id, | ||
| ) | ||
|
|
||
| return {"message": "log received"}, 201 | ||
|
|
||
| except Exception as e: | ||
| logger.warning(f"Client log creation failed: {e}") | ||
| return {"message": "Internal server error"}, 500 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Отформатируйте файл |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| function getTrainingIdFromUrl() { | ||
| // попытка URL с id: /trainings/<training_id>/, /trainings/statistics/<training_id>/ | ||
| const match = window.location.pathname.match(/\/trainings(?:\/statistics)?\/([0-9a-fA-F]{24})\//); | ||
| return match ? match[1] : null; | ||
| } | ||
|
|
||
| (function () { | ||
|
|
||
| // const trainingId = | ||
| // window.APP_CONTEXT?.trainingId ?? null; | ||
|
|
||
| class Logger { | ||
|
Comment on lines
+7
to
+12
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если подобный ход ( |
||
|
|
||
| log(...args) { | ||
|
|
||
| const trainingId = getTrainingIdFromUrl(); | ||
|
|
||
| console.log(...args); | ||
|
|
||
| fetch('/logs', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify({ | ||
| timestamp: new Date().toISOString(), | ||
| message: args.join(' '), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Добавьте trainingId и в сообщение |
||
| trainingId: trainingId | ||
| }) | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| window.logger = new Logger(); | ||
| })(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,24 @@ let pdfDoc, | |
| trainingId, | ||
| currentPage; | ||
|
|
||
| function waitForLogger(callback, timeout = 3000) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В чем смысл waitForLogger? При этом он используется только на этой странице (и то не везде) - на других страницах не нужно его ожидать? |
||
| const start = Date.now(); | ||
|
|
||
| (function check() { | ||
| if (typeof window.logger === 'object' && typeof window.logger.log === 'function') { | ||
| callback(); | ||
| return; | ||
| } | ||
|
|
||
| if (Date.now() - start > timeout) { | ||
| console.error('Logger not loaded'); | ||
| return; | ||
| } | ||
|
|
||
| setTimeout(check, 50); | ||
| })(); | ||
| } | ||
|
|
||
| function renderPage(num) { | ||
| pageRendering = true; | ||
| pdfDoc.getPage(num).then(function(page) { | ||
|
|
@@ -52,6 +70,7 @@ function onNextPage() { | |
| return; | ||
| } | ||
| pageNum++; | ||
| waitForLogger(() => logger.log('[TRAINING] slide_changed', `to=${pageNum}`)); | ||
| callShowPage(); | ||
| queueRenderPage(pageNum); | ||
| } | ||
|
|
@@ -64,12 +83,20 @@ function setPage(pageNum){ | |
| return; | ||
| } | ||
| currentPage = pageNum; | ||
| waitForLogger(() => logger.log('[TRAINING] slide_set', `to=${pageNum}`)); | ||
| changeURLByParam('page', currentPage); | ||
| queueRenderPage(pageNum); | ||
| } | ||
|
|
||
| function setupPresentationViewer(trainingId_) { | ||
| trainingId = trainingId_; | ||
|
|
||
| // logger.setTrainingId(trainingId_); | ||
| waitForLogger(() => { | ||
| logger.log('[TRAINING] presentation_viewer_initialized'); | ||
| }); | ||
|
|
||
|
|
||
| let loadingTask = pdfjsLib.getDocument(`/api/files/presentations/by-training/${trainingId_}/`); | ||
| loadingTask.promise.then(function(pdfDoc_) { | ||
| pdfDoc = pdfDoc_; | ||
|
|
@@ -87,6 +114,9 @@ $(document).ready(function() { | |
| scale = 1.1; | ||
| canvas = $("#the-canvas")[0]; | ||
| ctx = canvas.getContext("2d"); | ||
| $("#done").click(callShowPage); | ||
| $("#done").click(function () { | ||
| waitForLogger(() => logger.log('[TRAINING] done_clicked')); | ||
| callShowPage(); | ||
| }); | ||
| $("#next").click(onNextPage); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Проверьте, чтобы на странице http://localhost:5000/api/logs/ - была информация о тренировке