diff --git a/Package.swift b/Package.swift index 7bb43a26..15a43e33 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ let package = Package( .library(name: "DatadogExporter", type: .static, targets: ["DatadogExporter"]), .library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]), .library(name: "OTelSwiftLog", type: .static, targets: ["OTelSwiftLog"]), + .library(name: "DataCompression", type: .static, targets: ["DataCompression"]), .executable(name: "simpleExporter", targets: ["SimpleExporter"]), .executable(name: "OTLPExporter", targets: ["OTLPExporter"]), .executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]), @@ -110,6 +111,9 @@ let package = Package( "OpenTelemetryProtocolExporterCommon", .product(name: "GRPC", package: "grpc-swift")], path: "Sources/Exporters/OpenTelemetryProtocolGrpc"), + .target(name: "DataCompression", + dependencies: [], + path: "Sources/Exporters/DataCompression"), .target(name: "StdoutExporter", dependencies: ["OpenTelemetrySdk"], path: "Sources/Exporters/Stdout"), @@ -117,7 +121,8 @@ let package = Package( dependencies: ["OpenTelemetrySdk"], path: "Sources/Exporters/InMemory"), .target(name: "DatadogExporter", - dependencies: ["OpenTelemetrySdk"], + dependencies: ["OpenTelemetrySdk", + "DataCompression"], path: "Sources/Exporters/DatadogExporter", exclude: ["NOTICE", "README.md"]), .target(name: "PersistenceExporter", @@ -163,6 +168,7 @@ let package = Package( .testTarget(name: "OpenTelemetryProtocolExporterTests", dependencies: ["OpenTelemetryProtocolExporterGrpc", "OpenTelemetryProtocolExporterHttp", + "DataCompression", .product(name: "NIO", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOTestUtils", package: "swift-nio")], @@ -190,7 +196,7 @@ let package = Package( path: "Examples/OTLP Exporter", exclude: ["README.md"]), .target(name: "OTLPHTTPExporter", - dependencies: ["OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"], + dependencies: ["OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration", "DataCompression"], path: "Examples/OTLP HTTP Exporter", exclude: ["README.md"]), .target(name: "PrometheusSample", diff --git a/Package@swift-5.6.swift b/Package@swift-5.6.swift index 64fd9aca..2d25e584 100644 --- a/Package@swift-5.6.swift +++ b/Package@swift-5.6.swift @@ -30,6 +30,7 @@ let package = Package( .library(name: "DatadogExporter", type: .static, targets: ["DatadogExporter"]), .library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]), .library(name: "OTelSwiftLog", type: .static, targets: ["OTelSwiftLog"]), + .library(name: "DataCompression", type: .static, targets: ["DataCompression"]), .executable(name: "simpleExporter", targets: ["SimpleExporter"]), .executable(name: "OTLPExporter", targets: ["OTLPExporter"]), .executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]), @@ -115,6 +116,9 @@ let package = Package( "OpenTelemetryProtocolExporterCommon", .product(name: "GRPC", package: "grpc-swift")], path: "Sources/Exporters/OpenTelemetryProtocolGrpc"), + .target(name: "DataCompression", + dependencies: [], + path: "Sources/Exporters/DataCompression"), .target(name: "StdoutExporter", dependencies: ["OpenTelemetrySdk"], path: "Sources/Exporters/Stdout"), @@ -122,7 +126,8 @@ let package = Package( dependencies: ["OpenTelemetrySdk"], path: "Sources/Exporters/InMemory"), .target(name: "DatadogExporter", - dependencies: ["OpenTelemetrySdk"], + dependencies: ["OpenTelemetrySdk", + "DataCompression"], path: "Sources/Exporters/DatadogExporter", exclude: ["NOTICE", "README.md"]), .target(name: "PersistenceExporter", @@ -173,6 +178,7 @@ let package = Package( .testTarget(name: "OpenTelemetryProtocolExporterTests", dependencies: ["OpenTelemetryProtocolExporterGrpc", "OpenTelemetryProtocolExporterHttp", + "DataCompression", .product(name: "NIO", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOTestUtils", package: "swift-nio")], @@ -207,7 +213,7 @@ let package = Package( ), .executableTarget( name: "OTLPHTTPExporter", - dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"], + dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration", "DataCompression"], path: "Examples/OTLP HTTP Exporter", exclude: ["README.md"] ), diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index e15fa60c..49ff7747 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -23,6 +23,7 @@ let package = Package( .library(name: "PersistenceExporter", targets: ["PersistenceExporter"]), .library(name: "InMemoryExporter", targets: ["InMemoryExporter"]), .library(name: "OTelSwiftLog", targets: ["OTelSwiftLog"]), + .library(name: "DataCompression", type: .static, targets: ["DataCompression"]), .executable(name: "ConcurrencyContext", targets: ["ConcurrencyContext"]), .executable(name: "loggingTracer", targets: ["LoggingTracer"]), ], @@ -70,6 +71,9 @@ let package = Package( "OpenTelemetryProtocolExporterCommon", .product(name: "GRPC", package: "grpc-swift")], path: "Sources/Exporters/OpenTelemetryProtocolGrpc"), + .target(name: "DataCompression", + dependencies: [], + path: "Sources/Exporters/DataCompression"), .target(name: "StdoutExporter", dependencies: ["OpenTelemetrySdk"], path: "Sources/Exporters/Stdout"), @@ -100,6 +104,7 @@ let package = Package( .testTarget(name: "OpenTelemetryProtocolExporterTests", dependencies: ["OpenTelemetryProtocolExporterGrpc", "OpenTelemetryProtocolExporterHttp", + "DataCompression", .product(name: "NIO", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOTestUtils", package: "swift-nio")], @@ -240,7 +245,7 @@ extension Package { ), .executableTarget( name: "OTLPHTTPExporter", - dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"], + dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporterHttp", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration", "DataCompression"], path: "Examples/OTLP HTTP Exporter", exclude: ["README.md"] ), @@ -256,7 +261,8 @@ extension Package { dependencies: ["ResourceExtension", "OpenTelemetrySdk"], path: "Tests/InstrumentationTests/SDKResourceExtensionTests"), .target(name: "DatadogExporter", - dependencies: ["OpenTelemetrySdk"], + dependencies: ["OpenTelemetrySdk", + "DataCompression"], path: "Sources/Exporters/DatadogExporter", exclude: ["NOTICE", "README.md"]), .testTarget(name: "DatadogExporterTests", diff --git a/Sources/Exporters/DatadogExporter/Utils/DataCompression/DataCompression.swift b/Sources/Exporters/DataCompression/DataCompression.swift similarity index 99% rename from Sources/Exporters/DatadogExporter/Utils/DataCompression/DataCompression.swift rename to Sources/Exporters/DataCompression/DataCompression.swift index 07c5a2f1..5289ec96 100644 --- a/Sources/Exporters/DatadogExporter/Utils/DataCompression/DataCompression.swift +++ b/Sources/Exporters/DataCompression/DataCompression.swift @@ -28,9 +28,11 @@ import Foundation + +#if canImport(Compression) import Compression -extension Data +public extension Data { /// Compresses the data. /// - parameter withAlgorithm: Compression algorithm to use. See the `CompressionAlgorithm` type @@ -247,7 +249,7 @@ extension Data /// Calculate the Adler32 checksum of the data. /// - returns: Adler32 checksum type. Can still be further advanced. - func adler32() -> Adler32 + internal func adler32() -> Adler32 { var res = Adler32() res.advance(withChunk: self) @@ -256,7 +258,7 @@ extension Data /// Calculate the Crc32 checksum of the data. /// - returns: Crc32 checksum type. Can still be further advanced. - func crc32() -> Crc32 + internal func crc32() -> Crc32 { var res = Crc32() res.advance(withChunk: self) @@ -514,3 +516,4 @@ fileprivate func perform(_ config: Config, source: UnsafePointer, sourceS } } } +#endif diff --git a/Sources/Exporters/DatadogExporter/Upload/RequestBuilder.swift b/Sources/Exporters/DatadogExporter/Upload/RequestBuilder.swift index b88c8536..8b686746 100644 --- a/Sources/Exporters/DatadogExporter/Upload/RequestBuilder.swift +++ b/Sources/Exporters/DatadogExporter/Upload/RequestBuilder.swift @@ -3,6 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +#if canImport(Compression) +import DataCompression +#endif import Foundation /// Builds `URLRequest` for sending data to Datadog. diff --git a/Sources/Exporters/OpenTelemetryProtocolCommon/common/OtlpConfiguration.swift b/Sources/Exporters/OpenTelemetryProtocolCommon/common/OtlpConfiguration.swift index 32863848..6a0f715a 100644 --- a/Sources/Exporters/OpenTelemetryProtocolCommon/common/OtlpConfiguration.swift +++ b/Sources/Exporters/OpenTelemetryProtocolCommon/common/OtlpConfiguration.swift @@ -5,6 +5,12 @@ import Foundation +public enum CompressionType { + case gzip + case deflate + case none +} + public struct OtlpConfiguration { public static let DefaultTimeoutInterval : TimeInterval = TimeInterval(10) @@ -23,9 +29,15 @@ public struct OtlpConfiguration { // let compression public let headers : [(String,String)]? public let timeout : TimeInterval + public let compression: CompressionType - public init(timeout : TimeInterval = OtlpConfiguration.DefaultTimeoutInterval, headers: [(String,String)]? = nil) { + public init( + timeout : TimeInterval = OtlpConfiguration.DefaultTimeoutInterval, + compression: CompressionType = .gzip, + headers: [(String,String)]? = nil + ) { self.headers = headers self.timeout = timeout + self.compression = compression } } diff --git a/Sources/Exporters/OpenTelemetryProtocolHttp/OtlpHttpExporterBase.swift b/Sources/Exporters/OpenTelemetryProtocolHttp/OtlpHttpExporterBase.swift index d1471964..99c7fa1b 100644 --- a/Sources/Exporters/OpenTelemetryProtocolHttp/OtlpHttpExporterBase.swift +++ b/Sources/Exporters/OpenTelemetryProtocolHttp/OtlpHttpExporterBase.swift @@ -3,6 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 // +#if canImport(Compression) +import DataCompression +#endif import Foundation import SwiftProtobuf import OpenTelemetryProtocolExporterCommon @@ -28,20 +31,44 @@ public class OtlpHttpExporterBase { } } - public func createRequest(body: Message, endpoint: URL) -> URLRequest { - var request = URLRequest(url: endpoint) - - do { - request.httpMethod = "POST" - request.httpBody = try body.serializedData() - request.setValue(Headers.getUserAgentHeader(), forHTTPHeaderField: Constants.HTTP.userAgent) - request.setValue("application/x-protobuf", forHTTPHeaderField: "Content-Type") - } catch { - print("Error serializing body: \(error)") + public func createRequest(body: Message, endpoint: URL) -> URLRequest { + var request = URLRequest(url: endpoint) + + do { + let rawData = try body.serializedData() + request.httpMethod = "POST" + request.setValue(Headers.getUserAgentHeader(), forHTTPHeaderField: Constants.HTTP.userAgent) + request.setValue("application/x-protobuf", forHTTPHeaderField: "Content-Type") + + var compressedData = rawData + +#if canImport(Compression) + switch config.compression { + case .gzip: + if let data = rawData.gzip() { + compressedData = data + request.setValue("gzip", forHTTPHeaderField: "Content-Encoding") + } + + case .deflate: + if let data = rawData.deflate() { + compressedData = data + request.setValue("deflate", forHTTPHeaderField: "Content-Encoding") + } + + case .none: + break + } +#endif + // Apply final data. Could be compressed or raw + // but it doesn't matter here + request.httpBody = compressedData + } catch { + print("Error serializing body: \(error)") + } + + return request } - - return request - } public func shutdown(explicitTimeout: TimeInterval? = nil) { diff --git a/Sources/Exporters/OpenTelemetryProtocolHttp/StableOtlpHTTPExporterBase.swift b/Sources/Exporters/OpenTelemetryProtocolHttp/StableOtlpHTTPExporterBase.swift index 2b7db4f9..fe19eb9c 100644 --- a/Sources/Exporters/OpenTelemetryProtocolHttp/StableOtlpHTTPExporterBase.swift +++ b/Sources/Exporters/OpenTelemetryProtocolHttp/StableOtlpHTTPExporterBase.swift @@ -3,6 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 // +#if canImport(Compression) +import DataCompression +#endif import Foundation import OpenTelemetryProtocolExporterCommon import SwiftProtobuf @@ -44,14 +47,38 @@ public class StableOtlpHTTPExporterBase { } do { + let rawData = try body.serializedData() request.httpMethod = "POST" - request.httpBody = try body.serializedData() request.setValue(Headers.getUserAgentHeader(), forHTTPHeaderField: Constants.HTTP.userAgent) request.setValue("application/x-protobuf", forHTTPHeaderField: "Content-Type") + + var compressedData = rawData + +#if canImport(Compression) + switch config.compression { + case .gzip: + if let data = rawData.gzip() { + compressedData = data + request.setValue("gzip", forHTTPHeaderField: "Content-Encoding") + } + + case .deflate: + if let data = rawData.deflate() { + compressedData = data + request.setValue("deflate", forHTTPHeaderField: "Content-Encoding") + } + + case .none: + break + } +#endif + + // Apply final data. Could be compressed or raw + // but it doesn't matter here + request.httpBody = compressedData } catch { print("Error serializing body: \(error)") } - return request } diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpExporterBaseTests.swift b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpExporterBaseTests.swift new file mode 100644 index 00000000..53f84cdb --- /dev/null +++ b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpExporterBaseTests.swift @@ -0,0 +1,132 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +#if canImport(Compression) +import DataCompression +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import Logging +import NIO +import NIOHTTP1 +import NIOTestUtils +import OpenTelemetryApi +import OpenTelemetryProtocolExporterCommon +@testable import OpenTelemetryProtocolExporterHttp +@testable import OpenTelemetrySdk +import XCTest + +class OtlpHttpExporterBaseTests: XCTestCase { + + var exporter: OtlpHttpExporterBase! + var spans: [SpanData] = [] + + override func setUp() { + super.setUp() + + spans = [] + let endpointName1 = "/api/foo" + String(Int.random(in: 1...100)) + let endpointName2 = "/api/bar" + String(Int.random(in: 100...500)) + spans.append(generateFakeSpan(endpointName: endpointName1)) + spans.append(generateFakeSpan(endpointName: endpointName2)) + } + + // Test for .gzip compression + func testCreateRequestWithGzipCompression() { + let config = OtlpConfiguration(compression: .gzip) + + exporter = OtlpHttpExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + /// gzip + let data = try! body.serializedData().gzip() + + // Verify Content-Encoding header is set to "gzip" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), "gzip") + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data!.count) + } + + // Test for .deflate compression + func testCreateRequestWithDeflateCompression() { + let config = OtlpConfiguration(compression: .deflate) + + exporter = OtlpHttpExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + /// deflate + let data = try! body.serializedData().deflate() + + // Verify Content-Encoding header is set to "deflate" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), "deflate") + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data!.count) + } + + // Test for .none compression (no compression) + func testCreateRequestWithNoCompression() { + let config = OtlpConfiguration(compression: .none) + + exporter = OtlpHttpExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + let data = try! body.serializedData() + + // Verify Content-Encoding header is set to "deflate" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), nil) + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data.count) + } + + private func generateFakeSpan(endpointName: String = "/api/endpoint") -> SpanData { + let duration = 0.9 + let start = Date() + let end = start.addingTimeInterval(duration) + let testattributes: [String: AttributeValue] = ["foo": AttributeValue("bar")!, "fizz": AttributeValue("buzz")!] + + var testData = SpanData(traceId: TraceId.random(), + spanId: SpanId.random(), + name: "GET " + endpointName, + kind: SpanKind.server, + startTime: start, + endTime: end, + totalAttributeCount: 2) + testData.settingAttributes(testattributes) + testData.settingTotalAttributeCount(2) + testData.settingHasEnded(true) + testData.settingTotalRecordedEvents(0) + testData.settingLinks([SpanData.Link]()) + testData.settingTotalRecordedLinks(0) + testData.settingStatus(.ok) + + return testData + } +} +#endif diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpLogRecordExporterTests.swift b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpLogRecordExporterTests.swift index a46cb3b1..be36cf66 100644 --- a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpLogRecordExporterTests.swift +++ b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpLogRecordExporterTests.swift @@ -45,7 +45,7 @@ class OtlpHttpLogRecordExporterTests: XCTestCase { attributes: ["event.name":AttributeValue.string("name"), "event.domain": AttributeValue.string("domain")]) let endpoint = URL(string: "http://localhost:\(testServer.serverPort)")! - let exporter = OtlpHttpLogExporter(endpoint: endpoint) + let exporter = OtlpHttpLogExporter(endpoint: endpoint, config: .init(compression: .none)) let _ = exporter.export(logRecords: [logRecord]) // TODO: Use protobuf to verify that we have received the correct Log records diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpMetricsExporterTest.swift b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpMetricsExporterTest.swift index 852fbd82..b9ca07c1 100644 --- a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpMetricsExporterTest.swift +++ b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpMetricsExporterTest.swift @@ -50,7 +50,7 @@ class OtlpHttpMetricsExporterTest: XCTestCase { } let endpoint = URL(string: "http://localhost:\(testServer.serverPort)")! - let exporter = OtlpHttpMetricExporter(endpoint: endpoint) + let exporter = OtlpHttpMetricExporter(endpoint: endpoint, config: .init(compression: .none)) let result = exporter.export(metrics: metrics) { () -> Bool in false } @@ -83,7 +83,7 @@ class OtlpHttpMetricsExporterTest: XCTestCase { } let endpoint = URL(string: "http://localhost:\(testServer.serverPort)")! - let exporter = OtlpHttpMetricExporter(endpoint: endpoint) + let exporter = OtlpHttpMetricExporter(endpoint: endpoint, config: .init(compression: .none)) let result = exporter.export(metrics: metrics) { () -> Bool in false diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpTraceExporterTests.swift b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpTraceExporterTests.swift index b88403b4..982593da 100644 --- a/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpTraceExporterTests.swift +++ b/Tests/ExportersTests/OpenTelemetryProtocol/OtlpHttpTraceExporterTests.swift @@ -37,7 +37,7 @@ class OtlpHttpTraceExporterTests: XCTestCase { // It should ideally turn that body into [SpanData] using protobuf and then confirm content func testExport() { let endpoint = URL(string: "http://localhost:\(testServer.serverPort)/v1/traces")! - let exporter = OtlpHttpTraceExporter(endpoint: endpoint) + let exporter = OtlpHttpTraceExporter(endpoint: endpoint, config: .init(compression: .none)) var spans: [SpanData] = [] let endpointName1 = "/api/foo" + String(Int.random(in: 1...100)) diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPExporterBaseTests.swift b/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPExporterBaseTests.swift new file mode 100644 index 00000000..2d687929 --- /dev/null +++ b/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPExporterBaseTests.swift @@ -0,0 +1,133 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +#if canImport(Compression) +import DataCompression + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import Logging +import NIO +import NIOHTTP1 +import NIOTestUtils +import OpenTelemetryApi +import OpenTelemetryProtocolExporterCommon +@testable import OpenTelemetryProtocolExporterHttp +@testable import OpenTelemetrySdk +import XCTest + +class StableOtlpHTTPExporterBaseTests: XCTestCase { + + var exporter: StableOtlpHTTPExporterBase! + var spans: [SpanData] = [] + + override func setUp() { + super.setUp() + + spans = [] + let endpointName1 = "/api/foo" + String(Int.random(in: 1...100)) + let endpointName2 = "/api/bar" + String(Int.random(in: 100...500)) + spans.append(generateFakeSpan(endpointName: endpointName1)) + spans.append(generateFakeSpan(endpointName: endpointName2)) + } + + // Test for .gzip compression + func testCreateRequestWithGzipCompression() { + let config = OtlpConfiguration(compression: .gzip) + + exporter = StableOtlpHTTPExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + /// gzip + let data = try! body.serializedData().gzip() + + // Verify Content-Encoding header is set to "gzip" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), "gzip") + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data!.count) + } + + // Test for .deflate compression + func testCreateRequestWithDeflateCompression() { + let config = OtlpConfiguration(compression: .deflate) + + exporter = StableOtlpHTTPExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + /// deflate + let data = try! body.serializedData().deflate() + + // Verify Content-Encoding header is set to "deflate" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), "deflate") + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data!.count) + } + + // Test for .none compression (no compression) + func testCreateRequestWithNoCompression() { + let config = OtlpConfiguration(compression: .none) + + exporter = StableOtlpHTTPExporterBase( + endpoint: URL(string: "http://example.com")!, + config: config + ) + + let body = Opentelemetry_Proto_Collector_Trace_V1_ExportTraceServiceRequest.with { + $0.resourceSpans = SpanAdapter.toProtoResourceSpans(spanDataList: spans) + } + + let request = exporter.createRequest(body: body, endpoint: URL(string: "http://example.com")!) + + let data = try! body.serializedData() + + // Verify Content-Encoding header is set to "deflate" + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Encoding"), nil) + XCTAssertNotNil(request.httpBody) + XCTAssertEqual(request.httpBody!.count, data.count) + } + + private func generateFakeSpan(endpointName: String = "/api/endpoint") -> SpanData { + let duration = 0.9 + let start = Date() + let end = start.addingTimeInterval(duration) + let testattributes: [String: AttributeValue] = ["foo": AttributeValue("bar")!, "fizz": AttributeValue("buzz")!] + + var testData = SpanData(traceId: TraceId.random(), + spanId: SpanId.random(), + name: "GET " + endpointName, + kind: SpanKind.server, + startTime: start, + endTime: end, + totalAttributeCount: 2) + testData.settingAttributes(testattributes) + testData.settingTotalAttributeCount(2) + testData.settingHasEnded(true) + testData.settingTotalRecordedEvents(0) + testData.settingLinks([SpanData.Link]()) + testData.settingTotalRecordedLinks(0) + testData.settingStatus(.ok) + + return testData + } +} +#endif diff --git a/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPMetricsExporterTest.swift b/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPMetricsExporterTest.swift index 9566a89c..201cd12c 100644 --- a/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPMetricsExporterTest.swift +++ b/Tests/ExportersTests/OpenTelemetryProtocol/StableOtlpHTTPMetricsExporterTest.swift @@ -63,7 +63,7 @@ class StableOtlpHttpMetricsExporterTest: XCTestCase { } let endpoint = URL(string: "http://localhost:\(testServer.serverPort)")! - let exporter = StableOtlpHTTPMetricExporter(endpoint: endpoint) + let exporter = StableOtlpHTTPMetricExporter(endpoint: endpoint, config: .init(compression: .none)) let result = exporter.export(metrics: metrics) XCTAssertEqual(result, ExportResult.success) @@ -95,7 +95,7 @@ class StableOtlpHttpMetricsExporterTest: XCTestCase { } let endpoint = URL(string: "http://localhost:\(testServer.serverPort)")! - let exporter = StableOtlpHTTPMetricExporter(endpoint: endpoint) + let exporter = StableOtlpHTTPMetricExporter(endpoint: endpoint, config: .init(compression: .none)) let result = exporter.export(metrics: metrics) XCTAssertEqual(result, ExportResult.success)