Module: Jekyll::LinkDecorator::Instrumentation

Defined in:
lib/jekyll-link-decorator/instrumentation.rb

Overview

OpenTelemetry instrumentation facade for jekyll-link-decorator.

All tracing is configured centrally in TRACED_METHODS — no span code lives in business logic classes. To add a span: append one entry. To rename a method: update the one entry. When a method is removed from its class the stale entry raises NoMethodError in tests, signalling the entry to delete.

Requires opentelemetry-api at runtime; falls back to a no-op if absent. Users opt in to real tracing by adding opentelemetry-sdk and opentelemetry-exporter-otlp to their site Gemfile and exporting:

OTEL_SERVICE_NAME=jekyll-link-decorator
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Defined Under Namespace

Classes: NoopSpan, NoopTracer

Constant Summary collapse

TRACER_NAME =
'jekyll-link-decorator'
TRACED_METHODS =

Central instrumentation registry.

Each row: [class_name, method_type, method_name, span_name, attribute_proc]

method_type:

:instance  — public instance method
:private   — private instance method (visibility is preserved on the wrapper)
:class     — class / module method (prepended on the singleton class)

attribute_proc: ->(span, this, args, result) or nil span — OTel span (or NoopSpan); call span.set_attribute after super returns this — receiver object (instance or nil for class methods) args — positional args array as passed to the method result — return value of the original method :nocov: — attribute procs are configuration data; coverage comes from integration tests

[
  # decorate_html is the converter's entry point — one span per page converted
  ['Jekyll::Converters::LinkDecorator', :instance, :decorate_html, 'link.decorate_page',
   lambda { |span, _this, args, result|
     span.set_attribute('link.html_size_bytes', args[0].to_s.bytesize)
     span.set_attribute('link.output_size_bytes', result.to_s.bytesize)
   }],

  ['Jekyll::Converters::LinkDecorator', :instance, :apply_link_styles, 'link.style_apply',
   lambda { |span, _this, args, _result|
     doc = args[0]
     span.set_attribute('link.styled_link_count', doc.respond_to?(:css) ? doc.css('a:not(.btn)').size : 0)
   }],

  ['Jekyll::Converters::LinkDecorator', :instance, :add_external_link_features,
   'link.external_link_features',
   lambda { |span, _this, args, _result|
     doc = args[0]
     span.set_attribute('link.external_link_count',
                        doc.respond_to?(:css) ? doc.css('a[target="_blank"]').size : 0)
   }],

  # add_heading_anchors is private on LinkDecorator; visibility is preserved on the wrapper
  ['Jekyll::Converters::LinkDecorator', :private, :add_heading_anchors, 'link.heading_anchors', nil]
].freeze

Class Method Summary collapse

Class Method Details

.enabled?Boolean

Returns true when OTel is requested via standard environment variables.

install! guards on this so the prepend wrappers are only applied when a real exporter is configured. In CI and local tests (no OTel env vars) business logic classes are untouched, keeping allow_any_instance_of stubs and other RSpec mechanics fully functional.

Returns:

  • (Boolean)


97
98
99
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 97

def self.enabled?
  ENV.key?('OTEL_EXPORTER_OTLP_ENDPOINT') || ENV.key?('OTEL_SERVICE_NAME')
end

.install!Object

Installs wrappers on all classes listed in TRACED_METHODS using Module#prepend.

Called once at plugin load time (end of jekyll-link-decorator.rb, after all requires), but only when enabled? returns true. Use OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_SERVICE_NAME to activate tracing.



106
107
108
109
110
111
112
113
114
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 106

def self.install!
  return if @installed

  @installed = true
  setup_sdk!
  TRACED_METHODS.group_by { |e| e[0] }.each do |class_name, entries|
    prepend_wrappers(resolve_class(class_name), entries)
  end
end

.instrument(span_name, attributes: {}) {|span| ... } ⇒ Object

Wraps a block in an OTel span.

Parameters:

  • span_name (String)

    Dot-separated span name (e.g. 'link.style_apply')

  • attributes (Hash) (defaults to: {})

    Initial span attributes

Yield Parameters:

  • span (OpenTelemetry::Trace::Span, NoopSpan)

    Active span

Returns:

  • (Object)

    The return value of the block



81
82
83
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 81

def self.instrument(span_name, attributes: {}, &block)
  tracer.in_span(span_name, attributes: attributes, &block)
end

.reset!Object

Resets the cached tracer and installation flag. Call in tests after changing OTel configuration.



86
87
88
89
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 86

def self.reset!
  @tracer    = nil
  @installed = false
end

.resolve_class(name) ⇒ Object

Resolves a dot-separated class name to a constant; returns nil on NameError.



161
162
163
164
165
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 161

def self.resolve_class(name)
  name.split('::').reduce(Object) { |m, c| m.const_get(c) }
rescue NameError
  nil
end

.tracerObject

Returns the active OTel tracer, or a no-op tracer if opentelemetry-api is absent.



64
65
66
67
68
69
70
71
72
73
# File 'lib/jekyll-link-decorator/instrumentation.rb', line 64

def self.tracer
  @tracer ||=
    if defined?(OpenTelemetry)
      # :nocov:
      OpenTelemetry.tracer_provider.tracer(TRACER_NAME, Jekyll::LinkDecorator::VERSION)
      # :nocov:
    else
      NoopTracer.new
    end
end