diff --git a/app/api/logs.py b/app/api/logs.py index 360f4f1..b14d5c6 100644 --- a/app/api/logs.py +++ b/app/api/logs.py @@ -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,56 @@ 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']) +def create_log(): + """ + Endpoint to receive client logs. + Expected JSON: + { + "timestamp": "...", + "message": "..." + } + """ + # 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 # номер строки + try: + data = request.get_json(force=True) + if not data: + return {"message": "Invalid json"}, 400 + + timestamp = data.get("timestamp") + message = data.get("message") + + 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 + ) + + return {"message": "log received"}, 201 + + except Exception as e: + logger.warning(f"Client log creation failed: {e}") + return {"message": "Internal server error"}, 500 diff --git a/app/static/js/logWrapper.js b/app/static/js/logWrapper.js new file mode 100644 index 0000000..f7c1a11 --- /dev/null +++ b/app/static/js/logWrapper.js @@ -0,0 +1,23 @@ +(function () { + // враппер над логами + window.logWrapper = function (...args) { + // стандартное логирование + console.log(...args); + + // отправка на роут + try { + fetch('/logs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + timestamp: new Date().toISOString(), + message: args.map(a => a.toString()).join(' ') + }) + }); + } catch (e) { + console.error('Log send error', e); + } + } +})(); \ No newline at end of file diff --git a/app/static/js/recording.js b/app/static/js/recording.js index e22200a..28a103b 100644 --- a/app/static/js/recording.js +++ b/app/static/js/recording.js @@ -6,9 +6,9 @@ let gumStream, timer; function startRecording() { - console.log("call startRecording(). Try to call navigator.mediaDevices.getUserMedia"); - console.log("navigator", navigator); - console.log("navigator.mediaDevices", navigator.mediaDevices); + logWrapper("call startRecording(). Try to call navigator.mediaDevices.getUserMedia"); + logWrapper("navigator", navigator); + logWrapper("navigator.mediaDevices", navigator.mediaDevices); $("#alert").hide() $("#record-contain").show(); navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(function (stream) { @@ -82,8 +82,8 @@ function startRecording() { $("#record")[0].disabled = true; $("#done")[0].disabled = false; }).catch( err => { - console.log('Error on calling avigator.mediaDevices.getUserMedia') - console.log(err) + logWrapper('Error on calling avigator.mediaDevices.getUserMedia') + logWrapper(err) $("#alert").show(); $("#error-text").html("Микрофон не доступен!"); }); diff --git a/app/static/js/show_all_trainings.js b/app/static/js/show_all_trainings.js index 2610cd9..9b7b9b2 100644 --- a/app/static/js/show_all_trainings.js +++ b/app/static/js/show_all_trainings.js @@ -161,7 +161,7 @@ function buildAllTrainingsTable(trainingsJson) { allTrainingsTable.appendChild(currentTrainingRowElement); }); }) - .catch(err => console.log(err)); + .catch(err => logWrapper(err)); } const REF_PAGE_COUNT = document.getElementById('ref-page-count'); diff --git a/app/static/js/show_all_trainings_filters.js b/app/static/js/show_all_trainings_filters.js index 4a73579..7c67e33 100644 --- a/app/static/js/show_all_trainings_filters.js +++ b/app/static/js/show_all_trainings_filters.js @@ -578,7 +578,7 @@ function createFilter(filterCode, initialValues = "") { */ function removeAllFiltersUI() { for (const key of Object.keys(currentFilters)) { - // console.log($("#" + key + "-filter")) + // logWrapper($("#" + key + "-filter")) $("#" + key + "-filter").remove(); } } diff --git a/app/static/js/training_statistics.js b/app/static/js/training_statistics.js index ab7f282..49f9b73 100644 --- a/app/static/js/training_statistics.js +++ b/app/static/js/training_statistics.js @@ -58,7 +58,7 @@ function configureAudio(info) { if (this.currentTime > info[info.length-1]) setPage(info.length, info); changeURLByParam('time', this.currentTime.toFixed(1)); - console.log(this.currentTime); + logWrapper(this.currentTime); } ) } @@ -114,7 +114,7 @@ function setCriteriaResults(s) { } function setRecognizedInfo(slides){ - console.log(slides) + logWrapper(slides) } function renderPageButtons(info){ diff --git a/app/static/js/upload.js b/app/static/js/upload.js index 22b22bf..5e600a2 100644 --- a/app/static/js/upload.js +++ b/app/static/js/upload.js @@ -80,7 +80,7 @@ function fileLoadingOnChange() { const file = $("#file-loading").prop("files")[0]; let parts = file.name.split("."); let extension = parts.pop().toLowerCase(); - console.log(`File extension ${extension}`) + logWrapper(`File extension ${extension}`) /* TODO: use list with user-allowed extensions */ if (parts.length < 1 || !user_formats.includes(extension)) { $("#alert").show(); diff --git a/app/static/js/utils.js b/app/static/js/utils.js index 5394d6c..62b144c 100644 --- a/app/static/js/utils.js +++ b/app/static/js/utils.js @@ -8,24 +8,24 @@ function buildTitleRow(columns) { return titleRowElement; } -function recheck(trainingId){ +function recheck(trainingId) { fetch('/api/sessions/admin') - .then(response => response.json()) - .then(res => { - if (res.admin) { - fetch(`/api/trainings/${trainingId}/`, {method: "POST"}) - .then(response => response.json()) - .then(innerResponseJson => { - if (innerResponseJson["message"] === "OK") { - window.open(`/trainings/statistics/${trainingId}/`); - //location.href = `/trainings/statistics/${trainingId}/`; - } - }); - } - }); + .then(response => response.json()) + .then(res => { + if (res.admin) { + fetch(`/api/trainings/${trainingId}/`, { method: "POST" }) + .then(response => response.json()) + .then(innerResponseJson => { + if (innerResponseJson["message"] === "OK") { + window.open(`/trainings/statistics/${trainingId}/`); + //location.href = `/trainings/statistics/${trainingId}/`; + } + }); + } + }); } -function strtobool(val, onError= false) { +function strtobool(val, onError = false) { try { val = val.toLowerCase(); if (['y', 'yes', 't', 'true', 'on', '1'].includes(val)) { diff --git a/app/templates/base.html b/app/templates/base.html index f09ef97..756cc36 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -16,6 +16,7 @@

{{ page_title }}

{% block content %} {% endblock %} {% block footer %} {% include "footer.html" %} {% endblock %} + diff --git a/app/templates/dumps.html b/app/templates/dumps.html index ae78056..212c93a 100644 --- a/app/templates/dumps.html +++ b/app/templates/dumps.html @@ -40,12 +40,12 @@ window.location.reload(); } else{ - console.log(data["message"]); + logWrapper(data["message"]); alert('{{ t("Ошибка создания архива.")}}' + '\n' + data["message"]) } }) .catch(error => { - console.log(error); + logWrapper(error); alert('{{ t("Ошибка создания архива.") + t("Попробуйте позже или обратитесь к администратору") }}') }); })