|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Instrumentation |
| 4 | +nav_order: 5 |
| 5 | +description: Observe RubyLLM requests, chats, tool calls, embeddings, and model refreshes |
| 6 | +redirect_from: |
| 7 | + - /guides/instrumentation |
| 8 | +--- |
| 9 | + |
| 10 | +# {{ page.title }} |
| 11 | +{: .no_toc .d-inline-block } |
| 12 | + |
| 13 | +v1.16.0+ |
| 14 | +{: .label .label-green } |
| 15 | + |
| 16 | +{{ page.description }}. |
| 17 | +{: .fs-6 .fw-300 } |
| 18 | + |
| 19 | +## Table of contents |
| 20 | +{: .no_toc .text-delta } |
| 21 | + |
| 22 | +1. TOC |
| 23 | +{:toc} |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +After reading this guide, you will know: |
| 28 | + |
| 29 | +* How to subscribe to RubyLLM events in Rails. |
| 30 | +* How to connect RubyLLM instrumentation outside Rails. |
| 31 | +* Which events RubyLLM emits. |
| 32 | +* Which payload fields may contain sensitive application data. |
| 33 | + |
| 34 | +## Rails |
| 35 | + |
| 36 | +Rails apps automatically emit RubyLLM events through `ActiveSupport::Notifications`. Subscribe to them the same way you would subscribe to Rails framework events: |
| 37 | + |
| 38 | +```ruby |
| 39 | +# config/initializers/ruby_llm_instrumentation.rb |
| 40 | +ActiveSupport::Notifications.subscribe('chat.ruby_llm') do |_name, _start, _finish, _id, payload| |
| 41 | + Rails.logger.info( |
| 42 | + provider: payload[:provider], |
| 43 | + model: payload[:model], |
| 44 | + input_tokens: payload[:input_tokens], |
| 45 | + output_tokens: payload[:output_tokens] |
| 46 | + ) |
| 47 | +end |
| 48 | +``` |
| 49 | + |
| 50 | +When an instrumented block raises, Rails adds the standard `:exception` and `:exception_object` payload keys. |
| 51 | + |
| 52 | +## Outside Rails |
| 53 | + |
| 54 | +Outside Rails, set `config.instrumenter` to any object that responds to `instrument(name, payload) { ... }`: |
| 55 | + |
| 56 | +```ruby |
| 57 | +class AppInstrumenter |
| 58 | + def instrument(name, payload) |
| 59 | + started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) |
| 60 | + |
| 61 | + result = yield if block_given? |
| 62 | + result |
| 63 | + rescue StandardError => error |
| 64 | + payload = payload.merge( |
| 65 | + exception: [error.class.name, error.message], |
| 66 | + exception_object: error |
| 67 | + ) |
| 68 | + raise |
| 69 | + ensure |
| 70 | + duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at |
| 71 | + Observability.record(name, payload.merge(duration: duration)) |
| 72 | + end |
| 73 | +end |
| 74 | + |
| 75 | +RubyLLM.configure do |config| |
| 76 | + config.instrumenter = AppInstrumenter.new |
| 77 | +end |
| 78 | +``` |
| 79 | + |
| 80 | +You can also set `instrumenter` on a [context]({% link _getting_started/configuration.md %}#contexts-isolated-configurations) when you only want instrumentation around a specific operation. |
| 81 | + |
| 82 | +## Events |
| 83 | + |
| 84 | +RubyLLM emits these events: |
| 85 | + |
| 86 | +* `request.ruby_llm` - HTTP request metadata such as provider, method, URL, and status |
| 87 | +* `chat.ruby_llm` - chat completion metadata including model, provider, messages, response, and token usage |
| 88 | +* `tool_call.ruby_llm` - tool name, arguments, and result |
| 89 | +* `embedding.ruby_llm` - embedding model, input, result, token usage, and vector dimensions |
| 90 | +* `image.ruby_llm` - image generation model, prompt, size, and result |
| 91 | +* `moderation.ruby_llm` - moderation model, input, result, and flagged status |
| 92 | +* `transcription.ruby_llm` - transcription model, language, result, and token usage |
| 93 | +* `models.refresh.ruby_llm` - model registry refresh metadata |
| 94 | + |
| 95 | +## Payloads |
| 96 | + |
| 97 | +Payloads include the Ruby objects needed by observability adapters, but message content, tool arguments, and provider responses may be sensitive. Only export or log those fields when your application policy allows it. |
| 98 | + |
| 99 | +Non-Rails instrumenters control their own error payload behavior. If your instrumenter records exceptions, keep those payloads consistent with the rest of your observability stack. |
0 commit comments