Class: CincOtel::Handler

Inherits:
Chef::EventDispatch::Base
  • Object
show all
Defined in:
lib/cinc_otel/handler.rb

Overview

Emits an OpenTelemetry trace for the Cinc Client run: a root span for the run, child spans for each phase (node load, cookbook sync, compilation, converge, ...) and one span per resource action.

Enable it from client.rb (or solo.rb):

require "cinc-otel"
CincOtel::Handler.register!

Exporter and endpoint are configured through the standard OTEL_* environment variables. The opentelemetry-sdk gem (and opentelemetry-exporter-otlp for the default OTLP exporter) are runtime dependencies, so they are always available.

Spans can be tagged with a __tenant resource attribute (used by Tempo multi-tenancy for search and tail-sampling routing) either through the standard env var:

OTEL_RESOURCE_ATTRIBUTES=__tenant=controlplane

or explicitly, which takes precedence over the env var:

CincOtel::Handler.register!(tenant: "controlplane")

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tracer_provider: nil, tenant: nil) ⇒ Handler

Returns a new instance of Handler.



66
67
68
69
70
# File 'lib/cinc_otel/handler.rb', line 66

def initialize(tracer_provider: nil, tenant: nil)
  @stack = []
  @tracer_provider = tracer_provider || default_tracer_provider(tenant)
  @tracer = @tracer_provider&.tracer("cinc-otel", CincOtel::VERSION)
end

Class Method Details

.register!(**opts) ⇒ Object

Adds the handler to Chef::Config. Idempotent, because some applications (chef-solo) evaluate the config file more than once and a second handler would emit every span twice.



60
61
62
63
64
# File 'lib/cinc_otel/handler.rb', line 60

def self.register!(**opts)
  return if Chef::Config[:event_handlers].any? { |handler| handler.is_a?(self) }

  Chef::Config[:event_handlers] << new(**opts)
end

Instance Method Details

#deprecation(message, location = nil) ⇒ Object



98
99
100
101
102
103
# File 'lib/cinc_otel/handler.rb', line 98

def deprecation(message, location = nil)
  text = message.respond_to?(:message) ? message.message.to_s : message.to_s
  attributes = { "chef.deprecation.message" => text }
  attributes["chef.deprecation.location"] = location.to_s if location
  current_span&.add_event("deprecation", attributes:)
end

#node_load_success(node) ⇒ Object



89
90
91
92
# File 'lib/cinc_otel/handler.rb', line 89

def node_load_success(node)
  root_span&.set_attribute("chef.node.name", node.name.to_s)
  root_span&.set_attribute("chef.environment", node.chef_environment.to_s)
end

#ohai_completed(node) ⇒ Object



94
95
96
# File 'lib/cinc_otel/handler.rb', line 94

def ohai_completed(node)
  current_span&.add_event("ohai_completed")
end

#resource_action_start(resource, action, notification_type = nil, notifier = nil) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/cinc_otel/handler.rb', line 143

def resource_action_start(resource, action, notification_type = nil, notifier = nil)
  attributes = {
    "chef.resource.type" => resource.resource_name.to_s,
    "chef.resource.name" => resource.name.to_s,
    "chef.resource.action" => action.to_s,
  }
  attributes["chef.resource.cookbook"] = resource.cookbook_name.to_s if resource.cookbook_name
  attributes["chef.resource.recipe"] = resource.recipe_name.to_s if resource.recipe_name
  attributes["chef.resource.notification_type"] = notification_type.to_s if notification_type
  attributes["chef.resource.notifying_resource"] = notifier.to_s if notifier
  start_span(resource.to_s, attributes)
end

#resource_completed(resource) ⇒ Object



187
188
189
# File 'lib/cinc_otel/handler.rb', line 187

def resource_completed(resource)
  end_span(resource.to_s)
end

#resource_failed(resource, action, exception) ⇒ Object



172
173
174
175
176
177
178
# File 'lib/cinc_otel/handler.rb', line 172

def resource_failed(resource, action, exception)
  span = current_span
  return unless span

  span.record_exception(exception)
  span.status = OpenTelemetry::Trace::Status.error(exception.message)
end

#resource_failed_retriable(resource, action, retry_count, exception) ⇒ Object



180
181
182
183
184
185
# File 'lib/cinc_otel/handler.rb', line 180

def resource_failed_retriable(resource, action, retry_count, exception)
  current_span&.add_event("retry", attributes: {
    "chef.resource.retries_remaining" => retry_count,
    "exception.message" => exception.message,
  })
end

#resource_skipped(resource, action, conditional) ⇒ Object



164
165
166
167
168
169
170
# File 'lib/cinc_otel/handler.rb', line 164

def resource_skipped(resource, action, conditional)
  span = current_span
  return unless span

  span.set_attribute("chef.resource.skipped", true)
  span.set_attribute("chef.resource.skip_reason", conditional.to_text)
end

#resource_up_to_date(resource, action) ⇒ Object



160
161
162
# File 'lib/cinc_otel/handler.rb', line 160

def resource_up_to_date(resource, action)
  current_span&.set_attribute("chef.resource.updated", false)
end

#resource_updated(resource, action) ⇒ Object



156
157
158
# File 'lib/cinc_otel/handler.rb', line 156

def resource_updated(resource, action)
  current_span&.set_attribute("chef.resource.updated", true)
end

#run_completed(node, run_status) ⇒ Object



79
80
81
82
# File 'lib/cinc_otel/handler.rb', line 79

def run_completed(node, run_status)
  end_span("chef.run", ok: true)
  @tracer_provider&.force_flush
end

#run_failed(exception, run_status) ⇒ Object



84
85
86
87
# File 'lib/cinc_otel/handler.rb', line 84

def run_failed(exception, run_status)
  end_span("chef.run", error: exception)
  @tracer_provider&.force_flush
end

#run_start(version, run_status) ⇒ Object



72
73
74
75
76
77
# File 'lib/cinc_otel/handler.rb', line 72

def run_start(version, run_status)
  start_span("chef.run", {
    "chef.version" => version.to_s,
    "chef.run_id" => run_status.run_id.to_s,
  }, with_parent: remote_parent_context)
end