diff --git a/.gitignore b/.gitignore index 8e883cc..5199943 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.py[cod] *$py.class +logs/ # C extensions *.so diff --git a/core/settings.py b/core/settings.py index 3eb273a..30acdda 100644 --- a/core/settings.py +++ b/core/settings.py @@ -25,9 +25,6 @@ env = environ.Env() env_file = os.path.join(BASE_DIR, ".env") if os.path.exists(env_file): environ.Env.read_env(env_file) - print(f"Файл .env найден и прочитан: {env_file}") -else: - print(f"ОШИБКА: Файл .env не найден по пути: {env_file}") # читаем переменную окружения ENV_TYPE = os.getenv('ENV_TYPE', 'local') @@ -197,12 +194,4 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') CSRF_TRUSTED_ORIGINS = env.list('CSRF_ORIGINS', default=['http://localhost']) -print(f"--- РАБОТАЕМ НА БАЗЕ: {DATABASES['default']['NAME']} (HOST: {DATABASES['default'].get('HOST', 'localhost')}) ---") - - -# Проверяем, видит ли он базу и режим отладки -print(f"DB_NAME: {env('DB_NAME', default='НЕ НАЙДЕНО')}") -print(f"ENV_TYPE: {env('ENV_TYPE', default='False')}") -print(f"SECRET_KEY: {env('SECRET_KEY', default='False')}") -print(f"CSRF_TRUSTED_ORIGINS: {CSRF_TRUSTED_ORIGINS}") diff --git a/shiftflow/management/commands/dxf_preview_job.py b/shiftflow/management/commands/dxf_preview_job.py index 97e1dea..4259a6f 100644 --- a/shiftflow/management/commands/dxf_preview_job.py +++ b/shiftflow/management/commands/dxf_preview_job.py @@ -1,5 +1,7 @@ +import logging import multiprocessing import os +import sys from django.core.management.base import BaseCommand from django.db import close_old_connections @@ -56,11 +58,25 @@ class Command(BaseCommand): job.started_at = timezone.now() job.finished_at = None job.last_message = "" - job.save(update_fields=["status", "started_at", "finished_at", "last_message"]) + try: + job.pid = os.getpid() + job.save(update_fields=["status", "started_at", "finished_at", "last_message", "pid"]) + except Exception: + job.save(update_fields=["status", "started_at", "finished_at", "last_message"]) + + logger = logging.getLogger('dxf_preview_job') + if not logger.handlers: + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%Y-%m-%d %H:%M:%S')) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + + logger.info('start job=%s pid=%s', job_id, os.getpid()) # Берём настройки таймаута из БД. settings, _ = DxfPreviewSettings.objects.get_or_create(pk=1) per_task_timeout = int(getattr(settings, 'per_task_timeout_sec', 45) or 45) + per_task_timeout = max(10, min(50, per_task_timeout)) deal_statuses = ["lead", "work"] qs = ProductionTask.objects.select_related("deal").filter(deal__status__in=deal_statuses) @@ -79,9 +95,7 @@ class Command(BaseCommand): skipped = 0 errors = 0 - # Таймаут обработки одной детали (сек). - # Если конкретный DXF «залип» — задача не должна блокироваться навсегда. - per_task_timeout = 45 + logger.info('per_task_timeout=%ss', per_task_timeout) try: for task in qs.iterator(chunk_size=50): @@ -106,7 +120,8 @@ class Command(BaseCommand): return # Обрабатываем одну деталь в отдельном процессе и ждём не больше per_task_timeout. - close_old_connections() + # Важно: НЕ вызываем close_old_connections() внутри qs.iterator(), иначе Django может закрыть курсор, + # и итерация по QuerySet упадёт с ошибкой "cursor already closed". q: multiprocessing.Queue = multiprocessing.Queue(maxsize=1) p = multiprocessing.Process(target=_run_one_task_preview, args=(task.id, q)) p.start() @@ -137,8 +152,7 @@ class Command(BaseCommand): skipped += 1 else: errors += 1 - - close_old_connections() + logger.error('error task=%s name=%s deal=%s: %s', task.id, task.drawing_name, task.deal.number, payload) DxfPreviewJob.objects.filter(pk=job_id).update( processed=processed, @@ -153,10 +167,11 @@ class Command(BaseCommand): last_message=f"Готово. Обновлены: {updated}. Пропущено: {skipped}. Ошибок: {errors}.", ) except Exception: + logger.exception('fatal error') DxfPreviewJob.objects.filter(pk=job_id).update( status="failed", finished_at=timezone.now(), - last_message="Задача завершилась с ошибкой (см. логи процесса).", + last_message="Задача завершилась с ошибкой (см. лог на странице обслуживания).", ) finally: close_old_connections() \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/maintenance.html b/shiftflow/templates/shiftflow/maintenance.html index 3ecc70d..ee6d95c 100644 --- a/shiftflow/templates/shiftflow/maintenance.html +++ b/shiftflow/templates/shiftflow/maintenance.html @@ -22,6 +22,9 @@
{% if last_job %}{{ last_job.log_tail }}{% endif %}