pkg/rules/mcp/common_otel_instrumenter.go (114 lines of code) (raw):

// Copyright (c) 2025 Alibaba Group Holding Ltd. // // Licensed 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 mcp import ( "context" "fmt" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/ai" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/instrumenter" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/version" "github.com/mark3labs/mcp-go/mcp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" ) type aiCommonRequest struct { } func (aiCommonRequest) GetAIOperationName(request mcpRequest) string { return request.operationName } func (aiCommonRequest) GetAISystem(request mcpRequest) string { return request.system } type LExperimentalAttributeExtractor struct { Base ai.AICommonAttrsExtractor[mcpRequest, any, aiCommonRequest] } func (l LExperimentalAttributeExtractor) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request mcpRequest) ([]attribute.KeyValue, context.Context) { attributes, parentContext = l.Base.OnStart(attributes, parentContext, request) var val attribute.Value if request.methodType == string(mcp.MethodToolsCall) { attributes = append(attributes, attribute.KeyValue{ Key: "gen_ai.tool.name", Value: attribute.StringValue(request.methodName), }, attribute.KeyValue{ Key: "gen_ai.tool.call.id", Value: attribute.StringValue(request.CallId), }) } if request.input != nil { for k, v := range request.input { switch v.(type) { case string: val = attribute.StringValue(v.(string)) case int: val = attribute.IntValue(v.(int)) case int64: val = attribute.Int64Value(v.(int64)) case float64: val = attribute.Float64Value(v.(float64)) case bool: val = attribute.BoolValue(v.(bool)) default: val = attribute.StringValue(fmt.Sprintf("%#v", v)) } if val.Type() > 0 { attributes = append(attributes, attribute.KeyValue{ Key: attribute.Key("gen_ai.other_input." + k), Value: val, }) } val = attribute.Value{} } } return attributes, parentContext } func (l LExperimentalAttributeExtractor) OnEnd(attributes []attribute.KeyValue, context context.Context, request mcpRequest, response any, err error) ([]attribute.KeyValue, context.Context) { attributes, context = l.Base.OnEnd(attributes, context, request, response, err) if request.output != nil { var val attribute.Value for k, v := range request.output { switch v.(type) { case string: val = attribute.StringValue(v.(string)) case int: val = attribute.IntValue(v.(int)) case int64: val = attribute.Int64Value(v.(int64)) case float64: val = attribute.Float64Value(v.(float64)) case bool: val = attribute.BoolValue(v.(bool)) default: val = attribute.StringValue(fmt.Sprintf("%#v", v)) } if val.Type() > 0 { attributes = append(attributes, attribute.KeyValue{ Key: attribute.Key("gen_ai.other_output." + k), Value: val, }) } val = attribute.Value{} } } return attributes, context } func BuildServerCommonOtelInstrumenter() instrumenter.Instrumenter[mcpRequest, any] { builder := instrumenter.Builder[mcpRequest, any]{} return builder.Init().SetSpanNameExtractor(&ai.AISpanNameExtractor[mcpRequest, any]{Getter: aiCommonRequest{}}). SetSpanKindExtractor(&instrumenter.AlwaysServerExtractor[mcpRequest]{}). AddAttributesExtractor(&LExperimentalAttributeExtractor{}). SetInstrumentationScope(instrumentation.Scope{ Name: utils.MCP_SCOPE_NAME, Version: version.Tag, }). BuildInstrumenter() } func BuildClientCommonOtelInstrumenter() instrumenter.Instrumenter[mcpRequest, any] { builder := instrumenter.Builder[mcpRequest, any]{} return builder.Init().SetSpanNameExtractor(&ai.AISpanNameExtractor[mcpRequest, any]{Getter: aiCommonRequest{}}). SetSpanKindExtractor(&instrumenter.AlwaysClientExtractor[mcpRequest]{}). AddAttributesExtractor(&LExperimentalAttributeExtractor{}). SetInstrumentationScope(instrumentation.Scope{ Name: utils.MCP_SCOPE_NAME, Version: version.Tag, }). BuildInstrumenter() }