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 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", "-") # '-' means stderr
32
- accesslog = os.getenv("ACCESS_LOG", "-") # '-' means stderr
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
- "default": {
459
  "formatter": "default",
460
  "class": "logging.StreamHandler",
461
  "stream": "ext://sys.stderr",
462
  },
 
 
 
 
 
 
 
 
463
  },
464
  "loggers": {
 
 
 
 
 
 
465
  "uvicorn.access": {
466
- "handlers": ["default"],
467
  "level": "INFO",
468
  "propagate": False,
469
  "filters": ["path_filter"],
470
  },
 
 
 
 
 
471
  "lightrag": {
472
- "handlers": ["default"],
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 logging
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
- logging.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,7 +266,7 @@ async def pipeline_enqueue_file(rag: LightRAG, file_path: Path) -> bool:
266
  )
267
  content += "\n"
268
  case _:
269
- logging.error(
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
- logging.info(f"Successfully fetched and enqueued file: {file_path.name}")
278
  return True
279
  else:
280
- logging.error(f"No content could be extracted from file: {file_path.name}")
281
 
282
  except Exception as e:
283
- logging.error(f"Error processing or enqueueing file {file_path.name}: {str(e)}")
284
- logging.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
- logging.error(f"Error deleting file {file_path}: {str(e)}")
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
- logging.error(f"Error indexing file {file_path.name}: {str(e)}")
307
- logging.error(traceback.format_exc())
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
- logging.error(f"Error indexing files: {str(e)}")
332
- logging.error(traceback.format_exc())
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
- logging.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
- logging.error(f"Error indexing file {file_path}: {str(e)}")
383
 
384
  except Exception as e:
385
- logging.error(f"Error during scanning process: {str(e)}")
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
- logging.error(f"Error /documents/upload: {file.filename}: {str(e)}")
451
- logging.error(traceback.format_exc())
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
- logging.error(f"Error /documents/text: {str(e)}")
484
- logging.error(traceback.format_exc())
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
- logging.error(f"Error /documents/text: {str(e)}")
519
- logging.error(traceback.format_exc())
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
- logging.error(f"Error /documents/file: {str(e)}")
562
- logging.error(traceback.format_exc())
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
- logging.error(f"Error /documents/batch: {str(e)}")
625
- logging.error(traceback.format_exc())
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
- logging.error(f"Error DELETE /documents: {str(e)}")
653
- logging.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,8 +682,8 @@ def create_document_routes(
682
 
683
  return status_dict
684
  except Exception as e:
685
- logging.error(f"Error getting pipeline status: {str(e)}")
686
- logging.error(traceback.format_exc())
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
- logging.error(f"Error GET /documents: {str(e)}")
743
- logging.error(traceback.format_exc())
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
- file_handler = logging.FileHandler(log_file, encoding="utf-8")
81
- file_handler.setLevel(level)
82
-
 
83
  formatter = logging.Formatter(
84
  "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
85
  )
86
- file_handler.setFormatter(formatter)
87
-
88
- if not logger.handlers:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: