Skip to content

Commit da32860

Browse files
author
Ignacio Bonafonte
authored
Merge pull request #376 from kevinearls/add-otlp-http-exporter
Experimental: Add a JSON/HTTP Opentelemetry Protocol (OTLP) exporter
2 parents fce5c32 + 7c31864 commit da32860

File tree

17 files changed

+877
-2
lines changed

17 files changed

+877
-2
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
### OTLP Exporter Example
2+
3+
This example shows how to use [OTLP HTTP Exporter](https://github.com/open-telemetry/opentelemetry-swift/tree/main/Sources/Exporters/OpenTelemetryProtocol) to instrument a simple Swift application.
4+
5+
This example will export spans data using [OTLP HTTP Exporter ](https://github.com/open-telemetry/opentelemetry-swift/tree/main/Sources/Exporters/OpenTelemetryProtocol). It will use [proto format](https://github.com/open-telemetry/opentelemetry-proto).
6+
7+
8+
## Run the Application
9+
10+
1. Run docker: This will start otel-collector, Zipkin and Prometheus
11+
12+
```shell script
13+
# from this directory
14+
docker-compose up
15+
```
16+
17+
2. Run app
18+
19+
```shell script
20+
# from this directory
21+
swift run OTLPHTTPExporter
22+
```
23+
24+
3. Teardown the docker images
25+
26+
```shell script
27+
# from this directory
28+
docker-compose down
29+
```
30+
31+
4. Open page at <http://localhost:9411/zipkin/> - you should be able to see the spans in zipkin
32+
![Screenshot of the running example](images/zipkin-spans.png)
33+
34+
### Prometheus UI
35+
36+
The prometheus client will be available at <http://localhost:9090>.
37+
38+
Note: It may take some time for the application metrics to appear on the Prometheus dashboard.
39+
![Screenshot of the running example](images/prometheus-metrics.png)
40+
41+
5. If you don't set service.name as per https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md the default name of the service and spans generate by the OTLP Exporter is `unknown_service:otlpexporter` You can either set the service.name by editing the schema in Xcode and the set the environment variable for OTEL_RESOURCE_ATTRIBUTES, or set it via command line:
42+
43+
```shell script
44+
# from this directory
45+
OTEL_RESOURCE_ATTRIBUTES="service.name=my-swift-app,service.version=v1.2.3" swift run OTLPExporter
46+
```
47+
This will create a service and spans with the name `my-swift-app`
48+
49+
## Useful links
50+
51+
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
52+
- For more information on trace, visit: <https://github.com/open-telemetry/opentelemetry-swift/tree/main/Sources/OpenTelemetrySdk/Trace>
53+
- For more information on metrics, visit: <https://github.com/open-telemetry/opentelemetry-swift/tree/main/Sources/OpenTelemetrySdk/Metrics>
54+
55+
## LICENSE
56+
57+
Apache License 2.0
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
grpc:
5+
http:
6+
cors:
7+
allowed_origins:
8+
- http://*
9+
- https://*
10+
11+
exporters:
12+
zipkin:
13+
endpoint: "http://zipkin-all-in-one:9411/api/v2/spans"
14+
prometheus:
15+
endpoint: "0.0.0.0:9464"
16+
17+
processors:
18+
resource:
19+
attributes:
20+
- key: service.name
21+
value: OTLP Exporter
22+
action: insert
23+
batch:
24+
25+
service:
26+
telemetry:
27+
logs:
28+
level: "debug"
29+
pipelines:
30+
traces:
31+
receivers: [otlp]
32+
exporters: [zipkin]
33+
processors: [resource, batch]
34+
metrics:
35+
receivers: [otlp]
36+
exporters: [prometheus]
37+
processors: [resource, batch]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
version: "3"
2+
services:
3+
# Collector
4+
collector:
5+
image: otel/opentelemetry-collector:latest
6+
# The latest image of the otel-collector may not work, so specifying the version that works with this release
7+
# image: otel/opentelemetry-collector:latest
8+
command: ["--config=/conf/collector-config.yaml"]
9+
volumes:
10+
- ./collector-config.yaml:/conf/collector-config.yaml
11+
ports:
12+
- "9464:9464"
13+
- "4317:4317"
14+
- "4318:4318"
15+
- "55681:55681"
16+
depends_on:
17+
- zipkin-all-in-one
18+
19+
# Zipkin
20+
zipkin-all-in-one:
21+
image: openzipkin/zipkin:latest
22+
ports:
23+
- "9411:9411"
24+
25+
# Prometheus
26+
prometheus:
27+
container_name: prometheus
28+
image: prom/prometheus:latest
29+
volumes:
30+
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
31+
ports:
32+
- "9090:9090"
33+
# - "9184:9184"
472 KB
Loading
567 KB
Loading
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#if os(macOS)
7+
8+
import Foundation
9+
import GRPC
10+
import NIO
11+
import NIOSSL
12+
import OpenTelemetryApi
13+
import OpenTelemetryProtocolExporter
14+
import OpenTelemetrySdk
15+
import ResourceExtension
16+
import SignPostIntegration
17+
import StdoutExporter
18+
import ZipkinExporter
19+
20+
let sampleKey = "sampleKey"
21+
let sampleValue = "sampleValue"
22+
23+
var resources = DefaultResources().get()
24+
25+
let instrumentationScopeName = "OTLPHTTPExporter"
26+
let instrumentationScopeVersion = "semver:0.1.0"
27+
28+
let otlpHttpTraceExporter = OtlpHttpTraceExporter()
29+
let stdoutExporter = StdoutExporter()
30+
let spanExporter = MultiSpanExporter(spanExporters: [otlpHttpTraceExporter, stdoutExporter])
31+
32+
let spanProcessor = SimpleSpanProcessor(spanExporter: spanExporter)
33+
OpenTelemetry.registerTracerProvider(tracerProvider:
34+
TracerProviderBuilder()
35+
.add(spanProcessor: spanProcessor)
36+
.build()
37+
)
38+
39+
let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: instrumentationScopeName, instrumentationVersion: instrumentationScopeVersion)
40+
41+
if #available(macOS 10.14, *), #available(iOS 12.0, *) {
42+
let tracerProviderSDK = OpenTelemetry.instance.tracerProvider as? TracerProviderSdk
43+
tracerProviderSDK?.addSpanProcessor(SignPostIntegration())
44+
}
45+
46+
func createSpans() {
47+
let parentSpan1 = tracer.spanBuilder(spanName: "Main").setSpanKind(spanKind: .client).startSpan()
48+
parentSpan1.setAttribute(key: sampleKey, value: sampleValue)
49+
OpenTelemetry.instance.contextProvider.setActiveSpan(parentSpan1)
50+
for _ in 1...3 {
51+
doWork()
52+
}
53+
Thread.sleep(forTimeInterval: 0.5)
54+
55+
let parentSpan2 = tracer.spanBuilder(spanName: "Another").setSpanKind(spanKind: .client).setActive(true).startSpan()
56+
parentSpan2.setAttribute(key: sampleKey, value: sampleValue)
57+
// do more Work
58+
for _ in 1...3 {
59+
doWork()
60+
}
61+
Thread.sleep(forTimeInterval: 0.5)
62+
63+
parentSpan2.end()
64+
parentSpan1.end()
65+
}
66+
67+
func doWork() {
68+
let childSpan = tracer.spanBuilder(spanName: "doWork").setSpanKind(spanKind: .client).startSpan()
69+
childSpan.setAttribute(key: sampleKey, value: sampleValue)
70+
Thread.sleep(forTimeInterval: Double.random(in: 0 ..< 10) / 100)
71+
childSpan.end()
72+
}
73+
74+
// Create a Parent span (Main) and do some Work (child Spans). Repeat for another Span.
75+
createSpans()
76+
77+
// Metrics
78+
let otlpHttpMetricExporter = OtlpHttpMetricExporter()
79+
let processor = MetricProcessorSdk()
80+
let meterProvider = MeterProviderSdk(metricProcessor: processor, metricExporter: otlpHttpMetricExporter, metricPushInterval: 0.1)
81+
82+
OpenTelemetry.registerMeterProvider(meterProvider: meterProvider)
83+
84+
85+
var meter = meterProvider.get(instrumentationName: "otlp_example_meter'")
86+
var exampleCounter = meter.createIntCounter(name: "otlp_example_counter")
87+
var exampleMeasure = meter.createIntMeasure(name: "otlp_example_measure")
88+
var exampleObserver = meter.createIntObserver(name: "otlp_example_observation") { observer in
89+
var taskInfo = mach_task_basic_info()
90+
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
91+
let _: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
92+
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
93+
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
94+
}
95+
}
96+
labels1 = ["dim1": "value1"]
97+
observer.observe(value: Int(taskInfo.resident_size), labels: labels1)
98+
}
99+
100+
var labels1 = ["dim1": "value1"]
101+
for _ in 1...3000 {
102+
exampleCounter.add(value: 1, labelset: meter.getLabelSet(labels: labels1))
103+
exampleMeasure.record(value: 100, labelset: meter.getLabelSet(labels: labels1))
104+
exampleMeasure.record(value: 500, labelset: meter.getLabelSet(labels: labels1))
105+
exampleMeasure.record(value: 5, labelset: meter.getLabelSet(labels: labels1))
106+
exampleMeasure.record(value: 750, labelset: meter.getLabelSet(labels: labels1))
107+
sleep(1)
108+
}
109+
110+
#endif
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
global:
2+
scrape_interval: 15s # Default is every 1 minute.
3+
4+
scrape_configs:
5+
- job_name: 'collector'
6+
# metrics_path defaults to '/metrics'
7+
# scheme defaults to 'http'.
8+
static_configs:
9+
- targets: ['collector:9464']

