Skip to content

Commit

Permalink
(Partially) Support secure gRPC channel setup (swift-otel#130)
Browse files Browse the repository at this point in the history
Co-authored-by: Moritz Lang <16192401+slashmo@users.noreply.github.com>
  • Loading branch information
t089 and slashmo authored Dec 5, 2024
1 parent 250b68e commit c9c5df0
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 44 deletions.
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.4.1"),
.package(url: "https://github.com/slashmo/swift-w3c-trace-context.git", exact: "1.0.0-beta.3"),
Expand Down Expand Up @@ -104,6 +105,7 @@ let package = Package(
dependencies: [
.target(name: "OTLPGRPC"),
.target(name: "OTelTesting"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
],
swiftSettings: sharedSwiftSettings
),
Expand Down
46 changes: 33 additions & 13 deletions Sources/OTLPGRPC/Metrics/OTLPGRPCMetricExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,53 @@ public final class OTLPGRPCMetricExporter: OTelMetricExporter {
private let client: Opentelemetry_Proto_Collector_Metrics_V1_MetricsServiceAsyncClient
private let logger = Logger(label: String(describing: OTLPGRPCMetricExporter.self))

public init(
public convenience init(
configuration: OTLPGRPCMetricExporterConfiguration,
group: any EventLoopGroup = MultiThreadedEventLoopGroup.singleton,
requestLogger: Logger = ._otelDisabled,
backgroundActivityLogger: Logger = ._otelDisabled
) {
self.configuration = configuration

var connectionConfiguration = ClientConnection.Configuration.default(
target: .host(configuration.endpoint.host, port: configuration.endpoint.port),
eventLoopGroup: group
self.init(
configuration: configuration,
group: group,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger,
trustRoots: .default
)
}

init(
configuration: OTLPGRPCMetricExporterConfiguration,
group: any EventLoopGroup,
requestLogger: Logger,
backgroundActivityLogger: Logger,
trustRoots: NIOSSLTrustRoots
) {
self.configuration = configuration

if configuration.endpoint.isInsecure {
logger.debug("Using insecure connection.", metadata: [
"host": "\(configuration.endpoint.host)",
"port": "\(configuration.endpoint.port)",
])
connection = ClientConnection.insecure(group: group)
.withBackgroundActivityLogger(backgroundActivityLogger)
.connect(host: configuration.endpoint.host, port: configuration.endpoint.port)
} else {
logger.debug("Using secure connection.", metadata: [
"host": "\(configuration.endpoint.host)",
"port": "\(configuration.endpoint.port)",
])
connection = ClientConnection
.usingPlatformAppropriateTLS(for: group)
.withTLS(trustRoots: trustRoots)
.withBackgroundActivityLogger(backgroundActivityLogger)
// TODO: Support OTEL_EXPORTER_OTLP_CERTIFICATE
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_KEY
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE
.connect(host: configuration.endpoint.host, port: configuration.endpoint.port)
}

// TODO: Support OTEL_EXPORTER_OTLP_CERTIFICATE
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_KEY
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE

var headers = configuration.headers
if !headers.isEmpty {
logger.trace("Configured custom request headers.", metadata: [
Expand All @@ -58,9 +81,6 @@ public final class OTLPGRPCMetricExporter: OTelMetricExporter {
}
headers.replaceOrAdd(name: "user-agent", value: "OTel-OTLP-Exporter-Swift/\(OTelLibrary.version)")

connectionConfiguration.backgroundActivityLogger = backgroundActivityLogger
connection = ClientConnection(configuration: connectionConfiguration)

client = Opentelemetry_Proto_Collector_Metrics_V1_MetricsServiceAsyncClient(
channel: connection,
defaultCallOptions: .init(customMetadata: headers, logger: requestLogger)
Expand Down
48 changes: 34 additions & 14 deletions Sources/OTLPGRPC/Tracing/OTLPGRPCSpanExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,60 @@ public final class OTLPGRPCSpanExporter: OTelSpanExporter {
private let client: Opentelemetry_Proto_Collector_Trace_V1_TraceServiceAsyncClient
private let logger = Logger(label: String(describing: OTLPGRPCSpanExporter.self))

/// Create a OTLP gRPC span exporter.
/// Create an OTLP gRPC span exporter.
///
/// - Parameters:
/// - configuration: The exporters configuration.
/// - group: The NIO event loop group to run the exporter in.
/// - requestLogger: Logs info about the underlying gRPC requests. Defaults to disabled, i.e. not emitting any logs.
/// - backgroundActivityLogger: Logs info about the underlying gRPC connection. Defaults to disabled, i.e. not emitting any logs.
public init(
public convenience init(
configuration: OTLPGRPCSpanExporterConfiguration,
group: any EventLoopGroup = MultiThreadedEventLoopGroup.singleton,
requestLogger: Logger = ._otelDisabled,
backgroundActivityLogger: Logger = ._otelDisabled
) {
self.configuration = configuration

var connectionConfiguration = ClientConnection.Configuration.default(
target: .host(configuration.endpoint.host, port: configuration.endpoint.port),
eventLoopGroup: group
self.init(
configuration: configuration,
group: group,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger,
trustRoots: .default
)
}

init(
configuration: OTLPGRPCSpanExporterConfiguration,
group: any EventLoopGroup,
requestLogger: Logger,
backgroundActivityLogger: Logger,
trustRoots: NIOSSLTrustRoots
) {
self.configuration = configuration

if configuration.endpoint.isInsecure {
logger.debug("Using insecure connection.", metadata: [
"host": "\(configuration.endpoint.host)",
"port": "\(configuration.endpoint.port)",
])
connection = ClientConnection.insecure(group: group)
.withBackgroundActivityLogger(backgroundActivityLogger)
.connect(host: configuration.endpoint.host, port: configuration.endpoint.port)
} else {
logger.debug("Using secure connection.", metadata: [
"host": "\(configuration.endpoint.host)",
"port": "\(configuration.endpoint.port)",
])
connection = ClientConnection
.usingPlatformAppropriateTLS(for: group)
.withTLS(trustRoots: trustRoots)
.withBackgroundActivityLogger(backgroundActivityLogger)
// TODO: Support OTEL_EXPORTER_OTLP_CERTIFICATE
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_KEY
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE
.connect(host: configuration.endpoint.host, port: configuration.endpoint.port)
}

// TODO: Support OTEL_EXPORTER_OTLP_CERTIFICATE
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_KEY
// TODO: Support OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE

var headers = configuration.headers
if !headers.isEmpty {
logger.trace("Configured custom request headers.", metadata: [
Expand All @@ -67,9 +90,6 @@ public final class OTLPGRPCSpanExporter: OTelSpanExporter {
}
headers.replaceOrAdd(name: "user-agent", value: "OTel-OTLP-Exporter-Swift/\(OTelLibrary.version)")

connectionConfiguration.backgroundActivityLogger = backgroundActivityLogger
connection = ClientConnection(configuration: connectionConfiguration)

client = Opentelemetry_Proto_Collector_Trace_V1_TraceServiceAsyncClient(
channel: connection,
defaultCallOptions: .init(customMetadata: headers, logger: requestLogger)
Expand Down
62 changes: 54 additions & 8 deletions Tests/OTLPGRPCTests/Metrics/OTLPGRPCMetricExporterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,60 @@
//===----------------------------------------------------------------------===//

@testable import Logging
import NIO
@testable import OTel
import OTLPCore
import OTLPGRPC
@testable import OTLPGRPC
import XCTest

final class OTLPGRPCMetricExporterTests: XCTestCase {
private var requestLogger: Logger!
private var backgroundActivityLogger: Logger!

override func setUp() async throws {
LoggingSystem.bootstrapInternal(logLevel: .trace)
requestLogger = Logger(label: "requestLogger")
backgroundActivityLogger = Logger(label: "backgroundActivityLogger")
}

func test_export_whenConnected_sendsExportRequestToCollector() async throws {
func test_export_whenConnected_withInsecureConnection_sendsExportRequestToCollector() async throws {
let collector = OTLPGRPCMockCollector()

try await collector.withServer { endpoint in
try await collector.withInsecureServer { endpoint in
let configuration = try OTLPGRPCMetricExporterConfiguration(environment: [:], endpoint: endpoint)
let exporter = OTLPGRPCMetricExporter(configuration: configuration)
let exporter = OTLPGRPCMetricExporter(
configuration: configuration,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger
)

let metrics = OTelResourceMetrics(scopeMetrics: [])
try await exporter.export([metrics])

await exporter.shutdown()
}

XCTAssertEqual(collector.metricsProvider.requests.count, 1)
let request = try XCTUnwrap(collector.metricsProvider.requests.first)

XCTAssertEqual(
request.headers.first(name: "user-agent"),
"OTel-OTLP-Exporter-Swift/\(OTelLibrary.version)"
)
}

func test_export_whenConnected_withSecureConnection_sendsExportRequestToCollector() async throws {
let collector = OTLPGRPCMockCollector()

try await collector.withSecureServer { endpoint, trustRoots in
let configuration = try OTLPGRPCMetricExporterConfiguration(environment: [:], endpoint: endpoint)
let exporter = OTLPGRPCMetricExporter(
configuration: configuration,
group: MultiThreadedEventLoopGroup.singleton,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger,
trustRoots: trustRoots
)

let metrics = OTelResourceMetrics(scopeMetrics: [])
try await exporter.export([metrics])
Expand Down Expand Up @@ -74,7 +112,7 @@ final class OTLPGRPCMetricExporterTests: XCTestCase {
]
)

try await collector.withServer { endpoint in
try await collector.withInsecureServer { endpoint in
let configuration = try OTLPGRPCMetricExporterConfiguration(
environment: [:],
endpoint: endpoint,
Expand All @@ -83,7 +121,11 @@ final class OTLPGRPCMetricExporterTests: XCTestCase {
"key2": "84",
]
)
let exporter = OTLPGRPCMetricExporter(configuration: configuration)
let exporter = OTLPGRPCMetricExporter(
configuration: configuration,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger
)

try await exporter.export([resourceMetricsToExport])

Expand Down Expand Up @@ -120,9 +162,13 @@ final class OTLPGRPCMetricExporterTests: XCTestCase {
let collector = OTLPGRPCMockCollector()
let errorCaught = expectation(description: "Caught expected error")
do {
try await collector.withServer { endpoint in
try await collector.withInsecureServer { endpoint in
let configuration = try OTLPGRPCMetricExporterConfiguration(environment: [:], endpoint: endpoint)
let exporter = OTLPGRPCMetricExporter(configuration: configuration)
let exporter = OTLPGRPCMetricExporter(
configuration: configuration,
requestLogger: requestLogger,
backgroundActivityLogger: backgroundActivityLogger
)
await exporter.shutdown()

let metrics = OTelResourceMetrics(scopeMetrics: [])
Expand Down
Loading

0 comments on commit c9c5df0

Please sign in to comment.