yangdx commited on
Commit
a2c0b5a
·
1 Parent(s): 5d78930

fix: log filtering void when uvicorn wokers is greater than 1

Browse files

- Centralize logging setup
- Fix logger propagation issues

Files changed (2) hide show
  1. lightrag/api/lightrag_server.py +55 -31
  2. lightrag/utils.py +16 -13
lightrag/api/lightrag_server.py CHANGED
@@ -30,7 +30,6 @@ from lightrag import LightRAG
30
  from lightrag.types import GPTKeywordExtractionFormat
31
  from lightrag.api import __api_version__
32
  from lightrag.utils import EmbeddingFunc
33
- from lightrag.utils import logger
34
  from .routers.document_routes import (
35
  DocumentManager,
36
  create_document_routes,
@@ -40,36 +39,40 @@ from .routers.query_routes import create_query_routes
40
  from .routers.graph_routes import create_graph_routes
41
  from .routers.ollama_api import OllamaAPI
42
 
 
 
43
  # Load environment variables
44
  try:
45
  load_dotenv(override=True)
46
  except Exception as e:
47
- logger.warning(f"Failed to load .env file: {e}")
48
 
49
  # Initialize config parser
50
  config = configparser.ConfigParser()
51
  config.read("config.ini")
52
 
53
 
54
- class AccessLogFilter(logging.Filter):
 
55
  def __init__(self):
56
  super().__init__()
57
  # Define paths to be filtered
58
  self.filtered_paths = ["/documents", "/health", "/webui/"]
59
-
60
  def filter(self, record):
61
  try:
 
62
  if not hasattr(record, "args") or not isinstance(record.args, tuple):
63
  return True
64
  if len(record.args) < 5:
65
  return True
66
 
 
67
  method = record.args[1]
68
  path = record.args[2]
69
  status = record.args[4]
70
- # print(f"Debug - Method: {method}, Path: {path}, Status: {status}")
71
- # print(f"Debug - Filtered paths: {self.filtered_paths}")
72
 
 
73
  if (
74
  method == "GET"
75
  and (status == 200 or status == 304)
@@ -78,17 +81,23 @@ class AccessLogFilter(logging.Filter):
78
  return False
79
 
80
  return True
81
-
82
  except Exception:
 
83
  return True
84
 
85
 
86
  def create_app(args):
87
  # Initialize verbose debug setting
88
- from lightrag.utils import set_verbose_debug
89
-
 
 
90
  set_verbose_debug(args.verbose)
91
 
 
 
 
 
92
  # Verify that bindings are correctly setup
93
  if args.llm_binding not in [
94
  "lollms",
@@ -120,11 +129,6 @@ def create_app(args):
120
  if not os.path.exists(args.ssl_keyfile):
121
  raise Exception(f"SSL key file not found: {args.ssl_keyfile}")
122
 
123
- # Setup logging
124
- logging.basicConfig(
125
- format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
126
- )
127
-
128
  # Check if API key is provided either through env var or args
129
  api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
130
 
@@ -406,6 +410,9 @@ def create_app(args):
406
 
407
  def get_application():
408
  """Factory function for creating the FastAPI application"""
 
 
 
409
  # Get args from environment variable
410
  args_json = os.environ.get('LIGHTRAG_ARGS')
411
  if not args_json:
@@ -414,24 +421,22 @@ def get_application():
414
  import types
415
  args = types.SimpleNamespace(**json.loads(args_json))
416
 
417
- return create_app(args)
 
 
418
 
 
419
 
420
- def main():
421
- from multiprocessing import freeze_support
422
- freeze_support()
423
-
424
- args = parse_args()
425
- # Save args to environment variable for child processes
426
- os.environ['LIGHTRAG_ARGS'] = json.dumps(vars(args))
427
 
428
- if args.workers > 1:
429
- from lightrag.kg.shared_storage import initialize_manager
430
- initialize_manager()
431
- import lightrag.kg.shared_storage as shared_storage
432
- shared_storage.is_multiprocess = True
 
 
433
 
434
- # Configure uvicorn logging
435
  logging.config.dictConfig({
436
  "version": 1,
437
  "disable_existing_loggers": False,
@@ -452,13 +457,32 @@ def main():
452
  "handlers": ["default"],
453
  "level": "INFO",
454
  "propagate": False,
 
 
 
 
 
 
 
 
 
 
 
 
455
  },
456
  },
457
  })
458
 
459
- # Add filter to uvicorn access logger
460
- uvicorn_access_logger = logging.getLogger("uvicorn.access")
461
- uvicorn_access_logger.addFilter(AccessLogFilter())
 
 
 
 
 
 
 
462
 
463
  display_splash_screen(args)
464
 
 
30
  from lightrag.types import GPTKeywordExtractionFormat
31
  from lightrag.api import __api_version__
32
  from lightrag.utils import EmbeddingFunc
 
33
  from .routers.document_routes import (
34
  DocumentManager,
35
  create_document_routes,
 
39
  from .routers.graph_routes import create_graph_routes
40
  from .routers.ollama_api import OllamaAPI
41
 
42
+ from lightrag.utils import logger as utils_logger
43
+
44
  # Load environment variables
45
  try:
46
  load_dotenv(override=True)
47
  except Exception as e:
48
+ utils_logger.warning(f"Failed to load .env file: {e}")
49
 
50
  # Initialize config parser
51
  config = configparser.ConfigParser()
52
  config.read("config.ini")
53
 
54
 
55
+ class LightragPathFilter(logging.Filter):
56
+ """Filter for lightrag logger to filter out frequent path access logs"""
57
  def __init__(self):
58
  super().__init__()
59
  # Define paths to be filtered
60
  self.filtered_paths = ["/documents", "/health", "/webui/"]
61
+
62
  def filter(self, record):
63
  try:
64
+ # Check if record has the required attributes for an access log
65
  if not hasattr(record, "args") or not isinstance(record.args, tuple):
66
  return True
67
  if len(record.args) < 5:
68
  return True
69
 
70
+ # Extract method, path and status from the record args
71
  method = record.args[1]
72
  path = record.args[2]
73
  status = record.args[4]
 
 
74
 
75
+ # Filter out successful GET requests to filtered paths
76
  if (
77
  method == "GET"
78
  and (status == 200 or status == 304)
 
81
  return False
82
 
83
  return True
 
84
  except Exception:
85
+ # In case of any error, let the message through
86
  return True
87
 
88
 
89
  def create_app(args):
90
  # Initialize verbose debug setting
91
+ # Can not use the logger at the top of this module when workers > 1
92
+ from lightrag.utils import set_verbose_debug, logger
93
+ # Setup logging
94
+ logger.setLevel(getattr(logging, args.log_level))
95
  set_verbose_debug(args.verbose)
96
 
97
+ # Display splash screen
98
+ from lightrag.kg.shared_storage import is_multiprocess
99
+ logger.info(f"==== Multi-processor mode: {is_multiprocess} ====")
100
+
101
  # Verify that bindings are correctly setup
102
  if args.llm_binding not in [
103
  "lollms",
 
129
  if not os.path.exists(args.ssl_keyfile):
130
  raise Exception(f"SSL key file not found: {args.ssl_keyfile}")
131
 
 
 
 
 
 
132
  # Check if API key is provided either through env var or args
133
  api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
134
 
 
410
 
411
  def get_application():
412
  """Factory function for creating the FastAPI application"""
413
+ # Configure logging for this worker process
414
+ configure_logging()
415
+
416
  # Get args from environment variable
417
  args_json = os.environ.get('LIGHTRAG_ARGS')
418
  if not args_json:
 
421
  import types
422
  args = types.SimpleNamespace(**json.loads(args_json))
423
 
424
+ # if args.workers > 1:
425
+ # from lightrag.kg.shared_storage import initialize_manager
426
+ # initialize_manager()
427
 
428
+ return create_app(args)
429
 
 
 
 
 
 
 
 
430
 
431
+ def configure_logging():
432
+ """Configure logging for both uvicorn and lightrag"""
433
+ # Reset any existing handlers to ensure clean configuration
434
+ for logger_name in ["uvicorn.access", "lightrag"]:
435
+ logger = logging.getLogger(logger_name)
436
+ logger.handlers = []
437
+ logger.filters = []
438
 
439
+ # Configure basic logging
440
  logging.config.dictConfig({
441
  "version": 1,
442
  "disable_existing_loggers": False,
 
457
  "handlers": ["default"],
458
  "level": "INFO",
459
  "propagate": False,
460
+ "filters": ["path_filter"],
461
+ },
462
+ "lightrag": {
463
+ "handlers": ["default"],
464
+ "level": "INFO",
465
+ "propagate": False,
466
+ "filters": ["path_filter"],
467
+ },
468
+ },
469
+ "filters": {
470
+ "path_filter": {
471
+ "()": "lightrag.api.lightrag_server.LightragPathFilter",
472
  },
473
  },
474
  })
475
 
476
+ def main():
477
+ from multiprocessing import freeze_support
478
+ freeze_support()
479
+
480
+ args = parse_args()
481
+ # Save args to environment variable for child processes
482
+ os.environ['LIGHTRAG_ARGS'] = json.dumps(vars(args))
483
+
484
+ # Configure logging before starting uvicorn
485
+ configure_logging()
486
 
487
  display_splash_screen(args)
488
 
lightrag/utils.py CHANGED
@@ -55,22 +55,13 @@ def set_verbose_debug(enabled: bool):
55
  global VERBOSE_DEBUG
56
  VERBOSE_DEBUG = enabled
57
 
58
-
59
- class UnlimitedSemaphore:
60
- """A context manager that allows unlimited access."""
61
-
62
- async def __aenter__(self):
63
- pass
64
-
65
- async def __aexit__(self, exc_type, exc, tb):
66
- pass
67
-
68
-
69
- ENCODER = None
70
-
71
  statistic_data = {"llm_call": 0, "llm_cache": 0, "embed_call": 0}
72
 
 
73
  logger = logging.getLogger("lightrag")
 
 
 
74
 
75
  # Set httpx logging level to WARNING
76
  logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -97,6 +88,18 @@ def set_logger(log_file: str, level: int = logging.DEBUG):
97
  logger.addHandler(file_handler)
98
 
99
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  @dataclass
101
  class EmbeddingFunc:
102
  embedding_dim: int
 
55
  global VERBOSE_DEBUG
56
  VERBOSE_DEBUG = enabled
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  statistic_data = {"llm_call": 0, "llm_cache": 0, "embed_call": 0}
59
 
60
+ # Initialize logger
61
  logger = logging.getLogger("lightrag")
62
+ logger.propagate = False # prevent log message send to root loggger
63
+ # Let the main application configure the handlers
64
+ logger.setLevel(logging.INFO)
65
 
66
  # Set httpx logging level to WARNING
67
  logging.getLogger("httpx").setLevel(logging.WARNING)
 
88
  logger.addHandler(file_handler)
89
 
90
 
91
+ class UnlimitedSemaphore:
92
+ """A context manager that allows unlimited access."""
93
+
94
+ async def __aenter__(self):
95
+ pass
96
+
97
+ async def __aexit__(self, exc_type, exc, tb):
98
+ pass
99
+
100
+
101
+ ENCODER = None
102
+
103
  @dataclass
104
  class EmbeddingFunc:
105
  embedding_dim: int