Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Observability Adjustments #217

Merged
merged 10 commits into from
Aug 23, 2024
3 changes: 2 additions & 1 deletion codegen/projections/white_label/spec/telemetry_spec.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ module WhiteLabel
end

it 'raises error when an otel dependency was not required' do
allow(Hearth::Telemetry).to receive(:otel_loaded?).and_return(false)
allow_any_instance_of(Hearth::Telemetry::OTelProvider)
.to receive(:require).with('opentelemetry-sdk').and_raise(LoadError)
expect { otel_provider }
.to raise_error(
ArgumentError,
Expand Down
1 change: 1 addition & 0 deletions hearth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Unreleased Changes
------------------

* Feature - Add support for Event Streams.
* Feature - Add support for Observability.
* Issue - Remove `Struct` from generated Types, Config, and other places to allow for better RBS typing as well as less reserved words.
* Feature - Add `Hearth::Cbor.encode` and `Hearth::Cbor.decode`.
* Issue - Fix query param `to_s` for empty arrays.
Expand Down
4 changes: 2 additions & 2 deletions hearth/lib/hearth/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ def initialize(options = {})
# @return [Symbol] The name of the API operation called.
attr_reader :operation_name

# @return [HTTP::Request]
# @return [Request]
attr_reader :request

# @return [HTTP::Response]
# @return [Response]
attr_reader :response

# @return [Configuration] An instance of operation configuration.
Expand Down
17 changes: 17 additions & 0 deletions hearth/lib/hearth/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,23 @@ def remove_query_param(name)
def prefix_host(prefix)
uri.host = prefix + uri.host
end

# Contains attributes for Telemetry span to emit.
# @return [Hash]
def span_attributes
jterapin marked this conversation as resolved.
Show resolved Hide resolved
{
'http.method' => http_method,
'net.protocol.name' => 'http',
'net.protocol.version' => Net::HTTP::HTTPVersion,
'net.peer.name' => uri.host,
'net.peer.port' => uri.port
}.tap do |h|
if headers.key?('Content-Length')
h['http.request_content_length'] =
headers['Content-Length']
end
end
end
end
end
end
13 changes: 13 additions & 0 deletions hearth/lib/hearth/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ def reset
@fields.clear
super
end

# Contains attributes for Telemetry span to emit.
# @return [Hash]
def span_attributes
{
'http.status_code' => status
}.tap do |h|
if headers.key?('Content-Length')
h['http.response_content_length'] =
headers['Content-Length']
end
end
end
end
end
end
37 changes: 4 additions & 33 deletions hearth/lib/hearth/middleware/send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def call(input, context)
private

def stub_response(input, context, output)
span_wrapper(context, stub_response: true) do
span_wrapper(context) do
stub = @stubs.next(context.operation_name)
log_debug(context, "Stubbing response with stub: #{stub}")
if @response_events
Expand Down Expand Up @@ -109,42 +109,13 @@ def send_request(context, output)
end
end

def span_wrapper(context, stub_response: false, &block)
def span_wrapper(context, &block)
context.tracer.in_span(
'Middleware.Send',
attributes: request_attrs(context, stub_response: stub_response)
attributes: context.request.span_attributes
jterapin marked this conversation as resolved.
Show resolved Hide resolved
) do |span|
block.call
span.add_attributes(response_attrs(context))
end
end

def request_attrs(context, stub_response: false)
{
'http.method' => context.request.http_method,
'net.protocol.name' => 'http',
'net.protocol.version' => Net::HTTP::HTTPVersion
}.tap do |h|
unless stub_response
h['net.peer.name'] = context.request.uri.host
h['net.peer.port'] = context.request.uri.port
end

if context.request.headers.key?('Content-Length')
h['http.request_content_length'] =
context.request.headers['Content-Length']
end
end
end

def response_attrs(context)
{
'http.status_code' => context.response.status
}.tap do |h|
if context.response.headers.key?('Content-Length')
h['http.response_content_length'] =
context.response.headers['Content-Length']
end
span.add_attributes(context.response.span_attributes)
end
end

Expand Down
19 changes: 2 additions & 17 deletions hearth/lib/hearth/telemetry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
module Hearth
# Observability is the extent to which a system's current state can be
# inferred from the data it emits. The data emitted is commonly referred
# as Telemetry. The AWS SDK for Ruby currently supports traces as
# a telemetry signal.
# as Telemetry. Hearth currently supports traces as a telemetry signal.
#
# A telemetry provider is used to emit telemetry data. By default, the
# +NoOpTelemetryProvider+ will not record or emit any telemetry data.
Expand All @@ -25,19 +24,5 @@ module Hearth
# * {TracerProviderBase}
# * {TracerBase}
# * {SpanBase}
module Telemetry
# @api private
def self.otel_loaded?
if @use_otel.nil?
@use_otel =
begin
require 'opentelemetry-sdk'
true
rescue LoadError, NameError
false
end
end
@use_otel
end
end
module Telemetry; end
end
14 changes: 7 additions & 7 deletions hearth/lib/hearth/telemetry/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ def start_span(name, with_parent: nil, attributes: nil, kind: nil)
def in_span(name, attributes: nil, kind: nil)
raise NotImplementedError
end

# Returns the current active span.
#
# @return Span
def current_span
raise NotImplementedError
end
end

# Base for Span classes.
Expand Down Expand Up @@ -146,13 +153,6 @@ def current
raise NotImplementedError
end

# Returns the current span from current context.
#
# @return Span
def current_span
raise NotImplementedError
end

# Associates a Context with the caller’s current execution unit.
# Returns a token to be used with the matching call to detach.
#
Expand Down
8 changes: 4 additions & 4 deletions hearth/lib/hearth/telemetry/no_op.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def start_span(name, with_parent: nil, attributes: nil, kind: nil)
def in_span(name, attributes: nil, kind: nil)
yield NoOpSpan.new
end

def current_span
NoOpSpan.new
end
end

# No-op implementation for Span.
Expand Down Expand Up @@ -61,10 +65,6 @@ def record_exception(exception, attributes: nil); end
class NoOpContextManager < ContextManagerBase
def current; end

def current_span
NoOpSpan.new
end

def attach(context); end

def detach(token); end
Expand Down
41 changes: 29 additions & 12 deletions hearth/lib/hearth/telemetry/otel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module Telemetry
# client = Service::Client.new(telemetry_provider: otel_provider)
class OTelProvider < TelemetryProviderBase
def initialize
unless Hearth::Telemetry.otel_loaded?
unless otel_loaded?
raise ArgumentError,
'Requires the `opentelemetry-sdk` gem to use OTel Provider.'
end
Expand All @@ -44,6 +44,22 @@ def initialize
context_manager: OTelContextManager.new
)
end

