Skip to content

Commit 004a29d

Browse files
committed
added OTLP log exporter tests
1 parent 5500615 commit 004a29d

File tree

3 files changed

+233
-1
lines changed

3 files changed

+233
-1
lines changed

Examples/OTLP Exporter/main.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ let spanExporter = MultiSpanExporter(spanExporters: [otlpTraceExporter, stdoutEx
4040
let spanProcessor = SimpleSpanProcessor(spanExporter: spanExporter)
4141
OpenTelemetrySDK.instance.tracerProvider.addSpanProcessor(spanProcessor)
4242

43-
if #available(macOS 10.14, *) {
43+
if #available(macOS 10.14, *), #available(iOS 12.0, *) {
4444
OpenTelemetrySDK.instance.tracerProvider.addSpanProcessor(SignPostIntegration())
4545
}
4646

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// Copyright The OpenTelemetry Authors
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
import Foundation
7+
8+
9+
import Foundation
10+
import OpenTelemetryApi
11+
@testable import OpenTelemetryProtocolExporter
12+
@testable import OpenTelemetrySdk
13+
import XCTest
14+
15+
class LogRecordAdapterTests : XCTestCase {
16+
let traceIdBytes: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4]
17+
let spanIdBytes: [UInt8] = [0, 0, 0, 0, 4, 3, 2, 1]
18+
var traceId: TraceId!
19+
var spanId: SpanId!
20+
let tracestate = TraceState()
21+
var spanContext: SpanContext!
22+
23+
24+
override func setUp() {
25+
traceId = TraceId(fromBytes: traceIdBytes)
26+
spanId = SpanId(fromBytes: spanIdBytes)
27+
spanContext = SpanContext.create(traceId: traceId, spanId: spanId, traceFlags: TraceFlags(), traceState: tracestate)
28+
}
29+
30+
func testToResourceProto() {
31+
let logRecord = ReadableLogRecord(resource: Resource(), instrumentationScopeInfo: InstrumentationScopeInfo(name: "scope"), timestamp: Date(), attributes: ["event.name":AttributeValue.string("name"), "event.domain": AttributeValue.string("domain")])
32+
33+
let result = LogRecordAdapter.toProtoResourceRecordLog(logRecordList: [logRecord])
34+
35+
XCTAssertTrue(result[0].scopeLogs.count > 0)
36+
}
37+
38+
func testToProto() {
39+
let timestamp = Date()
40+
let logRecord = ReadableLogRecord(resource: Resource(),
41+
instrumentationScopeInfo: InstrumentationScopeInfo(name: "scope"),
42+
timestamp: timestamp,
43+
observedTimestamp: Date.distantPast,
44+
spanContext: spanContext,
45+
severity: .fatal,
46+
body: "Hello, world",
47+
attributes: ["event.name":AttributeValue.string("name"), "event.domain": AttributeValue.string("domain")])
48+
49+
let protoLog = LogRecordAdapter.toProtoLogRecord(logRecord: logRecord)
50+
51+
XCTAssertEqual(protoLog.body.stringValue, "Hello, world")
52+
XCTAssertEqual(protoLog.hasBody, true)
53+
XCTAssertEqual(protoLog.severityText, "FATAL")
54+
XCTAssertEqual(protoLog.observedTimeUnixNano, Date.distantPast.timeIntervalSince1970.toNanoseconds)
55+
XCTAssertEqual(protoLog.severityNumber.rawValue, Severity.fatal.rawValue)
56+
XCTAssertEqual(protoLog.spanID, Data(bytes: spanIdBytes, count: 8))
57+
XCTAssertEqual(protoLog.traceID, Data(bytes: traceIdBytes, count: 16))
58+
XCTAssertEqual(protoLog.timeUnixNano, timestamp.timeIntervalSince1970.toNanoseconds)
59+
XCTAssertEqual(protoLog.attributes.count, 2)
60+
}
61+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//
2+
// Copyright The OpenTelemetry Authors
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
import Foundation
7+
import Logging
8+
import GRPC
9+
import NIO
10+
import OpenTelemetryApi
11+
@testable import OpenTelemetryProtocolExporter
12+
@testable import OpenTelemetrySdk
13+
import XCTest
14+
15+
16+
class OtlpLogRecordExporterTests: XCTestCase {
17+
let traceIdBytes: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4]
18+
let spanIdBytes: [UInt8] = [0, 0, 0, 0, 4, 3, 2, 1]
19+
var traceId: TraceId!
20+
var spanId: SpanId!
21+
let tracestate = TraceState()
22+
var spanContext: SpanContext!
23+
24+
25+
26+
27+
var fakeCollector: FakeLogCollector!
28+
var server: EventLoopFuture<Server>!
29+
var channel: ClientConnection!
30+
31+
let channelGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
32+
let serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
33+
34+
func startServer() -> EventLoopFuture<Server> {
35+
// Start the server and print its address once it has started.
36+
let server = Server.insecure(group: serverGroup)
37+
.withServiceProviders([fakeCollector])
38+
.bind(host: "localhost", port: 4317)
39+
40+
server.map {
41+
$0.channel.localAddress
42+
}.whenSuccess { address in
43+
print("server started on port \(address!.port!)")
44+
}
45+
return server
46+
}
47+
48+
func startChannel() -> ClientConnection {
49+
let channel = ClientConnection.insecure(group: channelGroup)
50+
.connect(host: "localhost", port: 4317)
51+
return channel
52+
}
53+
54+
override func setUp() {
55+
fakeCollector = FakeLogCollector()
56+
server = startServer()
57+
channel = startChannel()
58+
traceId = TraceId(fromBytes: traceIdBytes)
59+
spanId = SpanId(fromBytes: spanIdBytes)
60+
spanContext = SpanContext.create(traceId: traceId, spanId: spanId, traceFlags: TraceFlags(), traceState: tracestate)
61+
62+
}
63+
64+
override func tearDown() {
65+
try! serverGroup.syncShutdownGracefully()
66+
try! channelGroup.syncShutdownGracefully()
67+
}
68+
69+
func testExport() {
70+
let logRecord = ReadableLogRecord(resource: Resource(),
71+
instrumentationScopeInfo: InstrumentationScopeInfo(name: "scope"),
72+
timestamp: Date(),
73+
observedTimestamp: Date.distantPast,
74+
spanContext: spanContext,
75+
severity: .fatal,
76+
body: "Hello, world",
77+
attributes: ["event.name":AttributeValue.string("name"), "event.domain": AttributeValue.string("domain")])
78+
79+
let exporter = OtlpLogExporter(channel: channel)
80+
let result = exporter.export(logRecords: [logRecord])
81+
XCTAssertEqual(result, ExportResult.success)
82+
XCTAssertEqual(fakeCollector.receivedLogs, LogRecordAdapter.toProtoResourceRecordLog(logRecordList: [logRecord]))
83+
exporter.shutdown()
84+
}
85+
86+
func testImplicitGrpcLoggingConfig() throws {
87+
let exporter = OtlpLogExporter(channel: channel)
88+
guard let logger = exporter.callOptions?.logger else {
89+
throw "Missing logger"
90+
}
91+
XCTAssertEqual(logger.label, "io.grpc")
92+
}
93+
func testExplicitGrpcLoggingConfig() throws {
94+
let exporter = OtlpLogExporter(channel: channel, logger: Logger(label: "my.grpc.logger"))
95+
guard let logger = exporter.callOptions?.logger else {
96+
throw "Missing logger"
97+
}
98+
XCTAssertEqual(logger.label, "my.grpc.logger")
99+
}
100+
101+
func testConfigHeadersIsNil_whenDefaultInitCalled() throws {
102+
let exporter = OtlpLogExporter(channel: channel)
103+
XCTAssertNil(exporter.config.headers)
104+
}
105+
106+
func testConfigHeadersAreSet_whenInitCalledWithCustomConfig() throws {
107+
let config: OtlpConfiguration = OtlpConfiguration(timeout: TimeInterval(10), headers: [("FOO", "BAR")])
108+
let exporter = OtlpLogExporter(channel: channel, config: config)
109+
XCTAssertNotNil(exporter.config.headers)
110+
XCTAssertEqual(exporter.config.headers?[0].0, "FOO")
111+
XCTAssertEqual(exporter.config.headers?[0].1, "BAR")
112+
XCTAssertEqual("BAR", exporter.callOptions?.customMetadata.first(name: "FOO"))
113+
}
114+
115+
func testConfigHeadersAreSet_whenInitCalledWithExplicitHeaders() throws {
116+
let exporter = OtlpLogExporter(channel: channel, envVarHeaders: [("FOO", "BAR")])
117+
XCTAssertNil(exporter.config.headers)
118+
XCTAssertEqual("BAR", exporter.callOptions?.customMetadata.first(name: "FOO"))
119+
}
120+
121+
func testExportAfterShutdown() {
122+
let logRecord = ReadableLogRecord(resource: Resource(),
123+
instrumentationScopeInfo: InstrumentationScopeInfo(name: "scope"),
124+
timestamp: Date(),
125+
observedTimestamp: Date.distantPast,
126+
spanContext: spanContext,
127+
severity: .fatal,
128+
body: "Hello, world",
129+
attributes: ["event.name":AttributeValue.string("name"), "event.domain": AttributeValue.string("domain")])
130+
let exporter = OtlpLogExporter(channel: channel)
131+
exporter.shutdown()
132+
let result = exporter.export(logRecords: [logRecord])
133+
XCTAssertEqual(result, ExportResult.failure)
134+
}
135+
136+
func testExportCancelled() {
137+
fakeCollector.returnedStatus = GRPCStatus(code: .cancelled, message: nil)
138+
let exporter = OtlpLogExporter(channel: channel)
139+
let logRecord = ReadableLogRecord(resource: Resource(),
140+
instrumentationScopeInfo: InstrumentationScopeInfo(name: "scope"),
141+
timestamp: Date(),
142+
observedTimestamp: Date.distantPast,
143+
spanContext: spanContext,
144+
severity: .fatal,
145+
body: "Hello, world",
146+
attributes: ["event.name":AttributeValue.string("name"),
147+
"event.domain": AttributeValue.string("domain")])
148+
let result = exporter.export(logRecords: [logRecord])
149+
XCTAssertEqual(result, ExportResult.failure)
150+
exporter.shutdown()
151+
}
152+
153+
}
154+
155+
156+
157+
class FakeLogCollector: Opentelemetry_Proto_Collector_Logs_V1_LogsServiceProvider {
158+
var interceptors: OpenTelemetryProtocolExporter.Opentelemetry_Proto_Collector_Logs_V1_LogsServiceServerInterceptorFactoryProtocol?
159+
160+
func export(request: OpenTelemetryProtocolExporter.Opentelemetry_Proto_Collector_Logs_V1_ExportLogsServiceRequest, context: GRPC.StatusOnlyCallContext) -> NIOCore.EventLoopFuture<OpenTelemetryProtocolExporter.Opentelemetry_Proto_Collector_Logs_V1_ExportLogsServiceResponse> {
161+
receivedLogs.append(contentsOf: request.resourceLogs)
162+
if returnedStatus != GRPCStatus.ok {
163+
return context.eventLoop.makeFailedFuture(returnedStatus)
164+
}
165+
return context.eventLoop.makeSucceededFuture(Opentelemetry_Proto_Collector_Logs_V1_ExportLogsServiceResponse())
166+
}
167+
168+
var receivedLogs = [Opentelemetry_Proto_Logs_V1_ResourceLogs]()
169+
var returnedStatus = GRPCStatus.ok
170+
171+
}

0 commit comments

Comments
 (0)