Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions app/dubbo-admin/dubbo-admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,23 @@ log:
# [Optional] config for observability.
observability:
# [Optional] config for grafana and prometheus.
grafana: http://101.34.253.152:30300
# [Optional] config for prometheus.
prometheus: http://101.34.253.152:30900/

# [Optional] config for console.
console:
grafana: http://101.34.253.152:30300
# [Optional] config for prometheus.
prometheus: http://101.34.253.152:30900/
# [Optional] config for log providers used by MCP log tools.
# defaultProvider must match one provider name in providers.
# Currently only Loki is supported. endpoint should be the Loki HTTP API root.
# tenant is optional and will be sent as X-Scope-OrgID for multi-tenant Loki.
# logs:
# defaultProvider: loki-main
# providers:
# - name: loki-main
# type: loki
# endpoint: http://localhost:3100
# tenant: ""

# [Optional] config for console.
console:
# [Optional] port of http server, default is 8888.
port: 8888
# [Optional] running mode of Gin, options are debug, release, test, default is release.
Expand Down
169 changes: 169 additions & 0 deletions docs/examples/observability/alloy-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Example values for the Grafana Alloy Helm chart.
# The generated Loki labels match the MCP log tools:
# - appName filters app/appName labels
# - serviceName filters service/serviceName/service_name labels
# - instanceName filters instance/instanceName/pod labels
# - mesh filters the mesh label
#
# traceId is intentionally not promoted to a Loki label in this example.
# Trace IDs are usually high-cardinality values; use the MCP keywords filter
# or add custom log parsing if your deployment requires trace-id lookup.

global:
image:
registry: m.daocloud.io/docker.io

image:
registry: m.daocloud.io/docker.io
repository: grafana/alloy
pullPolicy: IfNotPresent

controller:
type: deployment
replicas: 1

configReloader:
enabled: false

rbac:
create: true

serviceAccount:
create: true

alloy:
enableReporting: false

mounts:
varlog: false
dockercontainers: false

resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 300m
memory: 512Mi

configMap:
create: true
content: |-
logging {
level = "info"
format = "logfmt"
}

discovery.kubernetes "pods" {
role = "pod"
}

discovery.relabel "dubbo_pod_logs" {
targets = discovery.kubernetes.pods.targets

rule {
source_labels = ["__meta_kubernetes_namespace"]
regex = "default|dubbo-system|dubbo-samples-shop|logging"
action = "keep"
}

rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}

rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}

rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "instance"
}

rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}

rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
regex = "(.+)"
target_label = "service_name"
action = "replace"
}

rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
regex = "(.+)"
target_label = "service_name"
action = "replace"
}

rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
regex = "(.+)"
target_label = "service_name"
action = "replace"
}

rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
regex = "(.+)"
target_label = "app"
action = "replace"
}

rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
regex = "(.+)"
target_label = "app"
action = "replace"
}

rule {
source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_container_name"]
separator = "/"
target_label = "job"
}
}

loki.source.kubernetes "dubbo_pod_logs" {
targets = discovery.relabel.dubbo_pod_logs.output
forward_to = [loki.process.dubbo_logs.receiver]
}

loki.process "dubbo_logs" {
forward_to = [loki.write.local.receiver]

stage.decolorize {}

stage.static_labels {
values = {
cluster = "k8s1.28.2",
mesh = "nacos2.5",
}
}
}

loki.write "local" {
endpoint {
url = "http://loki.logging.svc.cluster.local:3100/loki/api/v1/push"
}
}
7 changes: 7 additions & 0 deletions pkg/config/observability/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type Config struct {
Grafana string `json:"grafana" yaml:"grafana"`
// Prometheus is the url of prometheus
Prometheus string `json:"prometheus" yaml:"prometheus"`
// Logs configures the log query provider.
Logs *LogsConfig `json:"logs,omitempty" yaml:"logs,omitempty"`

GrafanaBaseURL *url.URL `json:"-" yaml:"-"`
PrometheusBaseURL *url.URL `json:"-" yaml:"-"`
Expand All @@ -55,6 +57,11 @@ func (c *Config) Validate() error {
}
c.GrafanaBaseURL = grafanaBaseURL
}
if c.Logs != nil {
if err := c.Logs.Validate(); err != nil {
return err
}
}
return nil
}

