Skip to content

Commit e5d9840

Browse files
authored
Merge branch 'infiniflow:main' into main
2 parents aafb99c + db5ab7b commit e5d9840

File tree

14 files changed

+298
-57
lines changed

14 files changed

+298
-57
lines changed

admin/server/routes.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from api.common.exceptions import AdminException
3131
from common.versions import get_ragflow_version
3232
from api.utils.api_utils import generate_confirmation_token
33+
from common.log_utils import get_log_levels, set_log_level
3334

3435
admin_bp = Blueprint("admin", __name__, url_prefix="/api/v1/admin")
3536

@@ -652,3 +653,39 @@ def test_sandbox_connection():
652653
return error_response(str(e), 400)
653654
except Exception as e:
654655
return error_response(str(e), 500)
656+
657+
658+
@admin_bp.route("/log_levels", methods=["GET"])
659+
@login_required
660+
@check_admin_auth
661+
def get_logger_levels():
662+
"""Get current log levels for all packages."""
663+
try:
664+
res = get_log_levels()
665+
return success_response(res, "Get log levels", 0)
666+
except Exception as e:
667+
return error_response(str(e), 500)
668+
669+
670+
@admin_bp.route("/log_levels", methods=["PUT"])
671+
@login_required
672+
@check_admin_auth
673+
def set_logger_level():
674+
"""Set log level for a package."""
675+
try:
676+
data = request.get_json()
677+
if not data or "pkg_name" not in data or "level" not in data:
678+
return error_response("pkg_name and level are required", 400)
679+
680+
pkg_name = data["pkg_name"]
681+
level = data["level"]
682+
if not isinstance(pkg_name, str) or not isinstance(level, str):
683+
return error_response("pkg_name and level must be strings", 400)
684+
685+
success = set_log_level(pkg_name, level)
686+
if success:
687+
return success_response({"pkg_name": pkg_name, "level": level}, "Log level updated successfully")
688+
else:
689+
return error_response(f"Invalid log level: {level}", 400)
690+
except Exception as e:
691+
return error_response(str(e), 500)

api/apps/system_app.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
)
3232
from common.versions import get_ragflow_version
3333
from common.time_utils import current_timestamp, datetime_format
34+
from common.log_utils import get_log_levels, set_log_level
3435
from timeit import default_timer as timer
3536

3637
from rag.utils.redis_conn import REDIS_CONN
@@ -375,3 +376,56 @@ def get_config():
375376
"registerEnabled": settings.REGISTER_ENABLED,
376377
"disablePasswordLogin": settings.DISABLE_PASSWORD_LOGIN,
377378
})
379+
380+
381+
@manager.route("/log_levels", methods=["GET"]) # noqa: F821
382+
@login_required
383+
async def get_logger_levels():
384+
"""
385+
Get current log levels for all packages.
386+
---
387+
tags:
388+
- System
389+
responses:
390+
200:
391+
description: Return current log levels
392+
"""
393+
return get_json_result(data=get_log_levels())
394+
395+
396+
@manager.route("/log_levels", methods=["PUT"]) # noqa: F821
397+
@login_required
398+
async def set_logger_level():
399+
"""
400+
Set log level for a package.
401+
---
402+
tags:
403+
- System
404+
parameters:
405+
- in: body
406+
name: body
407+
required: true
408+
schema:
409+
type: object
410+
properties:
411+
pkg_name:
412+
type: string
413+
description: Package name (e.g., "rag.utils.es_conn")
414+
level:
415+
type: string
416+
description: Log level (DEBUG, INFO, WARNING, ERROR)
417+
responses:
418+
200:
419+
description: Log level updated successfully
420+
"""
421+
from quart import request
422+
data = await request.get_json()
423+
if not data or "pkg_name" not in data or "level" not in data:
424+
return get_data_error_result(message="pkg_name and level are required")
425+
pkg_name = data["pkg_name"]
426+
level = data["level"]
427+
success = set_log_level(pkg_name, level)
428+
if success:
429+
return get_json_result(data={"pkg_name": pkg_name, "level": level})
430+
else:
431+
return get_data_error_result(message=f"Invalid log level: {level}")

common/log_utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
from common.file_utils import get_project_base_directory
2222

2323
initialized_root_logger = False
24+
pkg_levels = {} # module-level to allow runtime modification
2425

