Module: Jekyll::L10n::Instrumentation
- Defined in:
- lib/jekyll-l10n/instrumentation.rb
Overview
OpenTelemetry instrumentation facade for jekyll-l10n.
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-l10n
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
Defined Under Namespace
Classes: NoopSpan, NoopTracer
Constant Summary collapse
- TRACER_NAME =
'jekyll-l10n'- TRACED_METHODS =
[ # ── Jekyll integration ──────────────────────────────────────────────── ['Jekyll::L10n::Generator', :instance, :generate, 'l10n.generate', lambda { |span, _this, args, _result| span.set_attribute('l10n.site_page_count', args[0]&.pages&.size || 0) }], ['Jekyll::L10n::PostWriteProcessor', :instance, :process_localizations, 'l10n.post_write', nil], # translate is the post_render hook entry point — one span per localized page ['Jekyll::L10n::Translator', :instance, :translate, 'l10n.translate_render', lambda { |span, this, _args, _result| span.set_attribute('l10n.locale', this.page.data['locale'].to_s) span.set_attribute('l10n.page_url', this.page.url.to_s) }], # ── Extraction pipeline ─────────────────────────────────────────────── ['Jekyll::L10n::Extractor', :instance, :extract_site, 'l10n.extract_site', lambda { |span, _this, _args, result| span.set_attribute('l10n.file_count', Instrumentation.hash_val(result, :files_processed)) }], # process_file is the per-page body called from the html_files loop ['Jekyll::L10n::Extractor', :private, :process_file, 'l10n.extract_page', lambda { |span, _this, args, result| span.set_attribute('l10n.page_path', args[0].to_s) span.set_attribute('l10n.strings_extracted', Instrumentation.hash_val(result, :strings_extracted)) }], ['Jekyll::L10n::HtmlStringExtractor', :instance, :extract, 'l10n.html_extract', lambda { |span, _this, args, result| span.set_attribute('l10n.html_size_bytes', args[0].bytesize) span.set_attribute('l10n.extracted_count', Instrumentation.array_size(result)) }], ['Jekyll::L10n::ExtractionResultSaver', :instance, :save_results, 'l10n.po_file_write', lambda { |span, _this, args, result| span.set_attribute('l10n.page_path', args[2].to_s) span.set_attribute('l10n.entry_count', Instrumentation.array_size(args[1])) span.set_attribute('l10n.po_files_created', Instrumentation.hash_val(result, :po_files_created)) }], ['Jekyll::L10n::CompendiumMerger', :instance, :merge_compendia, 'l10n.compendium_merge', nil], ['Jekyll::L10n::CompendiumTranslator', :instance, :translate_compendia, 'l10n.translate_compendia', lambda { |span, _this, args, _result| config = args[0] span.set_attribute('l10n.locale_count', config.locales.size) if config.respond_to?(:locales) }], # ── Translation pipeline ────────────────────────────────────────────── ['Jekyll::L10n::PostWriteHtmlReprocessor', :instance, :reprocess_localized_pages, 'l10n.reprocess_localized_pages', nil], # translate_html_file is the per-page body called from the localized_files loop ['Jekyll::L10n::PostWriteHtmlReprocessor', :private, :translate_html_file, 'l10n.translate_page', lambda { |span, _this, args, _result| span.set_attribute('l10n.page_path', args[0].to_s) span.set_attribute('l10n.locale', args[1].to_s) }], ['Jekyll::L10n::PageTranslationLoader', :class, :load, 'l10n.translation_load', lambda { |span, _this, args, result| span.set_attribute('l10n.locale', args[1].to_s) span.set_attribute('l10n.page_path', args[2].to_s) span.set_attribute('l10n.entry_count', Instrumentation.hash_size(result)) }], ['Jekyll::L10n::HtmlTranslator', :instance, :translate, 'l10n.dom_translate', lambda { |span, this, args, _result| span.set_attribute('l10n.locale', (args[2] || 'en').to_s) span.set_attribute('l10n.fallback_mode', this.fallback_mode.to_s) }], ['Jekyll::L10n::LibreTranslator', :private, :make_api_request, 'l10n.libretranslate_batch', lambda { |span, _this, args, _result| span.set_attribute('l10n.locale', args[1].to_s) span.set_attribute('l10n.batch_size', args[0].is_a?(Array) ? args[0].size : 1) }], # ── Utilities ───────────────────────────────────────────────────────── ['Jekyll::L10n::HtmlParser', :class, :parse_document, 'l10n.html_parse', lambda { |span, _this, args, _result| span.set_attribute('l10n.html_size_bytes', args[0].bytesize) }], ['Jekyll::L10n::UrlTransformer', :class, :transform_document, 'l10n.url_transform', lambda { |span, _this, args, _result| span.set_attribute('l10n.locale', args[1].to_s) doc = args[0] span.set_attribute('l10n.href_count', doc.respond_to?(:css) ? doc.css('a[href]').size : 0) }], ['Jekyll::L10n::ExternalLinkIconPreserver', :class, :preserve, 'l10n.icon_preserve', nil], # ── PO file operations ──────────────────────────────────────────────── ['Jekyll::L10n::PoFileReader', :instance, :parse_for_translation, 'l10n.po_file_read', lambda { |span, this, _args, result| span.set_attribute('l10n.file_path', this.po_path.to_s) span.set_attribute('l10n.entry_count', Instrumentation.hash_size(result)) }], ['Jekyll::L10n::PoFileMerger', :class, :merge_for_locale, 'l10n.po_merge', lambda { |span, _this, args, result| span.set_attribute('l10n.locale', args[2].to_s) span.set_attribute('l10n.merged_count', Instrumentation.hash_size(result)) }] ].freeze
Class Method Summary collapse
- .array_size(val) ⇒ Object
-
.enabled? ⇒ Boolean
Returns true when OTel is requested via standard environment variables.
- .hash_size(hash) ⇒ Object
-
.hash_val(hash, key) ⇒ Object
Helpers for common attribute patterns used inside TRACED_METHODS procs.
-
.install! ⇒ Object
Installs wrappers on all classes listed in TRACED_METHODS using Module#prepend.
-
.instrument(span_name, attributes: {}) {|span| ... } ⇒ Object
Wraps a block in an OTel span.
-
.reset! ⇒ Object
Resets the cached tracer and installation flag.
-
.resolve_class(name) ⇒ Object
Resolves a dot-separated class name to a constant; returns nil on NameError.
-
.tracer ⇒ Object
Returns the active OTel tracer, or a no-op tracer if opentelemetry-api is absent.
Class Method Details
.array_size(val) ⇒ Object
46 47 48 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 46 def self.array_size(val) val.is_a?(Array) ? val.size : 0 end |
.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.
204 205 206 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 204 def self.enabled? ENV.key?('OTEL_EXPORTER_OTLP_ENDPOINT') || ENV.key?('OTEL_SERVICE_NAME') end |
.hash_size(hash) ⇒ Object
42 43 44 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 42 def self.hash_size(hash) hash.is_a?(Hash) ? hash.size : 0 end |
.hash_val(hash, key) ⇒ Object
Helpers for common attribute patterns used inside TRACED_METHODS procs.
38 39 40 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 38 def self.hash_val(hash, key) hash.is_a?(Hash) ? hash[key].to_i : 0 end |
.install! ⇒ Object
Installs wrappers on all classes listed in TRACED_METHODS using Module#prepend.
Called once at plugin load time (end of jekyll-l10n.rb, after all requires), but only when enabled? returns true. Use OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_SERVICE_NAME to activate tracing.
213 214 215 216 217 218 219 220 221 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 213 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.
188 189 190 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 188 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.
193 194 195 196 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 193 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.
249 250 251 252 253 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 249 def self.resolve_class(name) name.split('::').reduce(Object) { |m, c| m.const_get(c) } rescue NameError nil end |
.tracer ⇒ Object
Returns the active OTel tracer, or a no-op tracer if opentelemetry-api is absent.
171 172 173 174 175 176 177 178 179 180 |
# File 'lib/jekyll-l10n/instrumentation.rb', line 171 def self.tracer @tracer ||= if defined?(OpenTelemetry) # :nocov: OpenTelemetry.tracer_provider.tracer(TRACER_NAME, Jekyll::L10n::VERSION) # :nocov: else NoopTracer.new end end |