Expand Down
89 changes: 89 additions & 0 deletions pkg/config/observability/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package observability

import (
"fmt"
"net/url"

"github.com/duke-git/lancet/v2/strutil"

"github.com/apache/dubbo-admin/pkg/common/bizerror"
)

type LogProviderType string

const (
LogProviderLoki LogProviderType = "loki"
)

type LogsConfig struct {
DefaultProvider string `json:"defaultProvider" yaml:"defaultProvider"`
Providers []LogProviderConfig `json:"providers" yaml:"providers"`
}

type LogProviderConfig struct {
Name string `json:"name" yaml:"name"`
Type LogProviderType `json:"type" yaml:"type"`
Endpoint string `json:"endpoint" yaml:"endpoint"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
}

func (c *LogsConfig) Validate() error {
if c == nil || len(c.Providers) == 0 {
return nil
}
if strutil.IsBlank(c.DefaultProvider) {
return bizerror.New(bizerror.ConfigError, "default log provider is required")
}

foundDefault := false
for _, provider := range c.Providers {
if strutil.IsBlank(provider.Name) {
return bizerror.New(bizerror.ConfigError, "log provider name is required")
}
if provider.Name == c.DefaultProvider {
foundDefault = true
}
if provider.Type != LogProviderLoki {
return bizerror.New(bizerror.ConfigError, fmt.Sprintf("unsupported log provider type: %s", provider.Type))
}
if strutil.IsBlank(provider.Endpoint) {
return bizerror.New(bizerror.ConfigError, "log provider endpoint is required")
}
if _, err := url.Parse(provider.Endpoint); err != nil {
return bizerror.Wrap(err, bizerror.ConfigError, fmt.Sprintf("invalid log provider endpoint: %s", provider.Endpoint))
}
}
if !foundDefault {
return bizerror.New(bizerror.ConfigError, fmt.Sprintf("default log provider %q is not configured", c.DefaultProvider))
}
return nil
}

func (c *LogsConfig) Default() (LogProviderConfig, bool) {
if c == nil {
return LogProviderConfig{}, false
}
for _, provider := range c.Providers {
if provider.Name == c.DefaultProvider {
return provider, true
}
}
return LogProviderConfig{}, false
}
8 changes: 4 additions & 4 deletions pkg/console/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ func init() {
}

type consoleWebServer struct {
Engine *gin.Engine
cfg *console.Config
cs consolectx.Context
mcpPath string // MCP端点路径,用于auth中间件跳过认证
Engine *gin.Engine
cfg *console.Config
cs consolectx.Context
mcpPath string // MCP端点路径,用于auth中间件跳过认证
mcpAPIKey string // MCP API密钥,用于认证
}

Expand Down
32 changes: 32 additions & 0 deletions pkg/mcp/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package mcp
import (
"github.com/apache/dubbo-admin/pkg/mcp/common"
"github.com/apache/dubbo-admin/pkg/mcp/tools"
logtools "github.com/apache/dubbo-admin/pkg/mcp/tools/log"
)

// RegisterTools 注册所有 MCP 工具
Expand Down Expand Up @@ -310,4 +311,35 @@ func RegisterTools(server *Server) {
},
Handler: tools.GetApplicationServices,
})

logSearchProperties := logtools.LogSearchProperties()
server.RegisterTool(&common.ToolDef{
Name: "search_logs",
Description: "查询 Dubbo 服务日志,支持按应用、服务、实例、TraceID 和关键字过滤",
InputSchema: common.InputSchema{
Type: "object",
Properties: logSearchProperties,
},
Handler: logtools.SearchLogs,
})

server.RegisterTool(&common.ToolDef{
Name: "analyze_error_logs",
Description: "分析错误日志并按错误模式聚合",
InputSchema: common.InputSchema{
Type: "object",
Properties: logSearchProperties,
},
Handler: logtools.AnalyzeErrorLogs,
})

server.RegisterTool(&common.ToolDef{
Name: "get_log_capabilities",
Description: "获取日志查询能力,返回 Loki 当前可用 labels 以及查询参数到 labels 的映射",
InputSchema: common.InputSchema{
Type: "object",
Properties: logtools.LogCapabilitiesProperties(),
},
Handler: logtools.GetLogCapabilities,
})
}
Loading