2526
def init_root_logger(logfile_basename: str, log_format: str = "%(asctime)-15s %(levelname)-8s %(process)d %(message)s"):
26-
global initialized_root_logger
27+
global initialized_root_logger, pkg_levels
2728
if initialized_root_logger:
2829
return
2930
initialized_root_logger = True
@@ -46,7 +47,6 @@ def init_root_logger(logfile_basename: str, log_format: str = "%(asctime)-15s %(
4647
logging.captureWarnings(True)
4748

4849
LOG_LEVELS = os.environ.get("LOG_LEVELS", "")
49-
pkg_levels = {}
5050
for pkg_name_level in LOG_LEVELS.split(","):
5151
terms = pkg_name_level.split("=")
5252
if len(terms)!= 2:
@@ -72,6 +72,24 @@ def init_root_logger(logfile_basename: str, log_format: str = "%(asctime)-15s %(
7272
logger.info(msg)
7373

7474

75+
def set_log_level(pkg_name: str, level: str) -> bool:
76+
"""Set log level for a package at runtime. Returns True if successful."""
77+
global pkg_levels
78+
level_value = logging.getLevelName(level.strip().upper())
79+
if not isinstance(level_value, int):
80+
return False
81+
pkg_levels[pkg_name] = logging.getLevelName(level_value)
82+
pkg_logger = logging.getLogger(pkg_name)
83+
pkg_logger.setLevel(level_value)
84+
return True
85+
86+
87+
def get_log_levels() -> dict:
88+
"""Get current log levels for all packages."""
89+
global pkg_levels
90+
return dict(pkg_levels)
91+
92+
7593
def log_exception(e, *args):
7694
logging.exception(e)
7795
for a in args:

common/query_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def is_chinese(line):
3232

3333
@staticmethod
3434
def sub_special_char(line):
35-
return re.sub(r"([:\{\}/\[\]\-\*\"\(\)\|\+~\^])", r"\\\1", line).strip()
35+
return re.sub(r"([:\{\}/\[\]\-\*\?\"\(\)\|\+~\^])", r"\\\1", line).strip()
3636

3737
@staticmethod
3838
def rmWWW(txt):

docs/guides/models/supported_models.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ A complete list of models supported by RAGFlow, which will continue to expand.
2828
| Fish Audio | | | | :heavy_check_mark: | | | |
2929
| Gemini | :heavy_check_mark: | :heavy_check_mark: | | | :heavy_check_mark: | | |
3030
| Google Cloud | :heavy_check_mark: | | | | | | |
31-
| GPUStack | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
31+
| GPUStack | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
3232
| Groq | :heavy_check_mark: | | | | | | |
3333
| HuggingFace | :heavy_check_mark: | | | | :heavy_check_mark: | | |
3434
| Jina | | | | | :heavy_check_mark: | :heavy_check_mark: | |

internal/admin/handler.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"net/http"
2323
"ragflow/internal/common"
24+
"ragflow/internal/logger"
2425
"ragflow/internal/server"
2526
"ragflow/internal/service"
2627
"ragflow/internal/utility"
@@ -1106,6 +1107,33 @@ func (h *Handler) HandleNoRoute(c *gin.Context) {
11061107
})
11071108
}
11081109

1110+
// GetLogLevel returns the current log level
1111+
func (h *Handler) GetLogLevel(c *gin.Context) {
1112+
level := logger.GetLevel()
1113+
success(c, gin.H{"level": level}, "")
1114+
}
1115+
1116+
// SetLogLevelRequest set log level request
1117+
type SetLogLevelRequest struct {
1118+
Level string `json:"level" binding:"required"`
1119+
}
1120+
1121+
// SetLogLevel sets the log level at runtime
1122+
func (h *Handler) SetLogLevel(c *gin.Context) {
1123+
var req SetLogLevelRequest
1124+
if err := c.ShouldBindJSON(&req); err != nil {
1125+
errorResponse(c, "level is required", 400)
1126+
return
1127+
}
1128+
1129+
if err := logger.SetLevel(req.Level); err != nil {
1130+
errorResponse(c, err.Error(), 400)
1131+
return
1132+
}
1133+
1134+
success(c, gin.H{"level": req.Level}, "Log level updated successfully")
1135+
}
1136+
11091137
// Reports handle heartbeat reports from servers
11101138
func (h *Handler) Reports(c *gin.Context) {
11111139
var req common.BaseMessage

internal/admin/router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func (r *Router) Setup(engine *gin.Engine) {
133133
protected.POST("/license", r.handler.SetLicense)
134134
protected.POST("/license/config", r.handler.UpdateLicenseConfig)
135135
protected.GET("/license", r.handler.ShowLicense)
136+
// Log level
137+
protected.GET("/log_level", r.handler.GetLogLevel)
138+
protected.PUT("/log_level", r.handler.SetLogLevel)
136139
}
137140
}
138141

internal/handler/system.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package handler
1818

