yangdx
commited on
Commit
·
8c0a0a8
1
Parent(s):
e9cfd68
Enhance logging system with file rotation and unified configuration
Browse files• Unify logging across Gunicorn and Uvicorn
• Add rotating file handlers
- gunicorn_config.py +58 -2
- lightrag/api/lightrag_server.py +33 -4
- lightrag/api/routers/document_routes.py +31 -31
- lightrag/utils.py +40 -7
- run_with_gunicorn.py +4 -0
gunicorn_config.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
# gunicorn_config.py
|
2 |
import os
|
|
|
|
|
|
|
3 |
from lightrag.kg.shared_storage import finalize_share_data
|
4 |
from lightrag.api.utils_api import parse_args
|
5 |
|
@@ -27,11 +30,64 @@ if args.ssl:
|
|
27 |
certfile = args.ssl_certfile
|
28 |
keyfile = args.ssl_keyfile
|
29 |
|
|
|
|
|
|
|
30 |
# Logging configuration
|
31 |
-
errorlog = os.getenv("ERROR_LOG",
|
32 |
-
accesslog = os.getenv("ACCESS_LOG",
|
33 |
loglevel = os.getenv("LOG_LEVEL", "info")
|
34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
def on_starting(server):
|
37 |
"""
|
|
|
1 |
# gunicorn_config.py
|
2 |
import os
|
3 |
+
import logging
|
4 |
+
from logging.config import dictConfig
|
5 |
+
from logging.handlers import RotatingFileHandler
|
6 |
from lightrag.kg.shared_storage import finalize_share_data
|
7 |
from lightrag.api.utils_api import parse_args
|
8 |
|
|
|
30 |
certfile = args.ssl_certfile
|
31 |
keyfile = args.ssl_keyfile
|
32 |
|
33 |
+
# 获取日志文件路径
|
34 |
+
log_file_path = os.path.abspath(os.path.join(os.getcwd(), "lightrag.log"))
|
35 |
+
|
36 |
# Logging configuration
|
37 |
+
errorlog = os.getenv("ERROR_LOG", log_file_path) # 默认写入到 lightrag.log
|
38 |
+
accesslog = os.getenv("ACCESS_LOG", log_file_path) # 默认写入到 lightrag.log
|
39 |
loglevel = os.getenv("LOG_LEVEL", "info")
|
40 |
|
41 |
+
# 配置日志系统
|
42 |
+
logconfig_dict = {
|
43 |
+
'version': 1,
|
44 |
+
'disable_existing_loggers': False,
|
45 |
+
'formatters': {
|
46 |
+
'standard': {
|
47 |
+
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
48 |
+
},
|
49 |
+
},
|
50 |
+
'handlers': {
|
51 |
+
'console': {
|
52 |
+
'class': 'logging.StreamHandler',
|
53 |
+
'level': 'INFO',
|
54 |
+
'formatter': 'standard',
|
55 |
+
'stream': 'ext://sys.stdout'
|
56 |
+
},
|
57 |
+
'file': {
|
58 |
+
'class': 'logging.handlers.RotatingFileHandler',
|
59 |
+
'level': 'INFO',
|
60 |
+
'formatter': 'standard',
|
61 |
+
'filename': log_file_path,
|
62 |
+
'maxBytes': 10485760, # 10MB
|
63 |
+
'backupCount': 5,
|
64 |
+
'encoding': 'utf8'
|
65 |
+
}
|
66 |
+
},
|
67 |
+
'loggers': {
|
68 |
+
'lightrag': {
|
69 |
+
'handlers': ['console', 'file'],
|
70 |
+
'level': 'INFO',
|
71 |
+
'propagate': False
|
72 |
+
},
|
73 |
+
'uvicorn': {
|
74 |
+
'handlers': ['console', 'file'],
|
75 |
+
'level': 'INFO',
|
76 |
+
'propagate': False
|
77 |
+
},
|
78 |
+
'gunicorn': {
|
79 |
+
'handlers': ['console', 'file'],
|
80 |
+
'level': 'INFO',
|
81 |
+
'propagate': False
|
82 |
+
},
|
83 |
+
'gunicorn.error': {
|
84 |
+
'handlers': ['console', 'file'],
|
85 |
+
'level': 'INFO',
|
86 |
+
'propagate': False
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
|
92 |
def on_starting(server):
|
93 |
"""
|
lightrag/api/lightrag_server.py
CHANGED
@@ -438,13 +438,20 @@ def get_application():
|
|
438 |
|
439 |
def configure_logging():
|
440 |
"""Configure logging for both uvicorn and lightrag"""
|
|
|
|
|
|
|
|
|
|
|
441 |
# Reset any existing handlers to ensure clean configuration
|
442 |
-
for logger_name in ["uvicorn.access", "lightrag"]:
|
443 |
logger = logging.getLogger(logger_name)
|
444 |
logger.handlers = []
|
445 |
logger.filters = []
|
446 |
|
447 |
# Configure basic logging
|
|
|
|
|
448 |
logging.config.dictConfig(
|
449 |
{
|
450 |
"version": 1,
|
@@ -453,23 +460,45 @@ def configure_logging():
|
|
453 |
"default": {
|
454 |
"format": "%(levelname)s: %(message)s",
|
455 |
},
|
|
|
|
|
|
|
456 |
},
|
457 |
"handlers": {
|
458 |
-
"
|
459 |
"formatter": "default",
|
460 |
"class": "logging.StreamHandler",
|
461 |
"stream": "ext://sys.stderr",
|
462 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
463 |
},
|
464 |
"loggers": {
|
|
|
|
|
|
|
|
|
|
|
|
|
465 |
"uvicorn.access": {
|
466 |
-
"handlers": ["
|
467 |
"level": "INFO",
|
468 |
"propagate": False,
|
469 |
"filters": ["path_filter"],
|
470 |
},
|
|
|
|
|
|
|
|
|
|
|
471 |
"lightrag": {
|
472 |
-
"handlers": ["
|
473 |
"level": "INFO",
|
474 |
"propagate": False,
|
475 |
"filters": ["path_filter"],
|
|
|
438 |
|
439 |
def configure_logging():
|
440 |
"""Configure logging for both uvicorn and lightrag"""
|
441 |
+
# Check if running under Gunicorn
|
442 |
+
if "GUNICORN_CMD_ARGS" in os.environ:
|
443 |
+
# If started with Gunicorn, return directly as Gunicorn will handle logging
|
444 |
+
return
|
445 |
+
|
446 |
# Reset any existing handlers to ensure clean configuration
|
447 |
+
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "lightrag"]:
|
448 |
logger = logging.getLogger(logger_name)
|
449 |
logger.handlers = []
|
450 |
logger.filters = []
|
451 |
|
452 |
# Configure basic logging
|
453 |
+
log_file_path = os.path.abspath(os.path.join(os.getcwd(), "lightrag.log"))
|
454 |
+
|
455 |
logging.config.dictConfig(
|
456 |
{
|
457 |
"version": 1,
|
|
|
460 |
"default": {
|
461 |
"format": "%(levelname)s: %(message)s",
|
462 |
},
|
463 |
+
"detailed": {
|
464 |
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
465 |
+
},
|
466 |
},
|
467 |
"handlers": {
|
468 |
+
"console": {
|
469 |
"formatter": "default",
|
470 |
"class": "logging.StreamHandler",
|
471 |
"stream": "ext://sys.stderr",
|
472 |
},
|
473 |
+
"file": {
|
474 |
+
"formatter": "detailed",
|
475 |
+
"class": "logging.handlers.RotatingFileHandler",
|
476 |
+
"filename": log_file_path,
|
477 |
+
"maxBytes": 10*1024*1024, # 10MB
|
478 |
+
"backupCount": 5,
|
479 |
+
"encoding": "utf-8",
|
480 |
+
},
|
481 |
},
|
482 |
"loggers": {
|
483 |
+
# Configure all uvicorn related loggers
|
484 |
+
"uvicorn": {
|
485 |
+
"handlers": ["console", "file"],
|
486 |
+
"level": "INFO",
|
487 |
+
"propagate": False,
|
488 |
+
},
|
489 |
"uvicorn.access": {
|
490 |
+
"handlers": ["console", "file"],
|
491 |
"level": "INFO",
|
492 |
"propagate": False,
|
493 |
"filters": ["path_filter"],
|
494 |
},
|
495 |
+
"uvicorn.error": {
|
496 |
+
"handlers": ["console", "file"],
|
497 |
+
"level": "INFO",
|
498 |
+
"propagate": False,
|
499 |
+
},
|
500 |
"lightrag": {
|
501 |
+
"handlers": ["console", "file"],
|
502 |
"level": "INFO",
|
503 |
"propagate": False,
|
504 |
"filters": ["path_filter"],
|
lightrag/api/routers/document_routes.py
CHANGED
@@ -3,7 +3,7 @@ This module contains all document-related routes for the LightRAG API.
|
|
3 |
"""
|
4 |
|
5 |
import asyncio
|
6 |
-
import
|
7 |
import aiofiles
|
8 |
import shutil
|
9 |
import traceback
|
@@ -147,7 +147,7 @@ class DocumentManager:
|
|
147 |
"""Scan input directory for new files"""
|
148 |
new_files = []
|
149 |
for ext in self.supported_extensions:
|
150 |
-
|
151 |
for file_path in self.input_dir.rglob(f"*{ext}"):
|
152 |
if file_path not in self.indexed_files:
|
153 |
new_files.append(file_path)
|
@@ -266,7 +266,7 @@ async def pipeline_enqueue_file(rag: LightRAG, file_path: Path) -> bool:
|
|
266 |
)
|
267 |
content += "\n"
|
268 |
case _:
|
269 |
-
|
270 |
f"Unsupported file type: {file_path.name} (extension {ext})"
|
271 |
)
|
272 |
return False
|
@@ -274,20 +274,20 @@ async def pipeline_enqueue_file(rag: LightRAG, file_path: Path) -> bool:
|
|
274 |
# Insert into the RAG queue
|
275 |
if content:
|
276 |
await rag.apipeline_enqueue_documents(content)
|
277 |
-
|
278 |
return True
|
279 |
else:
|
280 |
-
|
281 |
|
282 |
except Exception as e:
|
283 |
-
|
284 |
-
|
285 |
finally:
|
286 |
if file_path.name.startswith(temp_prefix):
|
287 |
try:
|
288 |
file_path.unlink()
|
289 |
except Exception as e:
|
290 |
-
|
291 |
return False
|
292 |
|
293 |
|
@@ -303,8 +303,8 @@ async def pipeline_index_file(rag: LightRAG, file_path: Path):
|
|
303 |
await rag.apipeline_process_enqueue_documents()
|
304 |
|
305 |
except Exception as e:
|
306 |
-
|
307 |
-
|
308 |
|
309 |
|
310 |
async def pipeline_index_files(rag: LightRAG, file_paths: List[Path]):
|
@@ -328,8 +328,8 @@ async def pipeline_index_files(rag: LightRAG, file_paths: List[Path]):
|
|
328 |
if enqueued:
|
329 |
await rag.apipeline_process_enqueue_documents()
|
330 |
except Exception as e:
|
331 |
-
|
332 |
-
|
333 |
|
334 |
|
335 |
async def pipeline_index_texts(rag: LightRAG, texts: List[str]):
|
@@ -373,16 +373,16 @@ async def run_scanning_process(rag: LightRAG, doc_manager: DocumentManager):
|
|
373 |
try:
|
374 |
new_files = doc_manager.scan_directory_for_new_files()
|
375 |
total_files = len(new_files)
|
376 |
-
|
377 |
|
378 |
for idx, file_path in enumerate(new_files):
|
379 |
try:
|
380 |
await pipeline_index_file(rag, file_path)
|
381 |
except Exception as e:
|
382 |
-
|
383 |
|
384 |
except Exception as e:
|
385 |
-
|
386 |
|
387 |
|
388 |
def create_document_routes(
|
@@ -447,8 +447,8 @@ def create_document_routes(
|
|
447 |
message=f"File '{file.filename}' uploaded successfully. Processing will continue in background.",
|
448 |
)
|
449 |
except Exception as e:
|
450 |
-
|
451 |
-
|
452 |
raise HTTPException(status_code=500, detail=str(e))
|
453 |
|
454 |
@router.post(
|
@@ -480,8 +480,8 @@ def create_document_routes(
|
|
480 |
message="Text successfully received. Processing will continue in background.",
|
481 |
)
|
482 |
except Exception as e:
|
483 |
-
|
484 |
-
|
485 |
raise HTTPException(status_code=500, detail=str(e))
|
486 |
|
487 |
@router.post(
|
@@ -515,8 +515,8 @@ def create_document_routes(
|
|
515 |
message="Text successfully received. Processing will continue in background.",
|
516 |
)
|
517 |
except Exception as e:
|
518 |
-
|
519 |
-
|
520 |
raise HTTPException(status_code=500, detail=str(e))
|
521 |
|
522 |
@router.post(
|
@@ -558,8 +558,8 @@ def create_document_routes(
|
|
558 |
message=f"File '{file.filename}' saved successfully. Processing will continue in background.",
|
559 |
)
|
560 |
except Exception as e:
|
561 |
-
|
562 |
-
|
563 |
raise HTTPException(status_code=500, detail=str(e))
|
564 |
|
565 |
@router.post(
|
@@ -621,8 +621,8 @@ def create_document_routes(
|
|
621 |
|
622 |
return InsertResponse(status=status, message=status_message)
|
623 |
except Exception as e:
|
624 |
-
|
625 |
-
|
626 |
raise HTTPException(status_code=500, detail=str(e))
|
627 |
|
628 |
@router.delete(
|
@@ -649,8 +649,8 @@ def create_document_routes(
|
|
649 |
status="success", message="All documents cleared successfully"
|
650 |
)
|
651 |
except Exception as e:
|
652 |
-
|
653 |
-
|
654 |
raise HTTPException(status_code=500, detail=str(e))
|
655 |
|
656 |
@router.get("/pipeline_status", dependencies=[Depends(optional_api_key)])
|
@@ -682,8 +682,8 @@ def create_document_routes(
|
|
682 |
|
683 |
return status_dict
|
684 |
except Exception as e:
|
685 |
-
|
686 |
-
|
687 |
raise HTTPException(status_code=500, detail=str(e))
|
688 |
|
689 |
@router.get("", dependencies=[Depends(optional_api_key)])
|
@@ -739,8 +739,8 @@ def create_document_routes(
|
|
739 |
)
|
740 |
return response
|
741 |
except Exception as e:
|
742 |
-
|
743 |
-
|
744 |
raise HTTPException(status_code=500, detail=str(e))
|
745 |
|
746 |
return router
|
|
|
3 |
"""
|
4 |
|
5 |
import asyncio
|
6 |
+
from lightrag.utils import logger
|
7 |
import aiofiles
|
8 |
import shutil
|
9 |
import traceback
|
|
|
147 |
"""Scan input directory for new files"""
|
148 |
new_files = []
|
149 |
for ext in self.supported_extensions:
|
150 |
+
logger.debug(f"Scanning for {ext} files in {self.input_dir}")
|
151 |
for file_path in self.input_dir.rglob(f"*{ext}"):
|
152 |
if file_path not in self.indexed_files:
|
153 |
new_files.append(file_path)
|
|
|
266 |
)
|
267 |
content += "\n"
|
268 |
case _:
|
269 |
+
logger.error(
|
270 |
f"Unsupported file type: {file_path.name} (extension {ext})"
|
271 |
)
|
272 |
return False
|
|
|
274 |
# Insert into the RAG queue
|
275 |
if content:
|
276 |
await rag.apipeline_enqueue_documents(content)
|
277 |
+
logger.info(f"Successfully fetched and enqueued file: {file_path.name}")
|
278 |
return True
|
279 |
else:
|
280 |
+
logger.error(f"No content could be extracted from file: {file_path.name}")
|
281 |
|
282 |
except Exception as e:
|
283 |
+
logger.error(f"Error processing or enqueueing file {file_path.name}: {str(e)}")
|
284 |
+
logger.error(traceback.format_exc())
|
285 |
finally:
|
286 |
if file_path.name.startswith(temp_prefix):
|
287 |
try:
|
288 |
file_path.unlink()
|
289 |
except Exception as e:
|
290 |
+
logger.error(f"Error deleting file {file_path}: {str(e)}")
|
291 |
return False
|
292 |
|
293 |
|
|
|
303 |
await rag.apipeline_process_enqueue_documents()
|
304 |
|
305 |
except Exception as e:
|
306 |
+
logger.error(f"Error indexing file {file_path.name}: {str(e)}")
|
307 |
+
logger.error(traceback.format_exc())
|
308 |
|
309 |
|
310 |
async def pipeline_index_files(rag: LightRAG, file_paths: List[Path]):
|
|
|
328 |
if enqueued:
|
329 |
await rag.apipeline_process_enqueue_documents()
|
330 |
except Exception as e:
|
331 |
+
logger.error(f"Error indexing files: {str(e)}")
|
332 |
+
logger.error(traceback.format_exc())
|
333 |
|
334 |
|
335 |
async def pipeline_index_texts(rag: LightRAG, texts: List[str]):
|
|
|
373 |
try:
|
374 |
new_files = doc_manager.scan_directory_for_new_files()
|
375 |
total_files = len(new_files)
|
376 |
+
logger.info(f"Found {total_files} new files to index.")
|
377 |
|
378 |
for idx, file_path in enumerate(new_files):
|
379 |
try:
|
380 |
await pipeline_index_file(rag, file_path)
|
381 |
except Exception as e:
|
382 |
+
logger.error(f"Error indexing file {file_path}: {str(e)}")
|
383 |
|
384 |
except Exception as e:
|
385 |
+
logger.error(f"Error during scanning process: {str(e)}")
|
386 |
|
387 |
|
388 |
def create_document_routes(
|
|
|
447 |
message=f"File '{file.filename}' uploaded successfully. Processing will continue in background.",
|
448 |
)
|
449 |
except Exception as e:
|
450 |
+
logger.error(f"Error /documents/upload: {file.filename}: {str(e)}")
|
451 |
+
logger.error(traceback.format_exc())
|
452 |
raise HTTPException(status_code=500, detail=str(e))
|
453 |
|
454 |
@router.post(
|
|
|
480 |
message="Text successfully received. Processing will continue in background.",
|
481 |
)
|
482 |
except Exception as e:
|
483 |
+
logger.error(f"Error /documents/text: {str(e)}")
|
484 |
+
logger.error(traceback.format_exc())
|
485 |
raise HTTPException(status_code=500, detail=str(e))
|
486 |
|
487 |
@router.post(
|
|
|
515 |
message="Text successfully received. Processing will continue in background.",
|
516 |
)
|
517 |
except Exception as e:
|
518 |
+
logger.error(f"Error /documents/text: {str(e)}")
|
519 |
+
logger.error(traceback.format_exc())
|
520 |
raise HTTPException(status_code=500, detail=str(e))
|
521 |
|
522 |
@router.post(
|
|
|
558 |
message=f"File '{file.filename}' saved successfully. Processing will continue in background.",
|
559 |
)
|
560 |
except Exception as e:
|
561 |
+
logger.error(f"Error /documents/file: {str(e)}")
|
562 |
+
logger.error(traceback.format_exc())
|
563 |
raise HTTPException(status_code=500, detail=str(e))
|
564 |
|
565 |
@router.post(
|
|
|
621 |
|
622 |
return InsertResponse(status=status, message=status_message)
|
623 |
except Exception as e:
|
624 |
+
logger.error(f"Error /documents/batch: {str(e)}")
|
625 |
+
logger.error(traceback.format_exc())
|
626 |
raise HTTPException(status_code=500, detail=str(e))
|
627 |
|
628 |
@router.delete(
|
|
|
649 |
status="success", message="All documents cleared successfully"
|
650 |
)
|
651 |
except Exception as e:
|
652 |
+
logger.error(f"Error DELETE /documents: {str(e)}")
|
653 |
+
logger.error(traceback.format_exc())
|
654 |
raise HTTPException(status_code=500, detail=str(e))
|
655 |
|
656 |
@router.get("/pipeline_status", dependencies=[Depends(optional_api_key)])
|
|
|
682 |
|
683 |
return status_dict
|
684 |
except Exception as e:
|
685 |
+
logger.error(f"Error getting pipeline status: {str(e)}")
|
686 |
+
logger.error(traceback.format_exc())
|
687 |
raise HTTPException(status_code=500, detail=str(e))
|
688 |
|
689 |
@router.get("", dependencies=[Depends(optional_api_key)])
|
|
|
739 |
)
|
740 |
return response
|
741 |
except Exception as e:
|
742 |
+
logger.error(f"Error GET /documents: {str(e)}")
|
743 |
+
logger.error(traceback.format_exc())
|
744 |
raise HTTPException(status_code=500, detail=str(e))
|
745 |
|
746 |
return router
|
lightrag/utils.py
CHANGED
@@ -75,18 +75,51 @@ def set_logger(log_file: str, level: int = logging.DEBUG):
|
|
75 |
log_file: Path to the log file
|
76 |
level: Logging level (e.g. logging.DEBUG, logging.INFO)
|
77 |
"""
|
|
|
78 |
logger.setLevel(level)
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
83 |
formatter = logging.Formatter(
|
84 |
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
85 |
)
|
86 |
-
|
87 |
-
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
logger.addHandler(file_handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
|
92 |
class UnlimitedSemaphore:
|
|
|
75 |
log_file: Path to the log file
|
76 |
level: Logging level (e.g. logging.DEBUG, logging.INFO)
|
77 |
"""
|
78 |
+
# 设置日志级别
|
79 |
logger.setLevel(level)
|
80 |
+
|
81 |
+
# 确保使用绝对路径
|
82 |
+
log_file = os.path.abspath(log_file)
|
83 |
+
|
84 |
+
# 创建格式化器
|
85 |
formatter = logging.Formatter(
|
86 |
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
87 |
)
|
88 |
+
|
89 |
+
# 检查是否已经有文件处理器
|
90 |
+
has_file_handler = False
|
91 |
+
has_console_handler = False
|
92 |
+
|
93 |
+
# 检查现有处理器
|
94 |
+
for handler in logger.handlers:
|
95 |
+
if isinstance(handler, logging.FileHandler):
|
96 |
+
has_file_handler = True
|
97 |
+
elif isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.FileHandler):
|
98 |
+
has_console_handler = True
|
99 |
+
|
100 |
+
# 如果没有文件处理器,添加一个
|
101 |
+
if not has_file_handler:
|
102 |
+
# 使用 RotatingFileHandler 代替 FileHandler
|
103 |
+
from logging.handlers import RotatingFileHandler
|
104 |
+
file_handler = RotatingFileHandler(
|
105 |
+
log_file,
|
106 |
+
maxBytes=10*1024*1024, # 10MB
|
107 |
+
backupCount=5,
|
108 |
+
encoding="utf-8"
|
109 |
+
)
|
110 |
+
file_handler.setLevel(level)
|
111 |
+
file_handler.setFormatter(formatter)
|
112 |
logger.addHandler(file_handler)
|
113 |
+
|
114 |
+
# 如果没有控制台处理器,添加一个
|
115 |
+
if not has_console_handler:
|
116 |
+
console_handler = logging.StreamHandler()
|
117 |
+
console_handler.setLevel(level)
|
118 |
+
console_handler.setFormatter(formatter)
|
119 |
+
logger.addHandler(console_handler)
|
120 |
+
|
121 |
+
# 设置日志传播为 False,避免重复输出
|
122 |
+
logger.propagate = False
|
123 |
|
124 |
|
125 |
class UnlimitedSemaphore:
|
run_with_gunicorn.py
CHANGED
@@ -157,6 +157,10 @@ def main():
|
|
157 |
value = getattr(self.config_module, key)
|
158 |
if callable(value):
|
159 |
self.cfg.set(key, value)
|
|
|
|
|
|
|
|
|
160 |
|
161 |
# Override with command line arguments if provided
|
162 |
if gunicorn_args.workers:
|
|
|
157 |
value = getattr(self.config_module, key)
|
158 |
if callable(value):
|
159 |
self.cfg.set(key, value)
|
160 |
+
|
161 |
+
# 确保正确加载 logconfig_dict
|
162 |
+
if hasattr(self.config_module, 'logconfig_dict'):
|
163 |
+
self.cfg.set('logconfig_dict', getattr(self.config_module, 'logconfig_dict'))
|
164 |
|
165 |
# Override with command line arguments if provided
|
166 |
if gunicorn_args.workers:
|