refactor(tracing): remove Jaeger integration and related components

This commit removes Jaeger tracing support from the project, including the associated Docker configurations, Go modules, and middleware. The changes simplify the codebase by eliminating unused tracing functionality, which was previously integrated for observability. Documentation and scripts have also been updated to reflect the removal of Jaeger references.
This commit is contained in:
wizardchen
2026-06-08 13:19:24 +08:00
committed by lyingbug
parent 77b9b6a465
commit 1778c5ac7b
24 changed files with 14 additions and 453 deletions
-2
View File
@@ -29,7 +29,6 @@ import (
"github.com/Tencent/WeKnora/internal/container"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/runtime"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/types/interfaces"
"github.com/joho/godotenv"
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
@@ -191,7 +190,6 @@ func main() {
err := c.Invoke(func(
cfg *config.Config,
router *gin.Engine,
tracer *tracing.Tracer,
resourceCleaner interfaces.ResourceCleaner,
) error {
server := &http.Server{Handler: router}
-2
View File
@@ -36,7 +36,6 @@ import (
"github.com/Tencent/WeKnora/internal/container"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/runtime"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/types/interfaces"
)
@@ -66,7 +65,6 @@ func main() {
err := c.Invoke(func(
cfg *config.Config,
router *gin.Engine,
tracer *tracing.Tracer,
resourceCleaner interfaces.ResourceCleaner,
systemSettingSvc interfaces.SystemSettingService,
) error {
-26
View File
@@ -298,31 +298,6 @@ services:
- WeKnora-network-dev
restart: unless-stopped
jaeger:
image: jaegertracing/all-in-one:latest
container_name: WeKnora-jaeger-dev
ports:
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "4317:4317"
- "4318:4318"
- "14250:14250"
- "14268:14268"
- "9411:9411"
environment:
- COLLECTOR_OTLP_ENABLED=true
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
volumes:
- jaeger_data_dev:/var/lib/jaeger
networks:
- WeKnora-network-dev
restart: unless-stopped
profiles:
- jaeger
- full
dex:
image: dexidp/dex:latest
container_name: WeKnora-dex-dev
@@ -547,7 +522,6 @@ volumes:
redis_data_dev:
minio_data_dev:
neo4j-data-dev:
jaeger_data_dev:
qdrant_data_dev:
opensearch_data_dev:
milvus_data_dev:
-32
View File
@@ -82,12 +82,6 @@ services:
- DB_NAME=${DB_NAME:-}
- TZ=${TZ:-Asia/Shanghai}
- WEKNORA_LANGUAGE=${WEKNORA_LANGUAGE:-zh-CN}
- OTEL_EXPORTER_OTLP_ENDPOINT=jaeger:4317
- OTEL_SERVICE_NAME=WeKnora
- OTEL_TRACES_EXPORTER=otlp
- OTEL_METRICS_EXPORTER=none
- OTEL_LOGS_EXPORTER=none
- OTEL_PROPAGATORS=tracecontext,baggage
# ========== Langfuse (optional observability) ==========
# Enabled automatically when LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are set.
# See docs/Langfuse集成.md for the full list of tuning knobs.
@@ -398,31 +392,6 @@ services:
- minio
- full
jaeger:
image: jaegertracing/all-in-one:1.76.0
container_name: WeKnora-jaeger
ports:
- "6831:6831/udp" # Jaeger Thrift接收器
- "6832:6832/udp" # Jaeger Thrift接收器(Compact)
- "5778:5778" # 配置端口
- "16686:16686" # Web UI
- "4317:4317" # OTLP gRPC接收器
- "4318:4318" # OTLP HTTP接收器
- "14250:14250" # 接收模型端口
- "14268:14268" # Jaeger HTTP接收器
- "9411:9411" # Zipkin兼容性端口
environment:
- COLLECTOR_OTLP_ENABLED=true
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
volumes:
- jaeger_data:/var/lib/jaeger # 持久化 Jaeger 数据
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- jaeger
- full
neo4j:
image: neo4j:2025.10.1
container_name: WeKnora-neo4j
@@ -822,7 +791,6 @@ volumes:
postgres-data:
data-files:
docreader-tmp:
jaeger_data:
minio_data:
neo4j-data:
qdrant_data:
+1 -1
View File
@@ -73,7 +73,7 @@ INIT_RERANK_MODEL_API_KEY=your_rerank_model_api_key
# 启动 MinIO 服务
docker-compose --profile minio up -d
# 或者启动完整服务(包括 MinIO、Jaeger、Neo4j、Qdrant)
# 或者启动完整服务(包括 MinIO、Neo4j、Qdrant)
docker-compose --profile full up -d
```
-1
View File
@@ -44,7 +44,6 @@ make dev-frontend
| Redis | localhost:6379 |
| MinIO Console | http://localhost:9001 |
| Neo4j Browser | http://localhost:7474 |
| Jaeger UI | http://localhost:16686 |
> Neo4j 的使用参见 [知识图谱](../核心功能/知识图谱.md) 和 [开启知识图谱功能](../核心功能/开启知识图谱功能.md)
-3
View File
@@ -18,7 +18,6 @@ make dev-start
- MinIO(对象存储)
- Neo4j(图数据库)
- DocReader(文档读取服务)
- Jaeger(链路追踪)
#### 2. 启动后端应用(新终端)
@@ -79,7 +78,6 @@ make dev-stop
- **Redis**: localhost:6379
- **MinIO Console**: http://localhost:9001
- **Neo4j Browser**: http://localhost:7474
- **Jaeger UI**: http://localhost:16686
## 开发工作流对比
@@ -227,7 +225,6 @@ VS Code 配置示例(`.vscode/launch.json`):
"DOCREADER_ADDR": "localhost:50051",
"MINIO_ENDPOINT": "localhost:9000",
"REDIS_ADDR": "localhost:6379",
"OTEL_EXPORTER_OTLP_ENDPOINT": "localhost:4317",
"NEO4J_URI": "bolt://localhost:7687"
},
"args": []
+5 -6
View File
@@ -65,12 +65,6 @@ require (
github.com/weaviate/weaviate-go-client/v5 v5.7.3
github.com/xuri/excelize/v2 v2.10.1
github.com/yanyiwu/gojieba v1.4.7
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
go.uber.org/dig v1.19.0
golang.org/x/crypto v0.51.0
golang.org/x/mod v0.36.0
@@ -310,7 +304,12 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
-2
View File
@@ -2669,8 +2669,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRl
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
-1
View File
@@ -230,7 +230,6 @@ These map to docker-compose profiles:
| `minio.enabled` | Enable MinIO storage | `false` |
| `neo4j.enabled` | Enable Neo4j (GraphRAG) | `false` |
| `qdrant.enabled` | Enable Qdrant vector DB | `false` |
| `jaeger.enabled` | Enable Jaeger tracing | `false` |
## Security Best Practices
+1 -4
View File
@@ -65,7 +65,7 @@ Components deployed:
- Redis ({{ include "weknora.redis.image" . }})
{{- end }}
{{- if or .Values.minio.enabled .Values.neo4j.enabled .Values.qdrant.enabled .Values.jaeger.enabled }}
{{- if or .Values.minio.enabled .Values.neo4j.enabled .Values.qdrant.enabled }}
Optional components enabled:
{{- if .Values.minio.enabled }}
@@ -77,9 +77,6 @@ Optional components enabled:
{{- if .Values.qdrant.enabled }}
- Qdrant (Vector Database)
{{- end }}
{{- if .Values.jaeger.enabled }}
- Jaeger (Distributed Tracing)
{{- end }}
{{- end }}
--------------------------------------------------------------------------------
-9
View File
@@ -492,12 +492,3 @@ qdrant:
persistence:
enabled: true
size: 10Gi
# -- Jaeger configuration (Distributed Tracing)
# Equivalent to: docker compose --profile jaeger
jaeger:
# -- Enable Jaeger tracing
enabled: false
image:
repository: jaegertracing/all-in-one
tag: latest
@@ -19,14 +19,12 @@ import (
"github.com/Tencent/WeKnora/internal/models/chat"
"github.com/Tencent/WeKnora/internal/models/embedding"
"github.com/Tencent/WeKnora/internal/searchutil"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/tracing/langfuse"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
secutils "github.com/Tencent/WeKnora/internal/utils"
"github.com/google/uuid"
"github.com/hibiken/asynq"
"go.opentelemetry.io/otel/attribute"
)
func (s *knowledgeService) cloneKnowledge(
@@ -258,22 +256,11 @@ func (s *knowledgeService) processChunks(ctx context.Context,
options = opts[0]
}
ctx, span := tracing.ContextWithSpan(ctx, "knowledgeService.processChunks")
defer span.End()
span.SetAttributes(
attribute.Int("tenant_id", int(knowledge.TenantID)),
attribute.String("knowledge_base_id", knowledge.KnowledgeBaseID),
attribute.String("knowledge_id", knowledge.ID),
attribute.String("embedding_model_id", kb.EmbeddingModelID),
attribute.Int("chunk_count", len(chunks)),
)
// Check if knowledge is being deleted/cancelled before processing.
// Both statuses short-circuit identically here — there's nothing to clean
// up yet so the branch is purely "stop early".
if aborted, status := s.isKnowledgeAborted(ctx, knowledge.TenantID, knowledge.ID); aborted {
logger.Infof(ctx, "Knowledge aborted (%s), skipping chunk processing: %s", status, knowledge.ID)
span.AddEvent("aborted: knowledge " + status)
return
}
@@ -284,7 +271,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
embeddingModel, err = s.modelService.GetEmbeddingModel(ctx, kb.EmbeddingModelID)
if err != nil {
logger.GetLogger(ctx).WithField("error", err).Errorf("processChunks get embedding model failed")
span.RecordError(err)
return
}
} else {
@@ -489,14 +475,12 @@ func (s *knowledgeService) processChunks(ctx context.Context,
// Nothing has been persisted yet, so both branches just bail.
if aborted, status := s.isKnowledgeAborted(ctx, knowledge.TenantID, knowledge.ID); aborted {
logger.Infof(ctx, "Knowledge aborted (%s), skipping chunk write: %s", status, knowledge.ID)
span.AddEvent("aborted: knowledge " + status + " before saving")
return
}
// Save chunks to database — ALWAYS, regardless of indexing strategy.
// Chunks are needed for wiki generation, graph extraction, and summary generation
// even when vector/keyword indexing is disabled.
span.AddEvent("create chunks")
s.beginStage(ctx, knowledge.ID, types.StageChunking, types.JSONMap{
"chunks_planned": len(insertChunks),
})
@@ -505,7 +489,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
knowledge.ErrorMessage = err.Error()
knowledge.UpdatedAt = time.Now()
s.repo.UpdateKnowledge(ctx, knowledge)
span.RecordError(err)
s.failStage(ctx, knowledge.ID, types.StageChunking,
werrors.ErrCodeChunkingFailed, "create chunks failed", err)
return
@@ -557,7 +540,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
}
// Calculate storage size required for embeddings
span.AddEvent("estimate storage size")
totalStorageSize = retrieveEngine.EstimateStorageSize(ctx, embeddingModel, indexInfoList)
if tenantInfo.StorageQuota > 0 {
// Re-fetch tenant storage information
@@ -567,7 +549,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
knowledge.ErrorMessage = err.Error()
knowledge.UpdatedAt = time.Now()
s.repo.UpdateKnowledge(ctx, knowledge)
span.RecordError(err)
return
}
// Check if there's enough storage quota available
@@ -576,7 +557,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
knowledge.ErrorMessage = "存储空间不足"
knowledge.UpdatedAt = time.Now()
s.repo.UpdateKnowledge(ctx, knowledge)
span.RecordError(errors.New("storage quota exceeded"))
return
}
}
@@ -591,11 +571,9 @@ func (s *knowledgeService) processChunks(ctx context.Context,
logger.Warnf(ctx, "Failed to cleanup chunks after deletion detected: %v", err)
}
}
span.AddEvent("aborted: knowledge " + status + " before indexing")
return
}
span.AddEvent("batch index")
err = retrieveEngine.BatchIndex(ctx, embeddingModel, indexInfoList)
if err != nil {
knowledge.ParseStatus = types.ParseStatusFailed
@@ -614,7 +592,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
); err != nil {
logger.Errorf(ctx, "Delete index failed: %v", err)
}
span.RecordError(err)
// Map vector store / embedding rate-limit errors to a
// stable code so the UI can offer "retry later" hints.
code := werrors.ErrCodeVectorStoreWriteFailed
@@ -645,7 +622,6 @@ func (s *knowledgeService) processChunks(ctx context.Context,
logger.Warnf(ctx, "Failed to cleanup index after deletion detected: %v", err)
}
}
span.AddEvent("aborted: knowledge " + status + " during processing")
return
}
} else {
@@ -11,10 +11,8 @@ import (
"github.com/Tencent/WeKnora/internal/common"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/models/embedding"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
"go.opentelemetry.io/otel/attribute"
)
// engineInfo holds information about a retrieve engine and its supported retriever types
@@ -200,8 +198,6 @@ func (c *CompositeRetrieveEngine) concurrentExecWithError(
func (c *CompositeRetrieveEngine) Index(ctx context.Context,
embedder embedding.Embedder, indexInfo *types.IndexInfo,
) error {
ctx, span := tracing.ContextWithSpan(ctx, "CompositeRetrieveEngine.Index")
defer span.End()
err := c.concurrentExecWithError(ctx, func(ctx context.Context, engineInfo *engineInfo) error {
if err := engineInfo.retrieveEngine.Index(ctx, embedder, indexInfo, engineInfo.retrieverType); err != nil {
logger.Errorf(ctx, "Repository %s failed to save: %v", engineInfo.retrieveEngine.EngineType(), err)
@@ -209,11 +205,6 @@ func (c *CompositeRetrieveEngine) Index(ctx context.Context,
}
return nil
})
span.RecordError(err)
span.SetAttributes(
attribute.String("embedder", embedder.GetModelName()),
attribute.String("source_id", indexInfo.SourceID),
)
return err
}
@@ -221,8 +212,6 @@ func (c *CompositeRetrieveEngine) Index(ctx context.Context,
func (c *CompositeRetrieveEngine) BatchIndex(ctx context.Context,
embedder embedding.Embedder, indexInfoList []*types.IndexInfo,
) error {
ctx, span := tracing.ContextWithSpan(ctx, "CompositeRetrieveEngine.BatchIndex")
defer span.End()
// Deduplicate sourceIDs
indexInfoList = common.Deduplicate(func(info *types.IndexInfo) string { return info.SourceID }, indexInfoList...)
err := c.concurrentExecWithError(ctx, func(ctx context.Context, engineInfo *engineInfo) error {
@@ -237,11 +226,6 @@ func (c *CompositeRetrieveEngine) BatchIndex(ctx context.Context,
}
return nil
})
span.RecordError(err)
span.SetAttributes(
attribute.String("embedder", embedder.GetModelName()),
attribute.Int("index_info_count", len(indexInfoList)),
)
return err
}
@@ -318,18 +302,13 @@ func (c *CompositeRetrieveEngine) DeleteByKnowledgeIDList(ctx context.Context,
func (c *CompositeRetrieveEngine) EstimateStorageSize(ctx context.Context,
embedder embedding.Embedder, indexInfoList []*types.IndexInfo,
) int64 {
ctx, span := tracing.ContextWithSpan(ctx, "CompositeRetrieveEngine.EstimateStorageSize")
defer span.End()
sum := atomic.Int64{}
err := c.concurrentExecWithError(ctx, func(ctx context.Context, engineInfo *engineInfo) error {
sum.Add(engineInfo.retrieveEngine.EstimateStorageSize(ctx, embedder, indexInfoList, engineInfo.retrieverType))
return nil
})
span.RecordError(err)
span.SetAttributes(
attribute.String("embedder", embedder.GetModelName()),
attribute.Int("index_info_count", len(indexInfoList)),
attribute.Int64("storage_size", sum.Load()),
)
if err != nil {
logger.Errorf(ctx, "EstimateStorageSize failed: %v", err)
}
return sum.Load()
}
-29
View File
@@ -78,7 +78,6 @@ import (
"github.com/Tencent/WeKnora/internal/models/utils/ollama"
"github.com/Tencent/WeKnora/internal/router"
"github.com/Tencent/WeKnora/internal/stream"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/tracing/langfuse"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
@@ -107,15 +106,12 @@ func BuildContainer(container *dig.Container) *dig.Container {
// Core infrastructure configuration
logger.Debugf(ctx, "[Container] Registering core infrastructure...")
must(container.Provide(config.LoadConfig))
must(container.Provide(initTracer))
must(container.Provide(initLangfuse))
must(container.Provide(initDatabase))
must(container.Provide(initFileService))
must(container.Provide(initRedisClient))
must(container.Provide(initAntsPool))
// Register tracer cleanup handler (tracer needs to be available for cleanup registration)
must(container.Invoke(registerTracerCleanup))
must(container.Invoke(registerLangfuseCleanup))
// Register goroutine pool cleanup handler
@@ -414,18 +410,6 @@ func must(err error) {
}
}
// initTracer initializes OpenTelemetry tracer
// Sets up distributed tracing for observability across the application
// Parameters:
// - None
//
// Returns:
// - Configured tracer instance
// - Error if initialization fails
func initTracer() (*tracing.Tracer, error) {
return tracing.InitTracer()
}
// initLangfuse initializes the Langfuse ingestion client.
// Configuration is read from LANGFUSE_* environment variables (see
// docs/langfuse.md). Returns a disabled manager if credentials are absent —
@@ -1205,19 +1189,6 @@ func registerPoolCleanup(pool *ants.Pool, cleaner interfaces.ResourceCleaner) {
})
}
// registerTracerCleanup registers the tracer for cleanup
// Ensures proper cleanup of the tracer when application shuts down
// Parameters:
// - tracer: Tracer instance
// - cleaner: Resource cleaner
func registerTracerCleanup(tracer *tracing.Tracer, cleaner interfaces.ResourceCleaner) {
// Register the cleanup function - actual context will be provided during cleanup
cleaner.RegisterWithName("Tracer", func() error {
// Create context for cleanup with longer timeout for tracer shutdown
return tracer.Cleanup(context.Background())
})
}
// registerLangfuseCleanup ensures buffered Langfuse events are flushed on
// shutdown. A 5-second timeout matches other external-service cleanups and
// balances data durability against a slow remote endpoint holding up exit.
+1 -45
View File
@@ -20,14 +20,10 @@ import (
"github.com/Tencent/WeKnora/internal/event"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/models/chat"
"github.com/Tencent/WeKnora/internal/tracing"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"gorm.io/gorm"
)
@@ -667,14 +663,6 @@ func (s *Service) LoadAndStartChannels() error {
// the leader lock and opens the connection; other instances periodically
// retry so they can take over if the leader dies.
func (s *Service) StartChannel(channel *IMChannel) error {
_, span := tracing.ContextWithSpan(context.Background(), "im.StartChannel")
defer span.End()
span.SetAttributes(
attribute.String("im.channel_id", channel.ID),
attribute.String("im.platform", channel.Platform),
attribute.String("im.mode", channel.Mode),
)
s.mu.Lock()
factory, ok := s.adapterFactories[channel.Platform]
if !ok {
@@ -1036,18 +1024,6 @@ func (s *Service) isDuplicate(ctx context.Context, messageID string) bool {
// HandleMessage processes an incoming IM message end-to-end using channel config.
func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, channelID string) error {
ctx, span := tracing.ContextWithSpan(ctx, "im.HandleMessage")
defer span.End()
span.SetAttributes(
attribute.String("im.channel_id", channelID),
attribute.String("im.platform", string(msg.Platform)),
attribute.String("im.user_id", msg.UserID),
attribute.String("im.chat_id", msg.ChatID),
attribute.String("im.thread_id", msg.ThreadID),
attribute.String("im.message_type", string(msg.MessageType)),
attribute.Bool("im.has_quote", msg.Quote != nil),
)
// Dedup: skip if this message was already processed (IM platforms may retry)
if msg.MessageID != "" {
if s.isDuplicate(ctx, msg.MessageID) {
@@ -1081,8 +1057,6 @@ func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, chann
}
}
span.SetAttributes(attribute.String("im.session_mode", channel.SessionMode))
// Resolve threadID for key building — only include in thread mode to avoid
// leaking thread scope into user-mode rate limit / inflight keys.
threadID := ""
@@ -1223,7 +1197,6 @@ func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, chann
pos, enqueueErr := s.qaQueue.Enqueue(req)
if enqueueErr != nil {
qaCancel()
span.AddEvent("queue rejected", trace.WithAttributes(attribute.String("reason", enqueueErr.Error())))
logger.Warnf(ctx, "[IM] Queue rejected: user=%s reason=%v", msg.UserID, enqueueErr)
_ = adapter.SendReply(ctx, msg, &ReplyMessage{
Content: "当前排队人数较多,请稍后再试。",
@@ -1254,13 +1227,7 @@ func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, chann
// executeQARequest is the worker handler that runs the QA pipeline for a queued request.
// It is called by qaQueue workers and must not block indefinitely.
func (s *Service) executeQARequest(req *qaRequest) {
ctx, span := tracing.ContextWithSpan(req.ctx, "im.ExecuteQA")
defer span.End()
span.SetAttributes(
attribute.String("im.channel_id", req.channelID),
attribute.String("im.user_key", req.userKey),
attribute.String("im.user_id", req.msg.UserID),
)
ctx := req.ctx
defer req.cancel()
// Track in-flight request so /stop can cancel it.
@@ -1270,7 +1237,6 @@ func (s *Service) executeQARequest(req *qaRequest) {
// Check if a pre-execution /stop was issued while this request was queued.
if s.checkAndClearStopMarker(ctx, req.userKey) {
span.AddEvent("cancelled by remote /stop before execution")
logger.Infof(ctx, "[IM] Request cancelled by remote /stop before execution: %s", req.userKey)
return
}
@@ -1289,7 +1255,6 @@ func (s *Service) executeQARequest(req *qaRequest) {
if !streamDisabled {
if streamer, ok := req.adapter.(StreamSender); ok {
if err := s.handleMessageStream(ctx, req.msg, req.session, req.agent, kbIDs, streamer, req.adapter, req.userKey, req.tenant); err != nil {
span.SetStatus(codes.Error, err.Error())
logger.Errorf(ctx, "[IM] Stream QA failed: %v", err)
}
return
@@ -1299,7 +1264,6 @@ func (s *Service) executeQARequest(req *qaRequest) {
// Non-streaming fallback: collect full answer then send.
answer, err := s.runQA(ctx, req.session, req.msg.Content, req.agent, kbIDs, req.userKey, req.msg.Quote)
if err != nil {
span.SetStatus(codes.Error, err.Error())
logger.Errorf(ctx, "[IM] QA failed: %v, sending fallback reply", err)
answer = "抱歉,处理您的问题时出现了异常,请稍后再试。"
}
@@ -1329,14 +1293,6 @@ func (s *Service) handleCommand(
channelSession *ChannelSession,
customAgent *types.CustomAgent,
) error {
ctx, span := tracing.ContextWithSpan(ctx, "im.HandleCommand")
defer span.End()
span.SetAttributes(
attribute.String("im.command", cmd.Name()),
attribute.String("im.channel_id", channel.ID),
attribute.String("im.user_id", msg.UserID),
)
agentName := ""
if customAgent != nil {
agentName = customAgent.Name
-117
View File
@@ -1,117 +0,0 @@
package middleware
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"github.com/Tencent/WeKnora/internal/tracing"
)
// Custom ResponseWriter to capture response content
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
// Override Write method to write response content to buffer and original writer
func (r responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
// TracingMiddleware provides a Gin middleware that creates a trace span for each request
func TracingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Extract trace context from request headers
propagator := tracing.GetTracer()
if propagator == nil {
c.Next()
return
}
// Create new span
spanName := fmt.Sprintf("%s %s", c.Request.Method, c.FullPath())
ctx, span := tracing.ContextWithSpan(c.Request.Context(), spanName)
defer span.End()
// Set basic span attributes
span.SetAttributes(
attribute.String("http.method", c.Request.Method),
attribute.String("http.url", c.Request.URL.String()),
attribute.String("http.path", c.FullPath()),
)
// Record request headers (optional, or selectively record important headers)
for key, values := range c.Request.Header {
// Skip sensitive or unnecessary headers
if strings.ToLower(key) == "authorization" || strings.ToLower(key) == "cookie" {
continue
}
span.SetAttributes(attribute.String("http.request.header."+key, strings.Join(values, ";")))
}
// Record request body (for POST/PUT/PATCH requests)
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "PATCH" {
if c.Request.Body != nil {
bodyBytes, _ := io.ReadAll(c.Request.Body)
span.SetAttributes(attribute.String("http.request.body", string(bodyBytes)))
// Reset request body because ReadAll consumes the Reader content
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}
}
// Record query parameters
if len(c.Request.URL.RawQuery) > 0 {
span.SetAttributes(attribute.String("http.request.query", c.Request.URL.RawQuery))
}
// Set request context with span context
c.Request = c.Request.WithContext(ctx)
// Store tracing context in Gin context
c.Set("trace.span", span)
c.Set("trace.ctx", ctx)
// Create response body capturer
responseBody := &bytes.Buffer{}
responseWriter := &responseBodyWriter{
ResponseWriter: c.Writer,
body: responseBody,
}
c.Writer = responseWriter
// Process request
c.Next()
// Set response status code
statusCode := c.Writer.Status()
span.SetAttributes(attribute.Int("http.status_code", statusCode))
// Record response body
responseContent := responseBody.String()
if len(responseContent) > 0 {
span.SetAttributes(attribute.String("http.response.body", responseContent))
}
// Record response headers (optional, or selectively record important headers)
for key, values := range c.Writer.Header() {
span.SetAttributes(attribute.String("http.response.header."+key, strings.Join(values, ";")))
}
// Mark as error if status code >= 400
if statusCode >= 400 {
span.SetStatus(codes.Error, fmt.Sprintf("HTTP %d", statusCode))
if err := c.Errors.Last(); err != nil {
span.RecordError(err.Err)
}
} else {
span.SetStatus(codes.Ok, "")
}
}
}
-3
View File
@@ -141,9 +141,6 @@ func NewRouter(params RouterParams) *gin.Engine {
// Diagnostic preview of presigned URLs (Admin only, behind auth middleware).
servePresignedPreview(r, params.Config)
// 添加OpenTelemetry追踪中间件
// r.Use(middleware.TracingMiddleware())
// Langfuse observability — only active when LANGFUSE_* env vars are set.
// The middleware is registered unconditionally; when disabled it's a no-op.
r.Use(langfuse.GinMiddleware())
-105
View File
@@ -1,105 +0,0 @@
package tracing
import (
"context"
"log"
"os"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
)
const (
AppName = "WeKnoraApp"
)
type Tracer struct {
Cleanup func(context.Context) error
}
var tracer trace.Tracer
// InitTracer initializes OpenTelemetry tracer
func InitTracer() (*Tracer, error) {
// Create resource description
labels := []attribute.KeyValue{
semconv.TelemetrySDKLanguageGo,
semconv.ServiceNameKey.String(AppName),
}
res := resource.NewWithAttributes(semconv.SchemaURL, labels...)
var err error
// First try to create OTLP exporter (can connect to Jaeger, Zipkin, etc.)
var traceExporter sdktrace.SpanExporter
if endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"); endpoint != "" {
// Use gRPC exporter
client := otlptracegrpc.NewClient(
otlptracegrpc.WithEndpoint(endpoint),
otlptracegrpc.WithInsecure(),
)
traceExporter, err = otlptrace.New(context.Background(), client)
if err != nil {
return nil, err
}
} else {
// If no OTLP endpoint is set, default to standard output
traceExporter, err = stdouttrace.New()
if err != nil {
return nil, err
}
}
// Create batch SpanProcessor
bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
sampler := sdktrace.AlwaysSample()
// Create and register TracerProvider
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sampler),
sdktrace.WithResource(res),
sdktrace.WithSpanProcessor(bsp),
)
otel.SetTracerProvider(tp)
// Set global propagator
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
// Create Tracer for project use
tracer = tp.Tracer(AppName)
// Return cleanup function
return &Tracer{
Cleanup: func(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := tp.Shutdown(ctx); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
return err
}
return nil
},
}, nil
}
// GetTracer gets global Tracer
func GetTracer() trace.Tracer {
return tracer
}
// Create context with span
func ContextWithSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
return GetTracer().Start(ctx, name, opts...)
}
-1
View File
@@ -88,7 +88,6 @@ WeKnora `docker-compose.yml` 大量服务是 **profile 限定**,本镜像只
| `minio` | 对象存储替代本地文件 |
| `qdrant` / `milvus` / `weaviate` / `doris` | 替代 pgvector |
| `neo4j` | GraphRAG 知识图谱 |
| `jaeger` | OpenTelemetry trace UI |
| `langfuse` | 自建 Langfuse 可观测平台 |
| `dex` | OIDC 登录 |
| `odl-hybrid` | OpenDataLoader Docling hybrid(体积大,无预发布镜像,需 `--build`) |
+1 -1
View File
@@ -182,7 +182,7 @@ docker compose up -d
echo "[prepare] 4.5/6 预拉 sandbox 镜像 (Agent Skills 用, 非常驻)"
docker compose --profile full pull sandbox || true
# 其他向量库 / 可观测组件 (qdrant, milvus, weaviate, doris, neo4j, langfuse-*, minio, jaeger, dex)
# 其他向量库 / 可观测组件 (qdrant, milvus, weaviate, doris, neo4j, langfuse-*, minio, dex)
# 不预拉, 体积可省 5-15GB. 用户如需启用:
# cd /opt/WeKnora && docker compose --profile <name> up -d
+2 -12
View File
@@ -68,7 +68,6 @@ show_help() {
echo " --minio 启动 MinIO 对象存储"
echo " --qdrant 启动 Qdrant 向量数据库"
echo " --neo4j 启动 Neo4j 图数据库"
echo " --jaeger 启动 Jaeger 链路追踪"
echo " --dex 启动 Dex(OIDC 身份认证)"
echo " --langfuse 启动 Langfuse(默认已开启)"
echo " --no-langfuse 不启动 Langfuse"
@@ -78,7 +77,6 @@ show_help() {
echo "示例:"
echo " $0 start # 启动基础服务"
echo " $0 start --qdrant # 启动基础服务 + Qdrant"
echo " $0 start --qdrant --jaeger # 启动基础服务 + Qdrant + Jaeger"
echo " $0 start --dex # 启动基础服务 + Dex"
echo " $0 start --odl-hybrid # 启动基础服务 + OpenDataLoader hybrid"
echo " $0 start --full # 启动所有服务"
@@ -172,7 +170,7 @@ start_services() {
# 解析 profile 参数
shift # 移除 "start" 命令本身
# 默认启动基础设施(postgres / redis / docreader)+ langfuse,
# 其余可选服务通过 --minio / --qdrant / --neo4j / --jaeger / --dex / --full 按需开启。
# 其余可选服务通过 --minio / --qdrant / --neo4j / --dex / --full 按需开启。
PROFILES="--profile langfuse"
ENABLED_SERVICES="langfuse"
while [ $# -gt 0 ]; do
@@ -189,10 +187,6 @@ start_services() {
PROFILES="$PROFILES --profile neo4j"
ENABLED_SERVICES="$ENABLED_SERVICES neo4j"
;;
--jaeger)
PROFILES="$PROFILES --profile jaeger"
ENABLED_SERVICES="$ENABLED_SERVICES jaeger"
;;
--dex)
PROFILES="$PROFILES --profile dex"
ENABLED_SERVICES="$ENABLED_SERVICES dex"
@@ -212,7 +206,7 @@ start_services() {
;;
--full)
PROFILES="--profile full"
ENABLED_SERVICES="minio qdrant neo4j jaeger dex"
ENABLED_SERVICES="minio qdrant neo4j dex"
break
;;
*)
@@ -254,9 +248,6 @@ start_services() {
if [[ "$ENABLED_SERVICES" == *"neo4j"* ]]; then
echo " - Neo4j: localhost:7474 (Bolt: localhost:7687)"
fi
if [[ "$ENABLED_SERVICES" == *"jaeger"* ]]; then
echo " - Jaeger: localhost:16686"
fi
if [[ "$ENABLED_SERVICES" == *"dex"* ]]; then
echo " - Dex: localhost:5556"
fi
@@ -349,7 +340,6 @@ start_app() {
export MINIO_ENDPOINT=localhost:9000
export REDIS_ADDR=localhost:6379
export MILVUS_ADDRESS=localhost:19530
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
export NEO4J_URI=bolt://localhost:7687
export QDRANT_HOST=localhost
-1
View File
@@ -99,7 +99,6 @@ log_info "访问地址:"
echo " - 前端: http://localhost:5173"
echo " - 后端 API: http://localhost:8080"
echo " - MinIO Console: http://localhost:9001"
echo " - Jaeger UI: http://localhost:16686"
echo ""
log_info "管理命令:"
-2
View File
@@ -749,7 +749,6 @@ else
log_success "所有服务启动完成,可通过以下地址访问:"
printf "%b\n" "${GREEN} - 前端界面: http://localhost:${FRONTEND_PORT:-80}${NC}"
printf "%b\n" "${GREEN} - API接口: http://localhost:${APP_PORT:-8080}${NC}"
printf "%b\n" "${GREEN} - Jaeger链路追踪: http://localhost:16686${NC}"
echo ""
log_info "正在持续输出容器日志(按 Ctrl+C 退出日志,容器不会停止)..."
"$DOCKER_COMPOSE_BIN" $DOCKER_COMPOSE_SUBCMD logs app docreader postgres --since=10s -f
@@ -763,7 +762,6 @@ else
log_success "Docker容器启动完成,可通过以下地址访问:"
printf "%b\n" "${GREEN} - 前端界面: http://localhost:${FRONTEND_PORT:-80}${NC}"
printf "%b\n" "${GREEN} - API接口: http://localhost:${APP_PORT:-8080}${NC}"
printf "%b\n" "${GREEN} - Jaeger链路追踪: http://localhost:16686${NC}"
echo ""
log_info "正在持续输出容器日志(按 Ctrl+C 退出日志,容器不会停止)..."
"$DOCKER_COMPOSE_BIN" $DOCKER_COMPOSE_SUBCMD logs app docreader postgres --since=10s -f