1919
import (
2020
"net/http"
21+
"ragflow/internal/logger"
2122
"ragflow/internal/server"
2223
"ragflow/internal/service"
2324

@@ -47,6 +48,13 @@ func (h *SystemHandler) Ping(c *gin.Context) {
4748
c.String(http.StatusOK, "pong")
4849
}
4950

51+
// Health health check
52+
func (h *SystemHandler) Health(c *gin.Context) {
53+
c.JSON(200, gin.H{
54+
"status": "ok",
55+
})
56+
}
57+
5058
// GetConfig get system configuration
5159
// @Summary Get System Configuration
5260
// @Description Get system configuration including register enabled status
@@ -122,3 +130,44 @@ func (h *SystemHandler) GetVersion(c *gin.Context) {
122130
"data": version.Version,
123131
})
124132
}
133+
134+
// GetLogLevel returns the current log level
135+
func (h *SystemHandler) GetLogLevel(c *gin.Context) {
136+
level := logger.GetLevel()
137+
c.JSON(http.StatusOK, gin.H{
138+
"code": 0,
139+
"message": "success",
140+
"data": gin.H{"level": level},
141+
})
142+
}
143+
144+
// SetLogLevelRequest set log level request
145+
type SetLogLevelRequest struct {
146+
Level string `json:"level" binding:"required"`
147+
}
148+
149+
// SetLogLevel sets the log level at runtime
150+
func (h *SystemHandler) SetLogLevel(c *gin.Context) {
151+
var req SetLogLevelRequest
152+
if err := c.ShouldBindJSON(&req); err != nil {
153+
c.JSON(http.StatusBadRequest, gin.H{
154+
"code": 400,
155+
"message": "level is required",
156+
})
157+
return
158+
}
159+
160+
if err := logger.SetLevel(req.Level); err != nil {
161+
c.JSON(http.StatusBadRequest, gin.H{
162+
"code": 400,
163+
"message": err.Error(),
164+
})
165+
return
166+
}
167+
168+
c.JSON(http.StatusOK, gin.H{
169+
"code": 0,
170+
"message": "Log level updated successfully",
171+
"data": gin.H{"level": req.Level},
172+
})
173+
}

internal/logger/logger.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ package logger
1919
import (
2020
"fmt"
2121
"runtime"
22+
"sync"
2223

2324
"go.uber.org/zap"
2425
"go.uber.org/zap/zapcore"
2526
)
2627

2728
var (
28-
Logger *zap.Logger
29-
Sugar *zap.SugaredLogger
29+
Logger *zap.Logger
30+
Sugar *zap.SugaredLogger
31+
levelMu sync.RWMutex
32+
atomicLevel zap.AtomicLevel
3033
)
3134

3235
// Init initializes the global logger
@@ -47,6 +50,9 @@ func Init(level string) error {
4750
zapLevel = zapcore.InfoLevel
4851
}
4952

53+
// Create atomic level for dynamic updates
54+
atomicLevel = zap.NewAtomicLevelAt(zapLevel)
55+
5056
// Custom encoder config to control output format
5157
encoderConfig := zapcore.EncoderConfig{
5258
TimeKey: "timestamp",
@@ -65,7 +71,7 @@ func Init(level string) error {
6571

6672
// Configure zap
6773
config := zap.Config{
68-
Level: zap.NewAtomicLevelAt(zapLevel),
74+
Level: atomicLevel,
6975
Development: false,
7076
Encoding: "console",
7177
EncoderConfig: encoderConfig,
@@ -136,3 +142,37 @@ func Warn(msg string, fields ...zap.Field) {
136142
}
137143
Logger.Warn(msg, fields...)
138144
}
145+
146+
// GetLevel returns the current log level
147+
func GetLevel() string {
148+
levelMu.RLock()
149+
defer levelMu.RUnlock()
150+
return atomicLevel.String()
151+
}
152+
153+
// SetLevel sets the log level at runtime
154+
func SetLevel(level string) error {
155+
levelMu.Lock()
156+
defer levelMu.Unlock()
157+
158+
var zapLevel zapcore.Level
159+
switch level {
160+
case "debug":
161+
zapLevel = zapcore.DebugLevel
162+
case "info":
163+
zapLevel = zapcore.InfoLevel
164+
case "warn", "warning":
165+
zapLevel = zapcore.WarnLevel
166+
case "error":
167+
zapLevel = zapcore.ErrorLevel
168+
case "fatal":
169+
zapLevel = zapcore.FatalLevel
170+
case "panic":
171+
zapLevel = zapcore.PanicLevel
172+
default:
173+
return fmt.Errorf("unknown log level: %s", level)
174+
}
175+
176+
atomicLevel.SetLevel(zapLevel)
177+
return nil
178+
}

internal/router/router.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,15 @@ func NewRouter(
8181
// Setup setup routes
8282
func (r *Router) Setup(engine *gin.Engine) {
8383
// Health check
84-
engine.GET("/health", func(c *gin.Context) {
85-
c.JSON(200, gin.H{
86-
"status": "ok",
87-
})
88-
})
84+
engine.GET("/health", r.systemHandler.Health)
8985

9086
// System endpoints
9187
engine.GET("/v1/system/ping", r.systemHandler.Ping)
9288
engine.GET("/v1/system/config", r.systemHandler.GetConfig)
9389
engine.GET("/v1/system/configs", r.systemHandler.GetConfigs)
9490
engine.GET("/v1/system/version", r.systemHandler.GetVersion)
91+
engine.GET("/v1/system/log_level", r.systemHandler.GetLogLevel)
92+
engine.PUT("/v1/system/log_level", r.systemHandler.SetLogLevel)
9593
engine.POST("/v1/user/register", r.userHandler.Register)
9694
// User login channels endpoint
9795
engine.GET("/v1/user/login/channels", r.userHandler.GetLoginChannels)

0 commit comments

Comments
 (0)