func TranslateSpan()

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
	}
}