debug_log: таблица + логи на каждом шаге _chunk_upload, v1.19
This commit is contained in:
+30
-8
@@ -17,6 +17,15 @@ from api import api_bp
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
import time as _time
|
||||||
|
|
||||||
|
def _log(step, detail=""):
|
||||||
|
"""Писать отладочный лог в таблицу debug_log."""
|
||||||
|
try:
|
||||||
|
db.execute("INSERT INTO debug_log (step, detail) VALUES (%s,%s)", (step, str(detail)[:500]))
|
||||||
|
except:
|
||||||
|
pass # не мешать основному потоку
|
||||||
|
|
||||||
|
|
||||||
def _save_file_to_db(filename, file_bytes, mime, contract_id, conn=None):
|
def _save_file_to_db(filename, file_bytes, mime, contract_id, conn=None):
|
||||||
"""Сохранить файл в БД. Если conn передан — использовать его, иначе свои подключения."""
|
"""Сохранить файл в БД. Если conn передан — использовать его, иначе свои подключения."""
|
||||||
@@ -136,35 +145,42 @@ class ContractsApp:
|
|||||||
chunk_data = chunk_file.read()
|
chunk_data = chunk_file.read()
|
||||||
mime = mimeutil.guess_mime(filename) or "application/octet-stream"
|
mime = mimeutil.guess_mime(filename) or "application/octet-stream"
|
||||||
|
|
||||||
|
_log("chunk_start", f"{filename} idx={chunk_index}/{total_chunks} size={len(chunk_data)} cid={cid}")
|
||||||
|
|
||||||
# Сохранить чанк в БД (ON CONFLICT — идемпотентно)
|
# Сохранить чанк в БД (ON CONFLICT — идемпотентно)
|
||||||
db.execute(
|
rc, err = db.execute(
|
||||||
"INSERT INTO chunks (upload_id, chunk_index, chunk_data, filename, mime, total_chunks, cid) "
|
"INSERT INTO chunks (upload_id, chunk_index, chunk_data, filename, mime, total_chunks, cid) "
|
||||||
"VALUES (%s,%s,%s,%s,%s,%s,%s) ON CONFLICT (upload_id, chunk_index) DO NOTHING",
|
"VALUES (%s,%s,%s,%s,%s,%s,%s) ON CONFLICT (upload_id, chunk_index) DO NOTHING",
|
||||||
(upload_id, chunk_index, chunk_data, filename, mime, total_chunks, cid),
|
(upload_id, chunk_index, chunk_data, filename, mime, total_chunks, cid),
|
||||||
)
|
)
|
||||||
|
_log("chunk_insert", f"idx={chunk_index} rc={rc} err={err}")
|
||||||
|
|
||||||
# Сколько уже получено
|
# Сколько уже получено
|
||||||
count_res, _ = db.query(
|
count_res, cerr = db.query(
|
||||||
"SELECT COUNT(*) FROM chunks WHERE upload_id=%s", (upload_id,)
|
"SELECT COUNT(*) FROM chunks WHERE upload_id=%s", (upload_id,)
|
||||||
)
|
)
|
||||||
received = count_res["rows"][0][0] if count_res else 0
|
received = count_res["rows"][0][0] if count_res else 0
|
||||||
|
_log("chunk_count", f"received={received}/{total_chunks} err={cerr}")
|
||||||
|
|
||||||
if received == total_chunks:
|
if received == total_chunks:
|
||||||
|
_log("assembly_start", f"{filename} {total_chunks} chunks, {sum(len(c) if c else 0 for c in [chunk_data])}b this chunk")
|
||||||
try:
|
try:
|
||||||
# ОДНО соединение на всю сборку + сохранение
|
|
||||||
conn, err = db.connect()
|
conn, err = db.connect()
|
||||||
|
_log("assembly_connect", f"err={err}")
|
||||||
if err:
|
if err:
|
||||||
return jsonify({"error": f"БД: {err}"}), 500
|
return jsonify({"error": f"БД: {err}"}), 500
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# Собрать все чанки по порядку
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT chunk_data FROM chunks WHERE upload_id=%s ORDER BY chunk_index",
|
"SELECT chunk_data FROM chunks WHERE upload_id=%s ORDER BY chunk_index",
|
||||||
(upload_id,),
|
(upload_id,),
|
||||||
)
|
)
|
||||||
file_bytes = b"".join(r[0] for r in cur.fetchall())
|
all_chunks = cur.fetchall()
|
||||||
|
_log("assembly_select", f"{len(all_chunks)} rows")
|
||||||
|
|
||||||
|
file_bytes = b"".join(r[0] for r in all_chunks)
|
||||||
|
_log("assembly_joined", f"{len(file_bytes)} bytes")
|
||||||
|
|
||||||
# Метаданные
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT filename, mime, cid FROM chunks WHERE upload_id=%s LIMIT 1",
|
"SELECT filename, mime, cid FROM chunks WHERE upload_id=%s LIMIT 1",
|
||||||
(upload_id,),
|
(upload_id,),
|
||||||
@@ -173,8 +189,8 @@ class ContractsApp:
|
|||||||
fname = meta[0] if meta else filename
|
fname = meta[0] if meta else filename
|
||||||
fmime = meta[1] if meta else mime
|
fmime = meta[1] if meta else mime
|
||||||
fcid = meta[2] if meta else cid
|
fcid = meta[2] if meta else cid
|
||||||
|
_log("assembly_meta", f"fname={fname} mime={fmime} cid={fcid}")
|
||||||
|
|
||||||
# Контракт если нет
|
|
||||||
if fcid:
|
if fcid:
|
||||||
contract_id = fcid
|
contract_id = fcid
|
||||||
else:
|
else:
|
||||||
@@ -183,11 +199,13 @@ class ContractsApp:
|
|||||||
("б/н " + __import__("datetime").datetime.now().strftime("%Y%m%d-%H%M"), ""),
|
("б/н " + __import__("datetime").datetime.now().strftime("%Y%m%d-%H%M"), ""),
|
||||||
)
|
)
|
||||||
contract_id = cur.fetchone()[0]
|
contract_id = cur.fetchone()[0]
|
||||||
|
_log("assembly_contract", f"new cid={contract_id}")
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
_log("assembly_commit", "ok")
|
||||||
|
|
||||||
# Сохранить файл через то же соединение
|
|
||||||
doc_id = _save_file_to_db(fname, file_bytes, fmime, str(contract_id), conn=conn)
|
doc_id = _save_file_to_db(fname, file_bytes, fmime, str(contract_id), conn=conn)
|
||||||
|
_log("save_file", f"doc_id={doc_id}")
|
||||||
if not doc_id:
|
if not doc_id:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
cur.close()
|
cur.close()
|
||||||
@@ -196,7 +214,9 @@ class ContractsApp:
|
|||||||
|
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
_log("assembly_done", f"cid={contract_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
_log("assembly_error", str(e))
|
||||||
try: conn.rollback()
|
try: conn.rollback()
|
||||||
except: pass
|
except: pass
|
||||||
try: cur.close()
|
try: cur.close()
|
||||||
@@ -206,9 +226,11 @@ class ContractsApp:
|
|||||||
return jsonify({"error": f"Сборка: {e}"}), 500
|
return jsonify({"error": f"Сборка: {e}"}), 500
|
||||||
finally:
|
finally:
|
||||||
db.execute("DELETE FROM chunks WHERE upload_id=%s", (upload_id,))
|
db.execute("DELETE FROM chunks WHERE upload_id=%s", (upload_id,))
|
||||||
|
_log("chunks_cleanup", f"upload_id={upload_id}")
|
||||||
|
|
||||||
return jsonify({"contract_id": str(contract_id)})
|
return jsonify({"contract_id": str(contract_id)})
|
||||||
|
|
||||||
|
_log("chunk_ok", f"idx={chunk_index} received={received}/{total_chunks}")
|
||||||
return jsonify({"chunk": chunk_index, "received": received, "total": total_chunks})
|
return jsonify({"chunk": chunk_index, "received": received, "total": total_chunks})
|
||||||
|
|
||||||
def _show_contract(self, cid):
|
def _show_contract(self, cid):
|
||||||
|
|||||||
@@ -116,6 +116,17 @@ CREATE TABLE IF NOT EXISTS chunks (
|
|||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# ── Таблица debug_log ─────────────────────────────────────────
|
||||||
|
|
||||||
|
DDL_DEBUG_LOG = """
|
||||||
|
CREATE TABLE IF NOT EXISTS debug_log (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
step TEXT NOT NULL,
|
||||||
|
detail TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
# ── Список всех DDL ────────────────────────────────────────────
|
# ── Список всех DDL ────────────────────────────────────────────
|
||||||
|
|
||||||
ALL_DDL = [
|
ALL_DDL = [
|
||||||
@@ -125,6 +136,7 @@ ALL_DDL = [
|
|||||||
("spec_rows", DDL_SPEC_ROWS),
|
("spec_rows", DDL_SPEC_ROWS),
|
||||||
("spec_history", DDL_SPEC_HISTORY),
|
("spec_history", DDL_SPEC_HISTORY),
|
||||||
("chunks", DDL_CHUNKS),
|
("chunks", DDL_CHUNKS),
|
||||||
|
("debug_log", DDL_DEBUG_LOG),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.18</span></span>
|
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.19</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
Reference in New Issue
Block a user