Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUM-1836 feat(otel-tracer): support multi level tags using key flattening #1626

Merged
Merged
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
12 changes: 12 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; };
3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; };
3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; };
3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */; };
3CC6AD192B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */; };
3CC6AD1D2B4F07FA00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */; };
3CC6AD1E2B4F07FB00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */; };
3CCCA5C42ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; };
3CCCA5C52ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; };
3CCCA5C72ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; };
Expand Down Expand Up @@ -1920,6 +1924,8 @@
3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = "<group>"; };
3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTaskDelegate+Tracking.swift"; sourceTree = "<group>"; };
3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTask+Tracking.swift"; sourceTree = "<group>"; };
3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+Datadog.swift"; sourceTree = "<group>"; };
3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+DatadogTests.swift"; sourceTree = "<group>"; };
3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DDURLSessionInstrumentation+objc.swift"; sourceTree = "<group>"; };
3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionInstrumentationConfigurationTests.swift; sourceTree = "<group>"; };
3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -3114,6 +3120,7 @@
3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = {
isa = PBXGroup;
children = (
3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */,
3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */,
3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */,
3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */,
Expand All @@ -3127,6 +3134,7 @@
3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */ = {
isa = PBXGroup;
children = (
3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */,
3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */,
3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */,
3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */,
Expand Down Expand Up @@ -8324,6 +8332,7 @@
files = (
61A2CC3C2A44BED30000FF25 /* Tracer.swift in Sources */,
D2C1A50229C4C4CB00946C31 /* Casting.swift in Sources */,
3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */,
D2C1A50C29C4C4CB00946C31 /* DDNoOps.swift in Sources */,
D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */,
D2C1A50D29C4C4CB00946C31 /* SpanTagsReducer.swift in Sources */,
Expand Down Expand Up @@ -8366,6 +8375,7 @@
buildActionMask = 2147483647;
files = (
D2C1A51E29C4C75700946C31 /* Casting+Tracing.swift in Sources */,
3CC6AD1D2B4F07FA00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */,
D2C1A52429C4C75700946C31 /* TracingURLSessionHandlerTests.swift in Sources */,
619CE75E2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */,
D2C1A52329C4C75700946C31 /* WarningsTests.swift in Sources */,
Expand Down Expand Up @@ -8522,6 +8532,7 @@
files = (
61A2CC3D2A44BED30000FF25 /* Tracer.swift in Sources */,
D2C1A53829C4F2DF00946C31 /* Casting.swift in Sources */,
3CC6AD192B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */,
D2C1A53929C4F2DF00946C31 /* DDNoOps.swift in Sources */,
D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */,
D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */,
Expand Down Expand Up @@ -8564,6 +8575,7 @@
buildActionMask = 2147483647;
files = (
D2C1A55F29C4F2E800946C31 /* Casting+Tracing.swift in Sources */,
3CC6AD1E2B4F07FB00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */,
D2C1A56029C4F2E800946C31 /* TracingURLSessionHandlerTests.swift in Sources */,
619CE75F2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */,
D2C1A56129C4F2E800946C31 /* WarningsTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import Foundation
import OpenTelemetryApi

extension Dictionary where Key == String, Value == OpenTelemetryApi.AttributeValue {
/// Converts OpenTelemetry attributes to Datadog tags. This method is recursive
/// and will flatten nested attributes. Collection attributes are flattened to multiple
/// tags with `key.index` naming convention. If attribute value is an empty collection,
/// it will be converted to empty string.
var tags: [String: String] {
var tags: [String: String] = [:]
for (key, value) in self {
switch value {
case .bool(let value):
tags[key] = value.description
case .string(let value):
tags[key] = value
case .int(let value):
tags[key] = value.description
case .double(let value):
tags[key] = value.description
case .stringArray(let array):
if array.isEmpty {
tags[key] = ""
} else {
for (index, element) in array.enumerated() {
tags["\(key).\(index)"] = element
}
}
case .boolArray(let array):
if array.isEmpty {
tags[key] = ""
} else {
for (index, element) in array.enumerated() {
tags["\(key).\(index)"] = element.description
}
}
case .intArray(let array):
if array.isEmpty {
tags[key] = ""
} else {
for (index, element) in array.enumerated() {
tags["\(key).\(index)"] = element.description
}
}
case .doubleArray(let array):
if array.isEmpty {
tags[key] = ""
} else {
for (index, element) in array.enumerated() {
tags["\(key).\(index)"] = element.description
}
}
case .set(let set):
if set.labels.tags.isEmpty {
tags[key] = ""
} else {
for (nestedKey, nestedValue) in set.labels.tags {
tags["\(key).\(nestedKey)"] = nestedValue
}
}
}
}
return tags
}
}
31 changes: 1 addition & 30 deletions DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ internal class OTelSpan: OpenTelemetryApi.Span {
return
}
isRecording = false
tags = makeTags()
tags = attributes.tags
}

// if the span was already ended before, we don't want to end it again
Expand All @@ -136,35 +136,6 @@ internal class OTelSpan: OpenTelemetryApi.Span {
ddSpan.finish(at: time)
}

private func makeTags() -> [String: String] {
var tags = [String: String]()
for (key, value) in attributes {
switch value {
case .string(let value):
tags[key] = value
case .bool(let value):
tags[key] = value.description
case .int(let value):
tags[key] = value.description
case .double(let value):
tags[key] = value.description
// swiftlint:disable unavailable_function
case .stringArray:
fatalError("Not implemented yet")
case .boolArray:
fatalError("Not implemented yet")
case .intArray:
fatalError("Not implemented yet")
case .doubleArray:
fatalError("Not implemented yet")
case .set:
fatalError("Not implemented yet")
// swiftlint:enable unavailable_function
}
}
return tags
}

var description: String {
return "OTelSpan"
}
Expand Down
131 changes: 131 additions & 0 deletions DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import XCTest
import TestUtilities
import DatadogInternal
import OpenTelemetryApi

@testable import DatadogTrace

final class OTelAttributeValueDatadogTests: XCTestCase {
func testTags_givenMultipleLevelsAttributes() {
// Given
let attributes = makeAttributes(level: 3)

// When
let tags = attributes.tags

let expectedTags =
[
"key3-0": "true",
"key3-1": "value1",
"key3-2": "2",
"key3-3": "3.0",
"key3-4.0": "value4",
"key3-4.1": "value5",
"key3-5.0": "true",
"key3-5.1": "false",
"key3-6.0": "7",
"key3-6.1": "8",
"key3-7.0": "7.0",
"key3-7.1": "8.0",
"key3-8.key2-0": "true",
"key3-8.key2-1": "value1",
"key3-8.key2-2": "2",
"key3-8.key2-3": "3.0",
"key3-8.key2-4.0": "value4",
"key3-8.key2-4.1": "value5",
"key3-8.key2-5.0": "true",
"key3-8.key2-5.1": "false",
"key3-8.key2-6.0": "7",
"key3-8.key2-6.1": "8",
"key3-8.key2-7.0": "7.0",
"key3-8.key2-7.1": "8.0",
"key3-8.key2-8.key1-0": "true",
"key3-8.key2-8.key1-1": "value1",
"key3-8.key2-8.key1-2": "2",
"key3-8.key2-8.key1-3": "3.0",
"key3-8.key2-8.key1-4.0": "value4",
"key3-8.key2-8.key1-4.1": "value5",
"key3-8.key2-8.key1-5.0": "true",
"key3-8.key2-8.key1-5.1": "false",
"key3-8.key2-8.key1-6.0": "7",
"key3-8.key2-8.key1-6.1": "8",
"key3-8.key2-8.key1-7.0": "7.0",
"key3-8.key2-8.key1-7.1": "8.0",
"key3-8.key2-8.key1-8": "" // when recursion ends, empty string is returned
]

// Then
DDAssertDictionariesEqual(expectedTags, tags)
}

func testTags_givenOneLevelAttributesWithEmptyCollections() {
// Given
let attributes: [String: OpenTelemetryApi.AttributeValue] = [
"key1": .bool(true),
"key2": .string("value1"),
"key3": .int(2),
"key4": .double(3.0),
"key5": .stringArray(["value5", "value6"]),
"key6": .boolArray([true, false]),
"key7": .intArray([7, 8]),
"key8": .doubleArray([8.0, 9.0]),
"key9": .set(.init(labels: [:])),
"key10": .stringArray([]),
"key11": .boolArray([]),
"key12": .intArray([]),
"key13": .doubleArray([]),
]

// When
let tags = attributes.tags

// Then
let expectedTags =
[
"key1": "true",
"key2": "value1",
"key3": "2",
"key4": "3.0",
"key5.0": "value5",
"key5.1": "value6",
"key6.0": "true",
"key6.1": "false",
"key7.0": "7",
"key7.1": "8",
"key8.0": "8.0",
"key8.1": "9.0",
"key9": "",
"key10": "",
"key11": "",
"key12": "",
"key13": "",
]
DDAssertDictionariesEqual(expectedTags, tags)
}

// MARK: - Helpers

func makeAttributes(level: UInt) -> [String: OpenTelemetryApi.AttributeValue] {
guard level > 0 else {
return [:]
}

return [
"key\(level)-0": .bool(true),
"key\(level)-1": .string("value1"),
"key\(level)-2": .int(2),
"key\(level)-3": .double(3.0),
"key\(level)-4": .stringArray(["value4", "value5"]),
"key\(level)-5": .boolArray([true, false]),
"key\(level)-6": .intArray([7, 8]),
"key\(level)-7": .doubleArray([7.0, 8.0]),
"key\(level)-8": .set(.init(labels: makeAttributes(level: level - 1)))
]
}
}