Package.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ let package = Package(
2929
.library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]),
3030
.executable(name: "simpleExporter", targets: ["SimpleExporter"]),
3131
.executable(name: "OTLPExporter", targets: ["OTLPExporter"]),
32+
.executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]),
3233
.executable(name: "loggingTracer", targets: ["LoggingTracer"]),
3334
],
3435
dependencies: [
@@ -138,7 +139,10 @@ let package = Package(
138139
dependencies: ["PrometheusExporter"],
139140
path: "Tests/ExportersTests/Prometheus"),
140141
.testTarget(name: "OpenTelemetryProtocolExporterTests",
141-
dependencies: ["OpenTelemetryProtocolExporter"],
142+
dependencies: ["OpenTelemetryProtocolExporter",
143+
.product(name: "NIO", package: "swift-nio"),
144+
.product(name: "NIOHTTP1", package: "swift-nio"),
145+
.product(name: "NIOTestUtils", package: "swift-nio")],
142146
path: "Tests/ExportersTests/OpenTelemetryProtocol"),
143147
.testTarget(name: "InMemoryExporterTests",
144148
dependencies: ["InMemoryExporter"],
@@ -162,6 +166,10 @@ let package = Package(
162166
dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporter", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
163167
path: "Examples/OTLP Exporter",
164168
exclude: ["README.md"]),
169+
.target(name: "OTLPHTTPExporter",
170+
dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporter", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
171+
path: "Examples/OTLP HTTP Exporter",
172+
exclude: ["README.md"]),
165173
.target(name: "PrometheusSample",
166174
dependencies: ["OpenTelemetrySdk", "PrometheusExporter"],
167175
path: "Examples/Prometheus Sample",

[email protected]

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let package = Package(
3030
.library(name: "NetworkStatus", type: .static, targets: ["NetworkStatus"]),
3131
.executable(name: "simpleExporter", targets: ["SimpleExporter"]),
3232
.executable(name: "OTLPExporter", targets: ["OTLPExporter"]),
33+
.executable(name: "OTLPHTTPExporter", targets: ["OTLPHTTPExporter"]),
3334
.executable(name: "loggingTracer", targets: ["LoggingTracer"]),
3435
],
3536
dependencies: [
@@ -146,7 +147,10 @@ let package = Package(
146147
dependencies: ["PrometheusExporter"],
147148
path: "Tests/ExportersTests/Prometheus"),
148149
.testTarget(name: "OpenTelemetryProtocolExporterTests",
149-
dependencies: ["OpenTelemetryProtocolExporter"],
150+
dependencies: ["OpenTelemetryProtocolExporter",
151+
.product(name: "NIO", package: "swift-nio"),
152+
.product(name: "NIOHTTP1", package: "swift-nio"),
153+
.product(name: "NIOTestUtils", package: "swift-nio")],
150154
path: "Tests/ExportersTests/OpenTelemetryProtocol"),
151155
.testTarget(name: "InMemoryExporterTests",
152156
dependencies: ["InMemoryExporter"],
@@ -176,6 +180,12 @@ let package = Package(
176180
path: "Examples/OTLP Exporter",
177181
exclude: ["README.md"]
178182
),
183+
.executableTarget(
184+
name: "OTLPHTTPExporter",
185+
dependencies: ["OpenTelemetrySdk", "OpenTelemetryProtocolExporter", "StdoutExporter", "ZipkinExporter", "ResourceExtension", "SignPostIntegration"],
186+
path: "Examples/OTLP HTTP Exporter",
187+
exclude: ["README.md"]
188+
),
179189
.executableTarget(
180190
name: "PrometheusSample",
181191
dependencies: ["OpenTelemetrySdk", "PrometheusExporter"],
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Copyright The OpenTelemetry Authors
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
import Foundation
7+
import SwiftProtobuf
8+
9+
public class OtlpHttpExporterBase {
10+
let endpoint: URL
11+
let httpClient: HTTPClient
12+
13+
public init(endpoint: URL, useSession: URLSession? = nil) {
14+
self.endpoint = endpoint
15+
if let providedSession = useSession {
16+
self.httpClient = HTTPClient(session: providedSession)
17+
} else {
18+
self.httpClient = HTTPClient()
19+
}
20+
}
21+
22+
public func createRequest(body: Message, endpoint: URL) -> URLRequest {
23+
var request = URLRequest(url: endpoint)
24+
25+
do {
26+
request.httpMethod = "POST"
27+
request.httpBody = try body.serializedData()
28+
request.setValue("application/x-protobuf", forHTTPHeaderField: "Content-Type")
29+
} catch {
30+
print("Error serializing body: \(error)")
31+
}
32+
33+
return request
34+
}
35+
36+
public func shutdown() {
37+
}
38+
}

0 commit comments

Comments
 (0)