private

# @api private
def otel_loaded?
if @use_otel.nil?
@use_otel =
begin
require 'opentelemetry-sdk'
true
rescue LoadError, NameError
false
end
end
@use_otel
end
end

# OpenTelemetry-based Tracer Provider, an entry point for
Expand All @@ -57,13 +73,14 @@ def initialize
# Returns a Tracer instance.
#
# @param [optional String] name Tracer name
# @return [Tracer]
# @return [Hearth::Telemetry::OTelTracer]
def tracer(name = nil)
OTelTracer.new(@tracer_provider.tracer(name))
end
end

# OpenTelemetry-based Tracer, responsible for creating spans.
# OpenTelemetry-based Tracer, responsible for creating spans
# and retrieving the current span.
class OTelTracer < TracerBase
def initialize(tracer)
super()
Expand All @@ -77,7 +94,7 @@ def initialize(tracer)
# @param [Object] with_parent Parent Context
# @param [Hash] attributes Attributes to attach to the span
# @param [Hearth::Telemetry::SpanKind] kind Type of Span
# @return [Span]
# @return [Hearth::Telemetry::OTelSpan]
def start_span(name, with_parent: nil, attributes: nil, kind: nil)
span = @tracer.start_span(
name,
Expand All @@ -97,12 +114,19 @@ def start_span(name, with_parent: nil, attributes: nil, kind: nil)
# @param [String] name Span name
# @param [Hash] attributes Attributes to attach to the span
# @param [Hearth::Telemetry::SpanKind] kind Type of Span
# @return [Span]
# @return [Hearth::Telemetry::OTelSpan]
def in_span(name, attributes: nil, kind: nil, &block)
@tracer.in_span(name, attributes: attributes, kind: kind) do |span|
block.call(OTelSpan.new(span))
end
end

# Returns the current active span.
#
# @return [Hearth::Telemetry::OTelSpan]
def current_span
OTelSpan.new(OpenTelemetry::Trace.current_span)
end
end

# OpenTelemetry-based Span, represents a single operation
Expand Down Expand Up @@ -192,13 +216,6 @@ def current
OpenTelemetry::Context.current
end

# Returns the current span from current context.
#
# @return Span
def current_span
OTelSpan.new(OpenTelemetry::Trace.current_span)
end

# Associates a Context with the caller’s current execution unit.
# Returns a token to be used with the matching call to detach.
#
Expand Down
4 changes: 2 additions & 2 deletions hearth/sig/lib/hearth/telemetry/base.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module Hearth
def start_span: (String name, ?untyped with_parent, ?Hash[String, untyped] attributes, ?SpanKind kind) -> SpanBase

def in_span: (String name, ?Hash[String, untyped] attributes, ?SpanKind kind) -> SpanBase

def current_span: () -> SpanBase
end

class SpanBase
Expand All @@ -35,8 +37,6 @@ module Hearth
class ContextManagerBase
def current: () -> untyped

def current_span: () -> SpanBase

def attach: (untyped context) -> untyped

def detach: (untyped token) -> bool
Expand Down
8 changes: 4 additions & 4 deletions hearth/spec/hearth/telemetry/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ module Telemetry
expect do
subject.in_span('foo')
end.to raise_error(NotImplementedError)

expect do
subject.current_span
end.to raise_error(NotImplementedError)
end
end

Expand Down Expand Up @@ -56,10 +60,6 @@ module Telemetry
subject.current
end.to raise_error(NotImplementedError)

expect do
subject.current_span
end.to raise_error(NotImplementedError)

expect do
subject.attach('foo')
end.to raise_error(NotImplementedError)
Expand Down
16 changes: 7 additions & 9 deletions hearth/spec/hearth/telemetry/no_op_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ module Telemetry
end
end
end

describe '#current_span' do
it 'returns an instance of no-op span' do
expect(subject.current_span)
.to be_an_instance_of(Hearth::Telemetry::NoOpSpan)
end
end
end

describe NoOpSpan do
Expand Down Expand Up @@ -67,14 +74,5 @@ module Telemetry
end
end
end

describe NoOpContextManager do
describe '#current_span' do
it 'returns an instance of no-op span' do
expect(subject.current_span)
.to be_an_instance_of(Hearth::Telemetry::NoOpSpan)
end
end
end
end
end
Loading
Loading