Improve logging shutdown (#40)

* Add proper logger resources release
* Track opened loggers
main
Maxim Zhiltsov 4 years ago committed by GitHub
parent 1598e0c5be
commit 2fda97cd5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,9 @@
import logging import logging
import sys import sys
from typing import Dict
from attr import define, field
from cvat.settings.base import LOGGING from cvat.settings.base import LOGGING
from .models import Job, Task, Project, CloudStorage from .models import Job, Task, Project, CloudStorage
@ -31,6 +34,8 @@ def _get_storage(storage_id):
except Exception: except Exception:
raise Exception('{} key must be a cloud storage identifier'.format(storage_id)) raise Exception('{} key must be a cloud storage identifier'.format(storage_id))
_opened_loggers: Dict[str, logging.Logger] = {}
def get_logger(logger_name, log_file): def get_logger(logger_name, log_file):
logger = logging.getLogger(name=logger_name) logger = logging.getLogger(name=logger_name)
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
@ -40,19 +45,39 @@ def get_logger(logger_name, log_file):
logger.addHandler(file_handler) logger.addHandler(file_handler)
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
logger.addHandler(logging.StreamHandler(sys.stderr)) logger.addHandler(logging.StreamHandler(sys.stderr))
_opened_loggers[logger_name] = logger
return logger return logger
class ProjectLoggerStorage: def _close_logger(logger: logging.Logger):
for handler in logger.handlers:
handler.close()
class LogManager:
def close(self):
raise NotImplementedError
class IndexedLogManager(LogManager):
def __init__(self): def __init__(self):
self._storage = dict() self._storage: Dict[int, logging.Logger] = {}
def close(self):
for logger in self._storage.values():
_close_logger(logger)
def __getitem__(self, pid): self._storage = {}
"""Get ceratain storage object for some project."""
if pid not in self._storage:
self._storage[pid] = self._create_project_logger(pid)
return self._storage[pid]
def _create_project_logger(self, pid): def __getitem__(self, idx: int) -> logging.Logger:
"""Get logger object"""
if idx not in self._storage:
self._storage[idx] = self._create_logger(idx)
return self._storage[idx]
def _create_logger(self, _: int) -> logging.Logger:
raise NotImplementedError
class ProjectLoggerStorage(IndexedLogManager):
def _create_logger(self, pid):
project = _get_project(pid) project = _get_project(pid)
logger = logging.getLogger('cvat.server.project_{}'.format(pid)) logger = logging.getLogger('cvat.server.project_{}'.format(pid))
@ -64,16 +89,8 @@ class ProjectLoggerStorage:
return logger return logger
class TaskLoggerStorage: class TaskLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, tid):
self._storage = dict()
def __getitem__(self, tid):
if tid not in self._storage:
self._storage[tid] = self._create_task_logger(tid)
return self._storage[tid]
def _create_task_logger(self, tid):
task = _get_task(tid) task = _get_task(tid)
logger = logging.getLogger('cvat.server.task_{}'.format(tid)) logger = logging.getLogger('cvat.server.task_{}'.format(tid))
@ -84,30 +101,13 @@ class TaskLoggerStorage:
return logger return logger
class JobLoggerStorage: class JobLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, jid):
self._storage = dict()
def __getitem__(self, jid):
if jid not in self._storage:
self._storage[jid] = self._get_task_logger(jid)
return self._storage[jid]
def _get_task_logger(self, jid):
job = _get_job(jid) job = _get_job(jid)
return slogger.task[job.segment.task.id] return slogger.task[job.segment.task.id]
class CloudSourceLoggerStorage: class CloudSourceLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, sid):
self._storage = dict()
def __getitem__(self, sid):
"""Get ceratain storage object for some cloud storage."""
if sid not in self._storage:
self._storage[sid] = self._create_cloud_storage_logger(sid)
return self._storage[sid]
def _create_cloud_storage_logger(self, sid):
cloud_storage = _get_storage(sid) cloud_storage = _get_storage(sid)
logger = logging.getLogger('cvat.server.cloud_storage_{}'.format(sid)) logger = logging.getLogger('cvat.server.cloud_storage_{}'.format(sid))
@ -118,17 +118,8 @@ class CloudSourceLoggerStorage:
return logger return logger
class ProjectClientLoggerStorage: class ProjectClientLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, pid):
self._storage = dict()
def __getitem__(self, pid):
"""Get logger for exact task by id."""
if pid not in self._storage:
self._storage[pid] = self._create_client_logger(pid)
return self._storage[pid]
def _create_client_logger(self, pid):
project = _get_project(pid) project = _get_project(pid)
logger = logging.getLogger('cvat.client.project_{}'.format(pid)) logger = logging.getLogger('cvat.client.project_{}'.format(pid))
client_file = logging.FileHandler(filename=project.get_client_log_path()) client_file = logging.FileHandler(filename=project.get_client_log_path())
@ -136,16 +127,8 @@ class ProjectClientLoggerStorage:
return logger return logger
class TaskClientLoggerStorage: class TaskClientLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, tid):
self._storage = dict()
def __getitem__(self, tid):
if tid not in self._storage:
self._storage[tid] = self._create_client_logger(tid)
return self._storage[tid]
def _create_client_logger(self, tid):
task = _get_task(tid) task = _get_task(tid)
logger = logging.getLogger('cvat.client.task_{}'.format(tid)) logger = logging.getLogger('cvat.client.task_{}'.format(tid))
client_file = logging.FileHandler(filename=task.get_client_log_path()) client_file = logging.FileHandler(filename=task.get_client_log_path())
@ -153,36 +136,42 @@ class TaskClientLoggerStorage:
return logger return logger
class JobClientLoggerStorage: class JobClientLoggerStorage(IndexedLogManager):
def __init__(self): def _create_logger(self, jid):
self._storage = dict()
def __getitem__(self, jid):
if jid not in self._storage:
self._storage[jid] = self._get_task_logger(jid)
return self._storage[jid]
def _get_task_logger(self, jid):
job = _get_job(jid) job = _get_job(jid)
return clogger.task[job.segment.task.id] return clogger.task[job.segment.task.id]
class dotdict(dict): @define(slots=False)
"""dot.notation access to dictionary attributes""" class _AggregateLogManager(LogManager):
__getattr__ = dict.get def close(self):
__setattr__ = dict.__setitem__ for logger in vars(self).values(): # vars is incompatible with slots
__delattr__ = dict.__delitem__ if hasattr(logger, 'close'):
logger.close()
clogger = dotdict({
'project': ProjectClientLoggerStorage(), @define(slots=False)
'task': TaskClientLoggerStorage(), class ClientLogManager(_AggregateLogManager):
'job': JobClientLoggerStorage(), project = field(factory=ProjectClientLoggerStorage)
'glob': logging.getLogger('cvat.client'), task = field(factory=TaskClientLoggerStorage)
}) job = field(factory=JobClientLoggerStorage)
glob = field(factory=lambda: logging.getLogger('cvat.client'))
slogger = dotdict({
'project': ProjectLoggerStorage(), clogger = ClientLogManager()
'task': TaskLoggerStorage(),
'job': JobLoggerStorage(), @define(slots=False)
'cloud_storage': CloudSourceLoggerStorage(), class ServerLogManager(_AggregateLogManager):
'glob': logging.getLogger('cvat.server'), project = field(factory=ProjectLoggerStorage)
}) task = field(factory=TaskLoggerStorage)
job = field(factory=JobLoggerStorage)
cloud_storage = field(factory=CloudSourceLoggerStorage)
glob = field(factory=lambda: logging.getLogger('cvat.server'))
slogger = ServerLogManager()
def close_all():
"""Closes all opened loggers"""
clogger.close()
slogger.close()
for logger in _opened_loggers.values():
_close_logger(logger)

@ -1,4 +1,4 @@
attrs==21.2.0 attrs==21.4.0
click==7.1.2 click==7.1.2
Django==3.2.13 Django==3.2.13
django-appconf==1.0.4 django-appconf==1.0.4

@ -12,6 +12,7 @@ from django.conf import settings
from PIL import Image from PIL import Image
from rest_framework.test import APITestCase, RequestsClient from rest_framework.test import APITestCase, RequestsClient
import cvat.apps.engine.log as log
from cvat.apps.engine.tests.test_rest_api import (create_db_users, from cvat.apps.engine.tests.test_rest_api import (create_db_users,
generate_image_file) generate_image_file)
from utils.cli.core import CLI, CVAT_API_V2, ResourceType from utils.cli.core import CLI, CVAT_API_V2, ResourceType
@ -35,6 +36,10 @@ class TestCLI(APITestCase):
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
log.addHandler(logging.StreamHandler(sys.stdout)) log.addHandler(logging.StreamHandler(sys.stdout))
def tearDown(self):
super().tearDown()
log.close_all() # Release logging resources correctly
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()

Loading…
Cancel
Save