in input/otlp/traces.go [575:1046]
func TranslateSpan(spanKind ptrace.SpanKind, attributes pcommon.Map, event *modelpb.APMEvent) {
isJaeger := strings.HasPrefix(event.GetAgent().GetName(), "Jaeger")
var (
netPeerName string
netPeerIP string
netPeerPort int
)
var (
peerService string
peerAddress string
)
var (
httpURL string
httpHost string
httpTarget string
httpURLPath string
httpURLQuery string
httpScheme = "http"
)
var (
messageSystem string
messageOperation string
messageTempDestination bool
)
var (
rpcSystem string
rpcService string
)
var (
genAiSystem string
)
var http modelpb.HTTP
var httpRequest modelpb.HTTPRequest
var httpResponse modelpb.HTTPResponse
var message modelpb.Message
var db modelpb.DB
var destinationService modelpb.DestinationService
var serviceTarget modelpb.ServiceTarget
var isHTTP, isDatabase, isRPC, isMessaging, isGenAi bool
var samplerType, samplerParam pcommon.Value
attributes.Range(func(kDots string, v pcommon.Value) bool {
if isJaeger {
switch kDots {
case "sampler.type":
samplerType = v
return true
case "sampler.param":
samplerParam = v
return true
}
}
k := replaceDots(kDots)
switch v.Type() {
case pcommon.ValueTypeBool:
switch kDots {
case semconv16.AttributeMessagingTempDestination, semconv.AttributeMessagingDestinationTemporary:
messageTempDestination = v.Bool()
fallthrough
default:
setLabel(k, event, v)
}
case pcommon.ValueTypeInt:
switch kDots {
case semconv25.AttributeHTTPStatusCode, semconv.AttributeHTTPResponseStatusCode:
httpResponse.StatusCode = uint32(v.Int())
http.Response = &httpResponse
isHTTP = true
case semconv25.AttributeNetPeerPort, semconv.AttributeServerPort, "peer.port":
netPeerPort = int(v.Int())
case semconv.AttributeRPCGRPCStatusCode:
rpcSystem = "grpc"
isRPC = true
default:
setLabel(k, event, v)
}
case pcommon.ValueTypeStr:
stringval := truncate(v.Str())
switch kDots {
// http.*
case semconv12.AttributeHTTPHost: //removed after 1.12 (stable)
httpHost = stringval
isHTTP = true
case semconv25.AttributeHTTPScheme, semconv.AttributeURLScheme:
httpScheme = stringval
isHTTP = true
case semconv25.AttributeHTTPTarget:
httpTarget = stringval
isHTTP = true
case semconv.AttributeURLPath:
httpURLPath = stringval
isHTTP = true
case semconv.AttributeURLQuery:
httpURLQuery = stringval
isHTTP = true
case semconv25.AttributeHTTPURL, semconv.AttributeURLFull:
httpURL = stringval
isHTTP = true
case semconv25.AttributeHTTPMethod, semconv.AttributeHTTPRequestMethod:
httpRequest.Method = stringval
http.Request = &httpRequest
isHTTP = true
// db.*
case "sql.query":
if db.Type == "" {
db.Type = "sql"
}
fallthrough
case semconv25.AttributeDBStatement, semconv.AttributeDBQueryText:
// Statement should not be truncated, use original string value.
db.Statement = v.Str()
isDatabase = true
case semconv25.AttributeDBName, semconv.AttributeDBNamespace, "db.instance", attributeDbElasticsearchClusterName:
db.Instance = stringval
isDatabase = true
case semconv.AttributeDBSystem, "db.type":
db.Type = stringval
isDatabase = true
case semconv25.AttributeDBUser: //removed after 1.25 (experimental)
db.UserName = stringval
isDatabase = true
// net.*
case semconv25.AttributeNetPeerName, "peer.hostname", semconv.AttributeServerAddress:
netPeerName = stringval
case semconv12.AttributeNetPeerIP, semconv25.AttributeNetSockPeerAddr, semconv.AttributeNetworkPeerAddress, "peer.ipv4", "peer.ipv6":
netPeerIP = stringval
case "peer.address":
peerAddress = stringval
case semconv.AttributeNetworkConnectionType:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Connection == nil {
event.Network.Connection = &modelpb.NetworkConnection{}
}
event.Network.Connection.Type = stringval
case semconv.AttributeNetworkConnectionSubtype:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Connection == nil {
event.Network.Connection = &modelpb.NetworkConnection{}
}
event.Network.Connection.Subtype = stringval
case semconv.AttributeNetworkCarrierMcc:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Carrier == nil {
event.Network.Carrier = &modelpb.NetworkCarrier{}
}
event.Network.Carrier.Mcc = stringval
case semconv.AttributeNetworkCarrierMnc:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Carrier == nil {
event.Network.Carrier = &modelpb.NetworkCarrier{}
}
event.Network.Carrier.Mnc = stringval
case semconv.AttributeNetworkCarrierName:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Carrier == nil {
event.Network.Carrier = &modelpb.NetworkCarrier{}
}
event.Network.Carrier.Name = stringval
case semconv.AttributeNetworkCarrierIcc:
if event.Network == nil {
event.Network = &modelpb.Network{}
}
if event.Network.Carrier == nil {
event.Network.Carrier = &modelpb.NetworkCarrier{}
}
event.Network.Carrier.Icc = stringval
// session.*
case "session.id":
if event.Session == nil {
event.Session = &modelpb.Session{}
}
event.Session.Id = stringval
// messaging.*
//
// messaging.destination is now called messaging.destination.name in the latest semconv
// https://opentelemetry.io/docs/specs/semconv/attributes-registry/messaging
// keep both of them for the backward compatibility
case semconv16.AttributeMessagingDestination, semconv.AttributeMessagingDestinationName, "message_bus.destination":
message.QueueName = stringval
isMessaging = true
case semconv25.AttributeMessagingOperation, semconv.AttributeMessagingOperationType:
messageOperation = stringval
isMessaging = true
case semconv.AttributeMessagingOperationName:
modelpb.Labels(event.Labels).Set(k, stringval)
isMessaging = true
case semconv.AttributeMessagingSystem:
messageSystem = stringval
isMessaging = true
// rpc.*
//
// TODO(axw) add RPC fieldset to ECS? Currently we drop these
// attributes, and rely on the operation name and span type/subtype
// like we do with Elastic APM agents.
case semconv.AttributeRPCSystem:
rpcSystem = stringval
isRPC = true
case semconv.AttributeRPCService:
rpcService = stringval
isRPC = true
case semconv.AttributeRPCGRPCStatusCode:
rpcSystem = "grpc"
isRPC = true
case semconv.AttributeRPCMethod:
case semconv.AttributeCodeStacktrace:
if event.Code == nil {
event.Code = &modelpb.Code{}
}
// stacktrace is expected to be large thus un-truncated value is needed
event.Code.Stacktrace = v.Str()
// gen_ai.*
case semconv.AttributeGenAiSystem:
genAiSystem = stringval
isGenAi = true
// miscellaneous
case "span.kind": // filter out
case semconv.AttributePeerService:
peerService = stringval
// data_stream.*
case attributeDataStreamDataset:
if event.DataStream == nil {
event.DataStream = &modelpb.DataStream{}
}
event.DataStream.Dataset = sanitizeDataStreamDataset(stringval)
case attributeDataStreamNamespace:
if event.DataStream == nil {
event.DataStream = &modelpb.DataStream{}
}
event.DataStream.Namespace = sanitizeDataStreamNamespace(stringval)
default:
setLabel(k, event, v)
}
default:
setLabel(k, event, v)
}
return true
})
if netPeerName == "" && (!strings.ContainsRune(peerAddress, ':') || net.ParseIP(peerAddress) != nil) {
// peer.address is not necessarily a hostname
// or IP address; it could be something like
// a JDBC connection string or ip:port. Ignore
// values containing colons, except for IPv6.
netPeerName = peerAddress
}
destPort := netPeerPort
destAddr := netPeerName
if destAddr == "" {
destAddr = netPeerIP
}
var fullURL *url.URL
if httpURL != "" {
fullURL, _ = url.Parse(httpURL)
} else if httpTarget != "" || httpURLPath != "" {
// Build http.url from http.scheme, http.target, etc.
// http.target was supported in older semconv versions, but was replaced by url.path and url.query
if httpTarget == "" {
httpTarget = httpURLPath
if httpURLQuery != "" {
httpTarget += "?" + httpURLQuery
}
}
if u, err := url.Parse(httpTarget); err == nil {
fullURL = u
fullURL.Scheme = httpScheme
if httpHost == "" {
// Set host from net.peer.*
httpHost = destAddr
if destPort > 0 {
httpHost = net.JoinHostPort(httpHost, strconv.Itoa(destPort))
}
}
fullURL.Host = httpHost
httpURL = fullURL.String()
}
}
if fullURL != nil {
var port int
portString := fullURL.Port()
if portString != "" {
port, _ = strconv.Atoi(portString)
} else {
port = schemeDefaultPort(fullURL.Scheme)
}
// Set destination.{address,port} from the HTTP URL,
// replacing peer.* based values to ensure consistency.
destAddr = truncate(fullURL.Hostname())
if port > 0 {
destPort = port
}
}
serviceTarget.Name = peerService
destinationService.Name = peerService
destinationService.Resource = peerService
if peerAddress != "" {
destinationService.Resource = peerAddress
}
if isHTTP {
if httpResponse.StatusCode > 0 && event.Event.Outcome == outcomeUnknown {
event.Event.Outcome = clientHTTPStatusCodeOutcome(int(httpResponse.StatusCode))
}
if http.SizeVT() != 0 {
event.Http = &http
}
if event.Url == nil {
event.Url = &modelpb.URL{}
}
event.Url.Original = httpURL
}
if isDatabase {
event.Span.Db = &db
}
if isMessaging {
event.Span.Message = &message
}
switch {
case isDatabase:
event.Span.Type = "db"
event.Span.Subtype = db.Type
serviceTarget.Type = event.Span.Type
if event.Span.Subtype != "" {
serviceTarget.Type = event.Span.Subtype
if destinationService.Name == "" {
// For database requests, we currently just identify
// the destination service by db.system.
destinationService.Name = event.Span.Subtype
destinationService.Resource = event.Span.Subtype
}
}
if db.Instance != "" {
serviceTarget.Name = db.Instance
}
case isMessaging:
event.Span.Type = "messaging"
event.Span.Subtype = messageSystem
if messageOperation == "" && spanKind == ptrace.SpanKindProducer {
messageOperation = "send"
}
event.Span.Action = messageOperation
serviceTarget.Type = event.Span.Type
if event.Span.Subtype != "" {
serviceTarget.Type = event.Span.Subtype
if destinationService.Name == "" {
destinationService.Name = event.Span.Subtype
destinationService.Resource = event.Span.Subtype
}
}
if destinationService.Resource != "" && message.QueueName != "" {
destinationService.Resource += "/" + message.QueueName
}
if message.QueueName != "" && !messageTempDestination {
serviceTarget.Name = message.QueueName
}
case isRPC:
event.Span.Type = "external"
event.Span.Subtype = rpcSystem
serviceTarget.Type = event.Span.Type
if event.Span.Subtype != "" {
serviceTarget.Type = event.Span.Subtype
}
// Set destination.service.* from the peer address, unless peer.service was specified.
if destinationService.Name == "" {
destHostPort := net.JoinHostPort(destAddr, strconv.Itoa(destPort))
destinationService.Name = destHostPort
destinationService.Resource = destHostPort
}
if rpcService != "" {
serviceTarget.Name = rpcService
}
case isHTTP:
event.Span.Type = "external"
subtype := "http"
event.Span.Subtype = subtype
serviceTarget.Type = event.Span.Subtype
if fullURL != nil {
url := url.URL{Scheme: fullURL.Scheme, Host: fullURL.Host}
resource := url.Host
if destPort == schemeDefaultPort(url.Scheme) {
if fullURL.Port() != "" {
// Remove the default port from destination.service.name
url.Host = destAddr
} else {
// Add the default port to destination.service.resource
resource = fmt.Sprintf("%s:%d", resource, destPort)
}
}
serviceTarget.Name = resource
if destinationService.Name == "" {
destinationService.Name = url.String()
destinationService.Resource = resource
}
}
case isGenAi:
event.Span.Type = "genai"
event.Span.Subtype = genAiSystem
serviceTarget.Type = event.Span.Type
default:
// Only set event.Span.Type if not already set
if event.Span.Type == "" {
switch spanKind {
case ptrace.SpanKindInternal:
event.Span.Type = "app"
event.Span.Subtype = "internal"
default:
event.Span.Type = "unknown"
}
}
}
if destAddr != "" {
event.Destination = &modelpb.Destination{
Address: destAddr,
Port: uint32(destPort),
}
}
if destinationService.SizeVT() != 0 {
if destinationService.Type == "" {
// Copy span type to destination.service.type.
destinationService.Type = event.Span.Type
}
event.Span.DestinationService = &destinationService
}
if serviceTarget.SizeVT() != 0 {
event.Service.Target = &serviceTarget
}
if samplerType != (pcommon.Value{}) {
// The client has reported its sampling rate, so we can use it to extrapolate transaction metrics.
parseSamplerAttributes(samplerType, samplerParam, event)
}
// if outcome is still not assigned, assign success
if event.Event.Outcome == outcomeUnknown {
event.Event.Outcome = outcomeSuccess
}
}