yangdx commited on
Commit
103da4d
·
1 Parent(s): c0beaac

Align Gunicorn configuration with Uvicorn

Browse files

- centralize config in gunicorn_config.py
- fix log level handling in Gunicorn

gunicorn_config.py CHANGED
@@ -2,17 +2,17 @@
2
  import os
3
  import logging
4
  from lightrag.kg.shared_storage import finalize_share_data
5
- from lightrag.api.utils_api import parse_args
6
  from lightrag.api.lightrag_server import LightragPathFilter
7
 
8
- # Parse command line arguments
9
- args = parse_args()
10
-
11
- # Determine worker count - from environment variable or command line arguments
12
- workers = int(os.getenv("WORKERS", args.workers))
13
 
14
- # Binding address
15
- bind = f"{os.getenv('HOST', args.host)}:{os.getenv('PORT', args.port)}"
 
 
 
 
16
 
17
  # Enable preload_app option
18
  preload_app = True
@@ -24,18 +24,9 @@ worker_class = "uvicorn.workers.UvicornWorker"
24
  timeout = int(os.getenv("TIMEOUT", 120))
25
  keepalive = 5
26
 
27
- # Optional SSL configuration
28
- if args.ssl:
29
- certfile = args.ssl_certfile
30
- keyfile = args.ssl_keyfile
31
-
32
- # 获取日志文件路径
33
- log_file_path = os.path.abspath(os.path.join(os.getcwd(), "lightrag.log"))
34
-
35
  # Logging configuration
36
  errorlog = os.getenv("ERROR_LOG", log_file_path) # 默认写入到 lightrag.log
37
  accesslog = os.getenv("ACCESS_LOG", log_file_path) # 默认写入到 lightrag.log
38
- loglevel = os.getenv("LOG_LEVEL", "info")
39
 
40
  # 配置日志系统
