Skip to content

Commit 5eaa7c5

Browse files
Add OpenTelemetry Sample (#61)
* copy over chad's commit * add test and update readme * rename module * Update README.md Co-authored-by: Chad Retz <chad@temporal.io> --------- Co-authored-by: Chad Retz <chad@temporal.io>
1 parent 689d332 commit 5eaa7c5

11 files changed

Lines changed: 306 additions & 0 deletions

File tree

Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ group :encryption do
2424
gem 'rackup'
2525
gem 'sinatra'
2626
end
27+
28+
group :opentelemetry do
29+
gem 'opentelemetry-api'
30+
gem 'opentelemetry-exporter-otlp'
31+
gem 'opentelemetry-sdk'
32+
end

Gemfile.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ GEM
6262
oj (3.16.10)
6363
bigdecimal (>= 3.0)
6464
ostruct (>= 0.2)
65+
opentelemetry-api (1.7.0)
66+
opentelemetry-common (0.22.0)
67+
opentelemetry-api (~> 1.0)
68+
opentelemetry-exporter-otlp (0.30.0)
69+
google-protobuf (>= 3.18)
70+
googleapis-common-protos-types (~> 1.3)
71+
opentelemetry-api (~> 1.1)
72+
opentelemetry-common (~> 0.20)
73+
opentelemetry-sdk (~> 1.2)
74+
opentelemetry-semantic_conventions
75+
opentelemetry-registry (0.4.0)
76+
opentelemetry-api (~> 1.1)
77+
opentelemetry-sdk (1.9.0)
78+
opentelemetry-api (~> 1.1)
79+
opentelemetry-common (~> 0.20)
80+
opentelemetry-registry (~> 0.2)
81+
opentelemetry-semantic_conventions
82+
opentelemetry-semantic_conventions (1.36.0)
83+
opentelemetry-api (~> 1.0)
6584
ostruct (0.6.1)
6685
parallel (1.26.3)
6786
parser (3.3.6.0)
@@ -134,6 +153,9 @@ PLATFORMS
134153

135154
DEPENDENCIES
136155
minitest
156+
opentelemetry-api
157+
opentelemetry-exporter-otlp
158+
opentelemetry-sdk
137159
puma
138160
rackup
139161
rake

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Prerequisites:
2727
* [encryption](encryption) - Demonstrates how to make a codec for end-to-end encryption.
2828
* [env_config](env_config) - Load client configuration from TOML files with programmatic overrides.
2929
* [message_passing_simple](message_passing_simple) - Simple workflow that accepts signals, queries, and updates.
30+
* [open_telemetry](open_telemetry) - Demonstrates how to use OpenTelemetry tracing and metrics with the Ruby SDK
3031
* [patching](patching) - Demonstrates how to safely alter a workflow.
3132
* [polling/frequent](polling/frequent) - Implement a frequent polling mechanism inside an Activity.
3233
* [polling/infrequent](polling/infrequent) - Implement an infrequent polling mechanism using Temporal's automatic Activity Retry feature.

open_telemetry/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# OpenTelemetry Sample
2+
3+
Demonstrates how to use OpenTelemetry tracing and metrics with the Ruby SDK
4+
5+
## How to Run
6+
7+
First, in another terminal start up a Grafana OpenTelemetry instance which will
8+
collect telemetry and provide the Grafana UI for viewing the data.
9+
10+
```bash
11+
docker compose up
12+
```
13+
14+
In another terminal, start the worker
15+
16+
```bash
17+
bundle exec ruby worker.rb
18+
```
19+
20+
Finally start the workflow
21+
22+
```bash
23+
bundle exec ruby starter.rb
24+
```
25+
26+
You should be able to see the result in the terminal.
27+
28+
To view the Grafana dashboard go to `http://localhost:3000`
29+
30+
You can find the trace by clicking on the "Explore" tab,
31+
selecting "Tempo" as the data source, and switching the query type to "Search".
32+
33+
There will be a trace for `my-service` containing the workflow trace.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
require 'temporalio/activity'
4+
5+
module OpenTelemetry
6+
class ComposeGreetingActivity < Temporalio::Activity::Definition
7+
def initialize(tracer)
8+
@tracer = tracer
9+
end
10+
11+
def execute(name)
12+
# Capture start time for histogram metric later
13+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14+
15+
# Run activity in our own span. Most users will not need to create their own spans in activities, they will just
16+
# rely on the default spans implicitly created. This is just a sample to show it can be done.
17+
@tracer.in_span('my-activity-span', attributes: { 'my-group-attr' => 'simple-activities' }) do
18+
# Sleep for a second, then return
19+
sleep(1)
20+
"Hello, #{name}!"
21+
ensure
22+
# Custom metrics can be created inside activities
23+
Temporalio::Activity::Context.current.metric_meter
24+
.create_metric(:histogram, 'my-activity-histogram', value_type: :duration)
25+
.with_additional_attributes({ 'my-group-attr' => 'simple-activities' })
26+
.record(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time)
27+
end
28+
end
29+
end
30+
end

open_telemetry/docker-compose.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
grafana-dashboard:
3+
image: grafana/otel-lgtm:latest
4+
tty: true
5+
ports:
6+
- 3000:3000
7+
- 4317:4317
8+
- 4318:4318
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
require 'temporalio/workflow'
4+
require 'temporalio/contrib/open_telemetry'
5+
require_relative 'compose_greeting_activity'
6+
7+
module OpenTelemetry
8+
class GreetingWorkflow < Temporalio::Workflow::Definition
9+
def initialize
10+
# Custom metrics can be created inside workflows. Most users will not need to create custom metrics inside
11+
# workflows, this just shows that it can be done.
12+
@my_workflow_counter = Temporalio::Workflow.metric_meter.create_metric(:counter, 'my-workflow-counter')
13+
.with_additional_attributes({ 'my-group-attr' => 'simple-workflows' })
14+
end
15+
16+
def execute(name)
17+
# Increment our custom metric
18+
@my_workflow_counter.record(35)
19+
20+
# We can create a span in the workflow too. This is just an example to show this can be done, most users will not
21+
# create spans in workflows but rather rely on the defaults.
22+
#
23+
# This span is completed as soon as created because OpenTelemetry doesn't support spans that may have to be
24+
# completed on different machines. The span will be parented to the outer workflow span. Whether the outer span is
25+
# the "StartWorkflow" from the client or the "RunWorkflow" where it first ran depends on if this is replayed
26+
# separately from where it started. See the Ruby SDK README for more details.
27+
Temporalio::Contrib::OpenTelemetry::Workflow.with_completed_span(
28+
'my-workflow-span',
29+
attributes: { 'my-group-attr' => 'simple-workflows' }
30+
) do
31+
# The span will be the parent of the span created here to start the activity
32+
Temporalio::Workflow.execute_activity(
33+
ComposeGreetingActivity,
34+
name, # Activity argument
35+
start_to_close_timeout: 5 * 60 # 5 minutes
36+
)
37+
end
38+
end
39+
end
40+
end

open_telemetry/starter.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
require 'opentelemetry/sdk'
4+
require 'temporalio/client'
5+
require 'temporalio/contrib/open_telemetry'
6+
require 'temporalio/runtime'
7+
require_relative 'greeting_workflow'
8+
require_relative 'util'
9+
10+
# Configure metrics and tracing
11+
OpenTelemetry::Util.configure_metrics_and_tracing
12+
13+
# Demonstrate that we can create a custom metric right on the runtime, though most users won't need this
14+
Temporalio::Runtime.default.metric_meter.create_metric(:gauge, 'my-starter-gauge', value_type: :float)
15+
.with_additional_attributes({ 'my-group-attr' => 'simple-starters' })
16+
.record(1.23)
17+
18+
# Create a client with the tracing interceptor set using the tracer
19+
tracer = OpenTelemetry.tracer_provider.tracer('temporal_ruby_sample', '0.1.0')
20+
client = Temporalio::Client.connect(
21+
'localhost:7233',
22+
'default',
23+
interceptors: [Temporalio::Contrib::OpenTelemetry::TracingInterceptor.new(tracer)]
24+
)
25+
26+
# Demonstrate an arbitrary outer span. Most users may not explicitly create outer spans before using clients and rather
27+
# solely rely on the implicit ones created in the client via interceptor, but this demonstrates that it can be done.
28+
tracer.in_span('my-client-span', attributes: { 'my-group-attr' => 'simple-client' }) do
29+
# Run workflow
30+
puts 'Executing workflow'
31+
result = client.execute_workflow(
32+
OpenTelemetry::GreetingWorkflow,
33+
'User', # Workflow argument
34+
id: 'opentelemetry-sample-workflow-id',
35+
task_queue: 'opentelemetry-sample'
36+
)
37+
puts "Workflow result: #{result}"
38+
end

open_telemetry/util.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require 'opentelemetry/exporter/otlp'
4+
require 'opentelemetry/sdk'
5+
require 'temporalio/contrib/open_telemetry'
6+
require 'temporalio/runtime'
7+
8+
module OpenTelemetry
9+
module Util
10+
def self.configure_metrics_and_tracing
11+
# Before doing anything, configure the default runtime with OpenTelemetry metrics. Unlike OpenTelemetry tracing in
12+
# Temporal, OpenTelemetry metrics does not use the Ruby OpenTelemetry library, but rather an internal one.
13+
Temporalio::Runtime.default = Temporalio::Runtime.new(
14+
telemetry: Temporalio::Runtime::TelemetryOptions.new(
15+
metrics: Temporalio::Runtime::MetricsOptions.new(
16+
opentelemetry: Temporalio::Runtime::OpenTelemetryMetricsOptions.new(
17+
url: 'http://127.0.0.1:4317',
18+
durations_as_seconds: true
19+
)
20+
)
21+
)
22+
)
23+
# Globally configure the Ruby OpenTelemetry library for tracing purposes. As of this writing, OpenTelemetry Ruby
24+
# does not support OTLP over gRPC, so we use the HTTP endpoint instead.
25+
OpenTelemetry::SDK.configure do |c|
26+
c.service_name = 'my-service'
27+
c.use_all
28+
# Can use a SimpleSpanProcessor instead of a BatchSpanProcessor, but batch is better for production and moves
29+
# the span exporting outside of the workflow instead of synchronously inside the workflow context.
30+
processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
31+
OpenTelemetry::Exporter::OTLP::Exporter.new(
32+
endpoint: 'http://localhost:4318/v1/traces'
33+
)
34+
)
35+
c.add_span_processor(processor)
36+
# We need to shutdown the batch span processor on process exit to flush spans
37+
at_exit { processor.shutdown }
38+
end
39+
end
40+
end
41+
end

open_telemetry/worker.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
require 'opentelemetry/sdk'
4+
require 'temporalio/client'
5+
require 'temporalio/contrib/open_telemetry'
6+
require 'temporalio/runtime'
7+
require 'temporalio/worker'
8+
require_relative 'compose_greeting_activity'
9+
require_relative 'greeting_workflow'
10+
require_relative 'util'
11+
12+
# Configure metrics and tracing
13+
OpenTelemetry::Util.configure_metrics_and_tracing
14+
15+
# Demonstrate that we can create a custom metric right on the runtime, though most users won't need this
16+
Temporalio::Runtime.default.metric_meter.create_metric(:gauge, 'my-worker-gauge', value_type: :float)
17+
.with_additional_attributes({ 'my-group-attr' => 'simple-workers' })
18+
.record(1.23)
19+
20+
# Create a client with the tracing interceptor set using the tracer
21+
tracer = OpenTelemetry.tracer_provider.tracer('opentelemetry_sample', '1.0.0')
22+
client = Temporalio::Client.connect(
23+
'localhost:7233',
24+
'default',
25+
interceptors: [Temporalio::Contrib::OpenTelemetry::TracingInterceptor.new(tracer)]
26+
)
27+
28+
# Run worker
29+
worker = Temporalio::Worker.new(
30+
client:,
31+
task_queue: 'opentelemetry-sample',
32+
activities: [OpenTelemetry::ComposeGreetingActivity.new(tracer)],
33+
workflows: [OpenTelemetry::GreetingWorkflow]
34+
)
35+
puts 'Starting worker (ctrl+c to exit)'
36+
worker.run(shutdown_signals: ['SIGINT'])

0 commit comments

Comments
 (0)