Skip to content

Commit 9774af7

Browse files
committed
Document open-telemetry example
1 parent c941748 commit 9774af7

File tree

2 files changed

+143
-30
lines changed

2 files changed

+143
-30
lines changed

Examples/open-telemetry/README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# OpenTelemetry example
2+
3+
An example HTTP server that uses a Valkey client, both of which emit Distributed Tracing spans
4+
via [Swift OTel](https://github.com/swift-otel/swift-otel).
5+
6+
## Overview
7+
8+
This example bootstraps Swift OTel to export Distributed Tracing spans to Jaeger.
9+
10+
It then starts a Hummingbird HTTP server along with its associated middleware for instrumentation.
11+
12+
Finally, the server uses a Valkey client in its request handler to demonstrate the spans
13+
created by executing various Valkey commands.
14+
15+
## Testing
16+
17+
The example uses [Docker Compose](https://docs.docker.com/compose) to run a Valkey server alongside Jaeger to collect
18+
and visualize the spans from the HTTP server and Valkey client, which is running on your local machine.
19+
20+
### Running Valkey and Jaeger
21+
22+
In one terminal window, run the following command:
23+
24+
```console
25+
% docker compose up
26+
[+] Running 4/4
27+
✔ Network open-telemetry_default Created 0.0s
28+
✔ Volume "open-telemetry_valkey_data" Created 0.0s
29+
✔ Container open-telemetry-jaeger-1 Created 0.0s
30+
✔ Container open-telemetry-valkey-1 Created 0.0s
31+
...
32+
```
33+
34+
### Running the server
35+
36+
Now, in another terminal, run the server locally using the following command:
37+
38+
```console
39+
% swift run
40+
```
41+
42+
### Making some requests
43+
44+
Finally, in a third terminal, make a request to the server:
45+
46+
```console
47+
% curl http://localhost:8080/compute/42
48+
```
49+
50+
The example server fakes an expensive algorithm which is hard-coded to take a couple of seconds to complete.
51+
That's why the first request will take a decent amount of time.
52+
53+
Now, make the same request again:
54+
55+
```console
56+
% curl http://localhost:8080/compute/42
57+
```
58+
59+
You should see that it returns instantaniously. We successfully cached the previously computed value in Valkey
60+
and can now read it from the cache instead of re-computing it each time.
61+
62+
### Visualizing the traces using Jaeger UI
63+
64+
Visit Jaeger UI in your browser at [localhost:16686](http://localhost:16686).
65+
66+
Select `example` from the dropdown and click `Find Traces`.
67+
68+
You should see a handful of traces, including:
69+
70+
#### `/compute/{x}` with an execution time of ~ 3.2 seconds
71+
72+
This corresponds to the first request to `/42` where we had to compute the value. Click on this trace to reveal
73+
its spans. The root span represents our entire Hummingbird request handling. Nested inside are three child spans:
74+
75+
1. `HGET`: Shows the `HGET` Valkey command used to look up the cached value for `42`.
76+
2. `compute`: Represents our expensive algorithm. We can see that this takes up the majority of the entire trace.
77+
3. `HSET`: Shows the `HSET` Valkey command sent to store the computed value for future retrieval.
78+
79+
#### `/compute/{x}` with an execution time of a few milliseconds
80+
81+
This span corresponds to a subsequent request to `/42` where we could utelize our cache to avoid the
82+
expensive computation. Click on this trace to reveal its spans. Like before, the root span represents
83+
the Hummingbird request handling. We can also see a single child span:
84+
85+
1. `HGET`: Shows the `HGET` Valkey command used to look up the cached value for `42`.
86+
87+
### Making some more requests
88+
89+
The example also comes with a few more API endpoints to demonstrate other Valkey commands:
90+
91+
#### Pipelined commands
92+
93+
Send the following request to kick off multiple pipelined commands:
94+
95+
```console
96+
% curl http://localhost:8080/multi
97+
```
98+
99+
This will run three pipelined `EVAL` commands and produces a trace made up of the following spans:
100+
101+
1. `/multi`: The Hummingbird request handling.
102+
2. `MULTI`: The Valkey client span representing the execution of the pipelined commands.
103+
104+
Click on the `MULTI` span to reveal its attributes. New here are the following two attributes:
105+
106+
- `db.operation.batch.size`: This is set to `3` and represents the number of pipelined commands.
107+
- `db.operation.name`: This is set to `MULTI EVAL`, showing that the pipeline consists only of `EVAL` commands.
108+
109+
#### Failing commands
110+
111+
Send the following request to send some gibberish to Valkey resulting in an error:
112+
113+
```console
114+
% curl http://localhost:8080/error
115+
```
116+
117+
This will send an `EVAL` command with invalid script contents (`EVAL not a script`) resulting in a trace
118+
made up of the following spans:
119+
120+
1. `/error`: The Hummingbird request handling.
121+
2. `EVAL`: The Valkey client span representing the failed `EVAL` command.
122+
123+
Click on the `EVAL` span to reveal its attributes. New here are the following two attributes:
124+
125+
- `db.response.status_code`: This is set to `ERR` and represents the prefix of the simple error returned
126+
by Valkey.
127+
- `error`: This is set to `true` indicating that the operation failed. In Jaeger, this is additionally displayed
128+
via a red exclamation mark next to the span name.

Examples/open-telemetry/Sources/Example/Example.swift

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,7 @@ struct Example {
2020
router.add(middleware: TracingMiddleware())
2121
router.add(middleware: LogRequestsMiddleware(.info))
2222

23-
router.get("/:x") { _, context in
24-
/*
25-
This demonstrates the span created for pipelined commands where all commands are of the same type.
26-
The `db.operation.name` indicates that it's multiple `EVAL` commands,
27-
and `db.operation.batch.size` indicates the number of commands.
28-
*/
29-
_ = await valkeyClient.execute(
30-
EVAL(script: "return '1'"),
31-
EVAL(script: "return '2'"),
32-
EVAL(script: "return '3'")
33-
)
34-
35-
/*
36-
This demonstrates the span created for pipelined commands where the commands are of different types.
37-
The `db.operation.name` resorts to `MULTI`, and `db.operation.batch.size` indicates the number of commands.
38-
*/
39-
_ = await valkeyClient.execute(
40-
EVAL(script: "return '1'"),
41-
ACL.WHOAMI()
42-
)
43-
44-
// This demonstrates the span created for a failed command.
45-
_ = try? await valkeyClient.execute(EVAL(script: "💩"))
46-
47-
// This demonstrates the span created for a failed pipelined command.
48-
_ = await valkeyClient.execute(
49-
EVAL(script: "return 'ok'"),
50-
EVAL(script: "💩")
51-
)
52-
23+
router.get("/compute/:x") { _, context in
5324
let x = try context.parameters.require("x", as: Int.self)
5425

5526
func expensiveAlgorithm(_ x: Int) async throws -> Int {
@@ -71,6 +42,20 @@ struct Example {
7142
return ByteBuffer(string: "\(result)")
7243
}
7344

45+
router.get("/multi") { _, _ in
46+
_ = await valkeyClient.execute(
47+
EVAL(script: "return '1'"),
48+
EVAL(script: "return '2'"),
49+
EVAL(script: "return '3'")
50+
)
51+
return HTTPResponse.Status.ok
52+
}
53+
54+
router.get("/error") { _, _ in
55+
_ = try? await valkeyClient.eval(script: "not a script")
56+
return HTTPResponse.Status.ok
57+
}
58+
7459
var app = Application(router: router)
7560
app.addServices(observability)
7661
app.addServices(valkeyClient)

0 commit comments

Comments
 (0)