41
  logconfig_dict = {
@@ -49,13 +40,11 @@ logconfig_dict = {
49
  'handlers': {
50
  'console': {
51
  'class': 'logging.StreamHandler',
52
- 'level': 'INFO',
53
  'formatter': 'standard',
54
  'stream': 'ext://sys.stdout'
55
  },
56
  'file': {
57
  'class': 'logging.handlers.RotatingFileHandler',
58
- 'level': 'INFO',
59
  'formatter': 'standard',
60
  'filename': log_file_path,
61
  'maxBytes': 10485760, # 10MB
@@ -71,22 +60,22 @@ logconfig_dict = {
71
  'loggers': {
72
  'lightrag': {
73
  'handlers': ['console', 'file'],
74
- 'level': 'INFO',
75
  'propagate': False
76
  },
77
  'gunicorn': {
78
  'handlers': ['console', 'file'],
79
- 'level': 'INFO',
80
  'propagate': False
81
  },
82
  'gunicorn.error': {
83
  'handlers': ['console', 'file'],
84
- 'level': 'INFO',
85
  'propagate': False
86
  },
87
  'gunicorn.access': {
88
  'handlers': ['console', 'file'],
89
- 'level': 'INFO',
90
  'propagate': False,
91
  'filters': ['path_filter']
92
  }
@@ -143,6 +132,10 @@ def post_fork(server, worker):
143
  Executed after a worker has been forked.
144
  This is a good place to set up worker-specific configurations.
145
  """
 
 
 
 
146
  # Disable uvicorn.error logger in worker processes
147
  uvicorn_error_logger = logging.getLogger("uvicorn.error")
148
  uvicorn_error_logger.setLevel(logging.CRITICAL)
 
2
  import os
3
  import logging
4
  from lightrag.kg.shared_storage import finalize_share_data
 
5
  from lightrag.api.lightrag_server import LightragPathFilter
6
 
7
+ # 获取日志文件路径
8
+ log_file_path = os.path.abspath(os.path.join(os.getcwd(), "lightrag.log"))
 
 
 
9
 
10
+ # These variables will be set by run_with_gunicorn.py
11
+ workers = None
12
+ bind = None
13
+ loglevel = None
14
+ certfile = None
15
+ keyfile = None
16
 
17
  # Enable preload_app option
18
  preload_app = True
 
24
  timeout = int(os.getenv("TIMEOUT", 120))
25
  keepalive = 5
26
 
 
 
 
 
 
 
 
 
27
  # Logging configuration
28
  errorlog = os.getenv("ERROR_LOG", log_file_path) # 默认写入到 lightrag.log
29
  accesslog = os.getenv("ACCESS_LOG", log_file_path) # 默认写入到 lightrag.log
 
30
 
31
  # 配置日志系统
32
  logconfig_dict = {
 
40
  'handlers': {
41
  'console': {
42
  'class': 'logging.StreamHandler',
 
43
  'formatter': 'standard',
44
  'stream': 'ext://sys.stdout'
45
  },
46
  'file': {
47
  'class': 'logging.handlers.RotatingFileHandler',
 
48
  'formatter': 'standard',
49
  'filename': log_file_path,
50
  'maxBytes': 10485760, # 10MB
 
60
  'loggers': {
61
  'lightrag': {
62
  'handlers': ['console', 'file'],
63
+ 'level': loglevel.upper() if loglevel else 'INFO',
64
  'propagate': False
65
  },
66
  'gunicorn': {
67
  'handlers': ['console', 'file'],
68
+ 'level': loglevel.upper() if loglevel else 'INFO',
69
  'propagate': False
70
  },
71
  'gunicorn.error': {
72
  'handlers': ['console', 'file'],
73
+ 'level': loglevel.upper() if loglevel else 'INFO',
74
  'propagate': False
75
  },
76
  'gunicorn.access': {
77
  'handlers': ['console', 'file'],
78
+ 'level': loglevel.upper() if loglevel else 'INFO',
79
  'propagate': False,
80
  'filters': ['path_filter']
81
  }
 
132
  Executed after a worker has been forked.
133
  This is a good place to set up worker-specific configurations.
134
  """
135
+ # Set lightrag logger level in worker processes using gunicorn's loglevel
136
+ from lightrag.utils import logger
137
+ logger.setLevel(loglevel.upper())
138
+
139
  # Disable uvicorn.error logger in worker processes
140
  uvicorn_error_logger = logging.getLogger("uvicorn.error")
141
  uvicorn_error_logger.setLevel(logging.CRITICAL)
lightrag/api/lightrag_server.py CHANGED
@@ -86,7 +86,7 @@ class LightragPathFilter(logging.Filter):
86
 
87
  def create_app(args):
88
  # Setup logging
89
- logger.setLevel(getattr(logging, args.log_level))
90
  set_verbose_debug(args.verbose)
91
 
92
  # Verify that bindings are correctly setup
@@ -412,17 +412,10 @@ def create_app(args):
412
  return app
413
 
414
 
415
- def get_application():
416
  """Factory function for creating the FastAPI application"""
417
- # Get args from environment variable
418
- args_json = os.environ.get("LIGHTRAG_ARGS")
419
- if not args_json:
420
- args = parse_args() # Fallback to parsing args if env var not set
421
- else:
422
- import types
423
-
424
- args = types.SimpleNamespace(**json.loads(args_json))
425
-
426
  return create_app(args)
427
 
428
 
@@ -513,10 +506,7 @@ def main():
513
  # Configure logging before parsing args
514
  configure_logging()
515
 
516
- args = parse_args()
517
- # Save args to environment variable for child processes
518
- os.environ["LIGHTRAG_ARGS"] = json.dumps(vars(args))
519
-
520
  display_splash_screen(args)
521
 
522
  # Create application instance directly instead of using factory function
 
86
 
87
  def create_app(args):
88
  # Setup logging
89
+ logger.setLevel(args.log_level)
90
  set_verbose_debug(args.verbose)
91
 
92
  # Verify that bindings are correctly setup
 
412
  return app
413
 
414
 
415
+ def get_application(args=None):
416
  """Factory function for creating the FastAPI application"""
417
+ if args is None:
418
+ args = parse_args()
 
 
 
 
 
 
 
419
  return create_app(args)
420
 
421
 
 
506
  # Configure logging before parsing args
507
  configure_logging()
508
 
509
+ args = parse_args(is_uvicorn_mode=True)
 
 
 
510
  display_splash_screen(args)
511
 
512
  # Create application instance directly instead of using factory function
lightrag/api/utils_api.py CHANGED
@@ -111,10 +111,13 @@ def get_env_value(env_key: str, default: any, value_type: type = str) -> any:
111
  return default
112
 
113
 
114
- def parse_args() -> argparse.Namespace:
115
  """
116
  Parse command line arguments with environment variable fallback
117
 
 
 
 
118
  Returns:
119
  argparse.Namespace: Parsed arguments
120
  """
@@ -287,9 +290,6 @@ def parse_args() -> argparse.Namespace:
287
 
288
  args = parser.parse_args()
289
 
290
- # Check if running under uvicorn mode (not Gunicorn)
291
- is_uvicorn_mode = "GUNICORN_CMD_ARGS" not in os.environ
292
-
293
  # If in uvicorn mode and workers > 1, force it to 1 and log warning
294
  if is_uvicorn_mode and args.workers > 1:
295
  original_workers = args.workers
 
111
  return default
112
 
113
 
114
+ def parse_args(is_uvicorn_mode: bool = False) -> argparse.Namespace:
115
  """
116
  Parse command line arguments with environment variable fallback
117
 
118
+ Args:
119
+ is_uvicorn_mode: Whether running under uvicorn mode
120
+
121
  Returns:
122
  argparse.Namespace: Parsed arguments
123
  """
 
290
 
291
  args = parser.parse_args()
292
 
 
 
 
293
  # If in uvicorn mode and workers > 1, force it to 1 and log warning
294
  if is_uvicorn_mode and args.workers > 1:
295
  original_workers = args.workers
run_with_gunicorn.py CHANGED
@@ -30,45 +30,10 @@ def main():
30
  # Register signal handlers for graceful shutdown
31
  signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
32
  signal.signal(signal.SIGTERM, signal_handler) # kill command
33
- # Create a parser to handle Gunicorn-specific parameters
34
- parser = argparse.ArgumentParser(description="Start LightRAG server with Gunicorn")
35
- parser.add_argument(
36
- "--workers",
37
- type=int,
38
- help="Number of worker processes (overrides the default or config.ini setting)",
39
- )
40
- parser.add_argument(
41
- "--timeout", type=int, help="Worker timeout in seconds (default: 120)"
42
- )
43
- parser.add_argument(
44
- "--log-level",
45
- choices=["debug", "info", "warning", "error", "critical"],
46
- help="Gunicorn log level",
47
- )
48
-
49
- # Parse Gunicorn-specific arguments
50
- gunicorn_args, remaining_args = parser.parse_known_args()
51
-
52
- # Pass remaining arguments to LightRAG's parse_args
53
- sys.argv = [sys.argv[0]] + remaining_args
54
- args = parse_args()
55
-
56
- # If workers specified, override args value
57
- if gunicorn_args.workers:
58
- args.workers = gunicorn_args.workers
59
- os.environ["WORKERS"] = str(gunicorn_args.workers)
60
-
61
- # If timeout specified, set environment variable
62
- if gunicorn_args.timeout:
63
- os.environ["TIMEOUT"] = str(gunicorn_args.timeout)
64
-
65
- # If log-level specified, set environment variable
66
- if gunicorn_args.log_level:
67
- os.environ["LOG_LEVEL"] = gunicorn_args.log_level
68
-
69
- # Save all LightRAG args to environment variable for worker processes
70
- # This is the key step for passing arguments to lightrag_server.py
71
- os.environ["LIGHTRAG_ARGS"] = json.dumps(vars(args))
72
 
73
  # Display startup information
74
  display_splash_screen(args)
@@ -83,11 +48,6 @@ def main():
83
  print(f"Workers setting: {args.workers}")
84
  print("=" * 80 + "\n")
85
 
86
- # Start application with Gunicorn using direct Python API
87
- # Ensure WORKERS environment variable is set before importing gunicorn_config
88
- if args.workers > 1:
89
- os.environ["WORKERS"] = str(args.workers)
90
-
91
  # Import Gunicorn's StandaloneApplication
92
  from gunicorn.app.base import BaseApplication
93
 
@@ -136,51 +96,45 @@ def main():
136
  "child_exit",
137
  }
138
 
139
- # Import the gunicorn_config module directly
140
- import importlib.util
141
 
142
- spec = importlib.util.spec_from_file_location(
143
- "gunicorn_config", "gunicorn_config.py"
144
- )
145
- self.config_module = importlib.util.module_from_spec(spec)
146
- spec.loader.exec_module(self.config_module)
 
 
 
 
147
 
148
- # Set configuration options
149
- for key in dir(self.config_module):
150
  if key in valid_options:
151
- value = getattr(self.config_module, key)
152
- # Skip functions like on_starting
153
- if not callable(value):
154
  self.cfg.set(key, value)
155
  # Set special hooks
156
  elif key in special_hooks:
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:
167
- self.cfg.set("workers", gunicorn_args.workers)
168
- if gunicorn_args.timeout:
169
- self.cfg.set("timeout", gunicorn_args.timeout)
170
- if gunicorn_args.log_level:
171
- self.cfg.set("loglevel", gunicorn_args.log_level)
172
 
173
  def load(self):
174
  # Import the application
175
  from lightrag.api.lightrag_server import get_application
176
 
177
- return get_application()
178
 
179
  # Create the application
180
  app = GunicornApp("")
181
 
182
- # Directly call initialize_share_data with the correct workers value
183
-
184
  # Force workers to be an integer and greater than 1 for multi-process mode
185
  workers_count = int(args.workers)
186
  if workers_count > 1:
 
30
  # Register signal handlers for graceful shutdown
31
  signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
32
  signal.signal(signal.SIGTERM, signal_handler) # kill command
33
+
34
+ # Parse all arguments using parse_args
35
+ args = parse_args(is_uvicorn_mode=False)
36
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  # Display startup information
39
  display_splash_screen(args)
 
48
  print(f"Workers setting: {args.workers}")
49
  print("=" * 80 + "\n")
50
 
 
 
 
 
 
51
  # Import Gunicorn's StandaloneApplication
52
  from gunicorn.app.base import BaseApplication
53
 
 
96
  "child_exit",
97
  }
98
 
99
+ # Import and configure the gunicorn_config module
100
+ import gunicorn_config
101
 
102
+ # Set configuration variables in gunicorn_config
103
+ gunicorn_config.workers = int(os.getenv("WORKERS", args.workers))
104
+ gunicorn_config.bind = f"{os.getenv('HOST', args.host)}:{os.getenv('PORT', args.port)}"
105
+ gunicorn_config.loglevel = args.log_level.lower() if args.log_level else os.getenv("LOG_LEVEL", "info")
106
+
107
+ # Set SSL configuration if enabled
108
+ if args.ssl:
109
+ gunicorn_config.certfile = args.ssl_certfile
110
+ gunicorn_config.keyfile = args.ssl_keyfile
111
 
112
+ # Set configuration options from the module
113
+ for key in dir(gunicorn_config):
114
  if key in valid_options:
115
+ value = getattr(gunicorn_config, key)
116
+ # Skip functions like on_starting and None values
117
+ if not callable(value) and value is not None:
118
  self.cfg.set(key, value)
119
  # Set special hooks
120
  elif key in special_hooks:
121
+ value = getattr(gunicorn_config, key)
122
  if callable(value):
123
  self.cfg.set(key, value)
124
 
125
  # 确保正确加载 logconfig_dict
126
+ if hasattr(gunicorn_config, 'logconfig_dict'):
127
+ self.cfg.set('logconfig_dict', getattr(gunicorn_config, 'logconfig_dict'))
 
 
 
 
 
 
 
 
128
 
129
  def load(self):
130
  # Import the application
131
  from lightrag.api.lightrag_server import get_application
132
 
133
+ return get_application(args)
134
 
135
  # Create the application
136
  app = GunicornApp("")
137
 
 
 
138
  # Force workers to be an integer and greater than 1 for multi-process mode
139
  workers_count = int(args.workers)
140
  if workers_count > 1: