Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 72 additions & 10 deletions app/api/logs.py
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
Expand All @@ -11,7 +11,6 @@
logger = get_root_logger()



@api_logs.route('/api/logs/', methods=['GET'])
Copy link
Copy Markdown
Collaborator

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/ - была информация о тренировке

def get_logs() -> (dict, int):
"""
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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'])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
1 change: 1 addition & 0 deletions app/mongo_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class Logs(MongoModel):
filename = fields.CharField()
funcName = fields.CharField()
lineno = fields.IntegerField()
trainingId = fields.ObjectIdField(blank=True, required=False)

class StorageMeta(MongoModel):
used_size = fields.IntegerField()
3 changes: 2 additions & 1 deletion app/mongo_odm.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ def __new__(cls):
cls.init_done = True
return cls.instance

def add_log(self, timestamp, serviceName, levelname, levelno, message, pathname, filename, funcName, lineno):
def add_log(self, timestamp, serviceName, levelname, levelno, message, pathname, filename, funcName, lineno, trainingId=None):
return Logs(
timestamp=timestamp,
serviceName=serviceName,
Expand All @@ -846,6 +846,7 @@ def add_log(self, timestamp, serviceName, levelname, levelno, message, pathname,
filename=filename,
funcName=funcName,
lineno=lineno,
trainingId=ObjectId(trainingId) if trainingId else None,
).save()

def get_logs_filtered(self, filters=None, limit=None, offset=None, ordering=None):
Expand Down
35 changes: 35 additions & 0 deletions app/static/js/logWrapper.js
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если подобный ход ((function () {...})()) сделан для запуска на этапе загрузке страницы (или после её загрузки) - лучше поменять на что-то более приличное (addEventListener или подобное)


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(' '),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавьте trainingId и в сообщение

trainingId: trainingId
})
});
}
}

window.logger = new Logger();
})();
10 changes: 5 additions & 5 deletions app/static/js/recording.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
logger.log("call startRecording(). Try to call navigator.mediaDevices.getUserMedia");
logger.log("navigator", navigator);
logger.log("navigator.mediaDevices", navigator.mediaDevices);
$("#alert").hide()
$("#record-contain").show();
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(function (stream) {
Expand Down Expand Up @@ -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)
logger.log('Error on calling avigator.mediaDevices.getUserMedia')
logger.log(err)
$("#alert").show();
$("#error-text").html("Микрофон не доступен!");
});
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/show_all_trainings.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ function buildAllTrainingsTable(trainingsJson) {
allTrainingsTable.appendChild(currentTrainingRowElement);
});
})
.catch(err => console.log(err));
.catch(err => logger.log(err));
}

const REF_PAGE_COUNT = document.getElementById('ref-page-count');
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/show_all_trainings_filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ function createFilter(filterCode, initialValues = "") {
*/
function removeAllFiltersUI() {
for (const key of Object.keys(currentFilters)) {
// console.log($("#" + key + "-filter"))
// logger.log($("#" + key + "-filter"))
$("#" + key + "-filter").remove();
}
}
Expand Down
32 changes: 31 additions & 1 deletion app/static/js/training.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ let pdfDoc,
trainingId,
currentPage;

function waitForLogger(callback, timeout = 3000) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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) {
Expand Down Expand Up @@ -52,6 +70,7 @@ function onNextPage() {
return;
}
pageNum++;
waitForLogger(() => logger.log('[TRAINING] slide_changed', `to=${pageNum}`));
callShowPage();
queueRenderPage(pageNum);
}
Expand All @@ -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_;
Expand All @@ -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);
});
4 changes: 2 additions & 2 deletions app/static/js/training_statistics.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
logger.log(this.currentTime);
}
)
}
Expand Down Expand Up @@ -114,7 +114,7 @@ function setCriteriaResults(s) {
}

function setRecognizedInfo(slides){
console.log(slides)
logger.log(slides)
}

function renderPageButtons(info){
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
logger.log(`File extension ${extension}`)
/* TODO: use list with user-allowed extensions */
if (parts.length < 1 || !user_formats.includes(extension)) {
$("#alert").show();
Expand Down
30 changes: 15 additions & 15 deletions app/static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ <h1>{{ page_title }}</h1>
{% block content %} {% endblock %}
<!--{% include "bug_reports.html" %}-->
{% block footer %} {% include "footer.html" %} {% endblock %}
<script src="{{ versioned_url('js/logWrapper.js') }}"></script>
<script src="{{ versioned_url('js/base.js') }}"></script>
</body>

Expand Down
4 changes: 2 additions & 2 deletions app/templates/dumps.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@
window.location.reload();
}
else{
console.log(data["message"]);
logger.log(data["message"]);
alert('{{ t("Ошибка создания архива.")}}' + '\n' + data["message"])
}
})
.catch(error => {
console.log(error);
logger.log(error);
alert('{{ t("Ошибка создания архива.") + t("Попробуйте позже или обратитесь к администратору") }}')
});
})
Expand Down