1"use strict";(globalThis.webpackChunkbase14_docs||=[]).push([[747],{47781(e,n,r){r.r(n),r.d(n,{assets:()=>p,contentTitle:()=>u,default:()=>g,frontMatter:()=>d,metadata:()=>t,toc:()=>m});const t=JSON.parse('{"id":"instrument/apps/auto-instrumentation/rails","title":"Rails OpenTelemetry Instrumentation - ActiveRecord & Sidekiq Tracing","description":"Add OpenTelemetry to Rails. Trace HTTP requests, ActiveRecord queries, and Sidekiq jobs. Detects N+1 queries automatically.","source":"@site/docs/instrument/apps/auto-instrumentation/rails.md","sourceDirName":"instrument/apps/auto-instrumentation","slug":"/instrument/apps/auto-instrumentation/rails","permalink":"/instrument/apps/auto-instrumentation/rails","draft":false,"unlisted":false,"tags":[],"version":"current","lastUpdatedAt":1789905503000,"sidebarPosition":24,"frontMatter":{"title":"Rails OpenTelemetry Instrumentation - ActiveRecord & Sidekiq Tracing","sidebar_label":"Ruby on Rails","sidebar_position":24,"description":"Add OpenTelemetry to Rails. Trace HTTP requests, ActiveRecord queries, and Sidekiq jobs. Detects N+1 queries automatically.","keywords":["rails opentelemetry instrumentation","rails monitoring","ruby apm","rails application performance monitoring","opentelemetry rails","ruby on rails monitoring","rails distributed tracing","activerecord query monitoring","rails observability","rails performance monitoring","ruby opentelemetry sdk","rails production monitoring","rails database monitoring","rails metrics","rails tracing","sidekiq monitoring","rails n+1 queries","rails instrumentation guide","opentelemetry ruby","rails telemetry"]},"sidebar":"tutorialSidebar","previous":{"title":"Go stdlib + Postgres","permalink":"/instrument/apps/auto-instrumentation/go-stdlib-postgres"},"next":{"title":"Ruby on Rails (Legacy)","permalink":"/instrument/apps/auto-instrumentation/rails-legacy"}}');var i=r(74848),s=r(28453),a=r(98362),o=r.n(a),l=r(74070),c=r.n(l);const d={title:"Rails OpenTelemetry Instrumentation - ActiveRecord & Sidekiq Tracing",sidebar_label:"Ruby on Rails",sidebar_position:24,description:"Add OpenTelemetry to Rails. Trace HTTP requests, ActiveRecord queries, and Sidekiq jobs. Detects N+1 queries automatically.",keywords:["rails opentelemetry instrumentation","rails monitoring","ruby apm","rails application performance monitoring","opentelemetry rails","ruby on rails monitoring","rails distributed tracing","activerecord query monitoring","rails observability","rails performance monitoring","ruby opentelemetry sdk","rails production monitoring","rails database monitoring","rails metrics","rails tracing","sidekiq monitoring","rails n+1 queries","rails instrumentation guide","opentelemetry ruby","rails telemetry"]},u="Ruby on Rails",p={},m=[{value:"Who This Guide Is For",id:"who-this-guide-is-for",level:2},{value:"Overview",id:"overview",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Compatibility Matrix",id:"compatibility-matrix",level:3},{value:"Required Packages",id:"required-packages",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Configuring Instrumentation Options",id:"configuring-instrumentation-options",level:3},{value:"Scout Collector Integration",id:"scout-collector-integration",level:3},{value:"Production Configuration",id:"production-configuration",level:2},{value:"Batch Span Processor (Recommended for Production)",id:"batch-span-processor-recommended-for-production",level:3},{value:"Resource Attributes",id:"resource-attributes",level:3},{value:"Environment-Based Configuration",id:"environment-based-configuration",level:3},{value:"Production Environment Variables",id:"production-environment-variables",level:3},{value:"Docker Production Configuration",id:"docker-production-configuration",level:3},{value:"Metrics",id:"metrics",level:2},{value:"Automatic HTTP Metrics",id:"automatic-http-metrics",level:3},{value:"Custom Business Metrics",id:"custom-business-metrics",level:3},{value:"Viewing Metrics in Scout Dashboard",id:"viewing-metrics-in-scout-dashboard",level:3},{value:"ActiveRecord Database Monitoring",id:"activerecord-database-monitoring",level:2},{value:"Automatic Query Tracing",id:"automatic-query-tracing",level:3},{value:"Configuring ActiveRecord Instrumentation",id:"configuring-activerecord-instrumentation",level:3}
1,{value:"Detecting N+1 Queries",id:"detecting-n1-queries",level:3},{value:"Custom Database Spans",id:"custom-database-spans",level:3},{value:"Custom Manual Instrumentation",id:"custom-manual-instrumentation",level:2},{value:"Creating Custom Spans for Business Logic",id:"creating-custom-spans-for-business-logic",level:3},{value:"Adding Attributes to Current Spans",id:"adding-attributes-to-current-spans",level:3},{value:"Exception Handling and Error Tracking",id:"exception-handling-and-error-tracking",level:3},{value:"Using Semantic Conventions",id:"using-semantic-conventions",level:3},{value:"Running Your Instrumented Application",id:"running-your-instrumented-application",level:2},{value:"Development Mode",id:"development-mode",level:3},{value:"Production Mode",id:"production-mode",level:3},{value:"Docker Deployment",id:"docker-deployment",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Verifying OpenTelemetry Installation",id:"verifying-opentelemetry-installation",level:3},{value:"Health Check Endpoint",id:"health-check-endpoint",level:3},{value:"Debug Mode",id:"debug-mode",level:3},{value:"Common Issues",id:"common-issues",level:3},{value:"Issue: No traces appearing in Scout Dashboard",id:"issue-no-traces-appearing-in-scout-dashboard",level:4},{value:"Issue: Missing database query spans",id:"issue-missing-database-query-spans",level:4},{value:"Issue: High memory usage",id:"issue-high-memory-usage",level:4},{value:"Issue: Performance degradation",id:"issue-performance-degradation",level:4},{value:"Security Considerations",id:"security-considerations",level:2},{value:"Protecting Sensitive Data",id:"protecting-sensitive-data",level:3},{value:"Sanitizing SQL Statements",id:"sanitizing-sql-statements",level:3},{value:"Filtering Sensitive HTTP Headers",id:"filtering-sensitive-http-headers",level:3},{value:"Compliance Considerations",id:"compliance-considerations",level:3},{value:"Performance Considerations",id:"performance-considerations",level:2},{value:"Expected Performance Impact",id:"expected-performance-impact",level:3},{value:"Optimization Best Practices",id:"optimization-best-practices",level:3},{value:"1. Use BatchSpanProcessor in Production",id:"1-use-batchspanprocessor-in-production",level:4},{value:"2. Skip Non-Critical Endpoints",id:"2-skip-non-critical-endpoints",level:4},{value:"3. Conditional Span Recording",id:"3-conditional-span-recording",level:4},{value:"4. Limit Attribute Sizes",id:"4-limit-attribute-sizes",level:4},{value:"Frequently Asked Questions",id:"frequently-asked-questions",level:2},{value:"What is the performance impact of OpenTelemetry on Rails apps?",id:"what-is-the-performance-impact-of-opentelemetry-on-rails-apps",level:3},{value:"Which Rails versions are supported?",id:"which-rails-versions-are-supported",level:3},{value:"Can I use OpenTelemetry with Sidekiq or other background job processors?",id:"can-i-use-opentelemetry-with-sidekiq-or-other-background-job-processors",level:3},{value:"Is OpenTelemetry compatible with Rack middleware?",id:"is-opentelemetry-compatible-with-rack-middleware",level:3},{value:"Can I use OpenTelemetry alongside other APM tools?",id:"can-i-use-opentelemetry-alongside-other-apm-tools",level:3},{value:"How do I handle multi-tenant Rails applications?",id:"how-do-i-handle-multi-tenant-rails-applications",level:3},{value:"What's the difference between traces and metrics?",id:"whats-the-difference-between-traces-and-metrics",level:3},{value:"How do I monitor N+1 database queries?",id:"how-do-i-monitor-n1-database-queries",level:3},{value:"Can I customize which gems are instrumented?",id:"can-i-customize-which-gems-are-instrumented",level:3},{value:"Can OpenTelemetry detect N+1 queries in Rails?",id:"can-opentelemetry-detect-n1-queries-in-rails",level:3},{value:"What's Next?",id:"whats-next",level:2},{value:"Advanced Topics",id:"advanced-topics",level:3},{value:"Scout Platform Features",id:"scout-platform-features",level:3},{value:"Deployment and Operations",id:"deployment-and-operations",level:3},{value:"Complete Example",id:"complete-example",level:2},{value:"Gemfile",id:"gemfile",level:3},{value:"OpenTelemetry Initializer",id:"opentelemetry-initializer",level:3},{value:"Instrumented Controller",id:"instrumented-controller",level:3}
1,{value:"Environment Variables",id:"environment-variables",level:3},{value:"References",id:"references",level:2},{value:"Related Guides",id:"related-guides",level:2}];function h(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"ruby-on-rails",children:"Ruby on Rails"})}),"\n",(0,i.jsx)(n.p,{children:"Implement OpenTelemetry instrumentation for Ruby on Rails applications to enable\ncomprehensive application performance monitoring (APM), distributed tracing, and\nobservability. This guide shows you how to auto-instrument your Rails application\nto collect traces and metrics from HTTP requests, database queries, background\njobs, and custom business logic using the OpenTelemetry Ruby SDK."}),"\n",(0,i.jsxs)(n.p,{children:["This guide covers modern Ruby on Rails. For older Rails versions, see the\n",(0,i.jsx)(n.a,{href:"/instrument/apps/auto-instrumentation/rails-legacy",children:"legacy Rails guide"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Rails applications benefit from automatic instrumentation of popular frameworks\nand libraries including ActiveRecord, ActionPack, ActionView, Redis, Sidekiq,\nand dozens of commonly used gems. With OpenTelemetry, you can monitor production\nperformance, debug slow requests, trace distributed transactions across\nmicroservices, and identify database bottlenecks without significant code changes."}),"\n",(0,i.jsx)(n.p,{children:"Whether you're implementing observability for the first time, migrating from\ncommercial APM solutions, or troubleshooting performance issues in production,\nthis guide provides production-ready configurations and best practices for\nRails OpenTelemetry instrumentation."}),"\n",(0,i.jsx)(n.admonition,{title:"TL;DR",type:"tip",children:(0,i.jsxs)(n.p,{children:["Add ",(0,i.jsx)(n.code,{children:"opentelemetry-sdk"})," and ",(0,i.jsx)(n.code,{children:"opentelemetry-instrumentation-all"})," to your\nGemfile, then call ",(0,i.jsx)(n.code,{children:"OpenTelemetry::SDK.configure"})," with ",(0,i.jsx)(n.code,{children:"use_all"})," in a Rails\ninitializer - this automatically instruments ActiveRecord, ActionPack, Redis,\nSidekiq, and most popular gems. Set ",(0,i.jsx)(n.code,{children:"OTEL_SERVICE_NAME"})," and\n",(0,i.jsx)(n.code,{children:"OTEL_EXPORTER_OTLP_ENDPOINT"})," as environment variables, and configure the OTLP\nexporter to point at your Scout collector with no additional code changes."]})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Note:"})," This guide provides a practical Rails-focused overview based on the\nofficial OpenTelemetry documentation. For complete Ruby language information,\nplease consult the ",(0,i.jsx)(n.a,{href:"https://opentelemetry.io/
1docs/languages/ruby/instrumentation",children:"official OpenTelemetry Ruby documentation"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"Running this in production",type:"note",children:(0,i.jsxs)(n.p,{children:["Storing and querying this data at production volume is what base14 Scout does.\n",(0,i.jsx)(n.a,{href:"https://base14.io/scout/apm",children:"Check out Scout APM"}),"."]})}),"\n",(0,i.jsx)(n.h2,{id:"who-this-guide-is-for",children:"Who This Guide Is For"}),"\n",(0,i.jsx)(n.p,{children:"This documentation is designed for:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Rails developers:"}),"\nimplementing observability and distributed tracing for the first time"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"DevOps engineers:"}),"\ndeploying Rails applications with production monitoring requirements"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Engineering teams:"}),"\nmigrating from DataDog, New Relic, or other commercial APM solutions"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Developers:"}),"\ndebugging performance issues, slow database queries, or N+1 problems in Rails\napplications"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Platform teams:"}),"\nstandardizing observability across multiple Rails services"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,i.jsx)(n.p,{children:"This comprehensive guide demonstrates how to:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Install and configure OpenTelemetry SDK for Rails applications"}),"\n",(0,i.jsx)(n.li,{children:"Set up automatic instrumentation for HTTP requests, database queries, and\npopular gems"}),"\n",(0,i.jsx)(n.li,{children:"Configure production-ready telemetry export to Scout Collector"}),"\n",(0,i.jsx)(n.li,{children:"Implement custom instrumentation for business-critical operations"}),"\n",(0,i.jsx)(n.li,{children:"Collect and analyze traces, metrics, and performance data"}),"\n",(0,i.jsx)(n.li,{children:"Deploy instrumented Rails applications to development, staging, and\nproduction environments"}),"\n",(0,i.jsx)(n.li,{children:"Troubleshoot common instrumentation issues and optimize performance"}),"\n",(0,i.jsx)(n.li,{children:"Secure sensitive data in telemetry exports"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["\ud83d\udce6 ",(0,i.jsx)(n.strong,{children:"Using older versions?"})," If you're on Ruby 3.0, Ruby 2.7,\nRails 6.1, Rails 5.x, or other legacy versions, see our\n",(0,i.jsx)(n.a,{href:"/instrument/apps/auto-instrumentation/rails-legacy",children:"Legacy Rails Instrumentation Guide"})," for\nversion-specific configurations and known limitations."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Before starting, ensure you have:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Ruby 3.1 or later"})," (CRuby), ",(0,i.jsx)(n.strong,{children:"JRuby 9.3.2.0+"}),", or ",(0,i.jsx)(n.strong,{children:"TruffleRuby 22.1+"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Ruby 3.0 requires pinned gem versions - see the ",(0,i.jsx)(n.a,{href:"/instrument/apps/auto-instrumentation/rails-legacy#ruby-30--rails-61",children:"Legacy Guide"})]}),"\n",(0,i.jsx)(n.li,{children:"Ruby 4.0 is recommended for new applications"}),"\n",(0,i.jsx)(n.li,{children:"JRuby users should use the latest stable release"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Rails 6.0 or later"})," installed","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Rails 7.0+ is recommended for optimal OpenTelemetry support"}),"\n",(0,i.jsx)(n.li,{children:"Rails 6.x is supported but may require additional configuration"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Bundler 2.0+"})," for dependency management"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Scout Collector"})," configured and accessible","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["See ",(0,i.jsx)(n.a,{href:"/instrument/collector-setup/docker-compose-example",children:"Docker Compose Setup"}),"\nfor local development"]}),"\n",(0,i.jsx)(n.li,{children:"Production deployments should use a dedicated Scout Collector instance"}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.li,{children:"Basic understanding of OpenTelemetry concepts (traces, spans, attributes)"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"compatibility-matrix",children:"Compatibility Matrix"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Component"}),(0,i.jsx)(n.th,{children:"Minimum Version"}),(0,i.jsx)(n.th,{children:"Recommended Version"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Ruby (CRuby)"}),(0,i.jsx)(n.td,{children:"3.1.0"}),(0,i.jsx)(n.td,{children:"3.2.0+"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"JRuby"}),(0,i.jsx)(n.td,{children:"9.3.2.0"}),(0,i.jsx)(n.td,{children:"9.4.0+"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"TruffleRuby"}),(0,i.jsx)(n.td,{children:"22.1.0"}),(0,i.jsx)(n.td,{children:"Latest stable"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Rails"}),(0,i.jsx)(n.td,{children:"6.0.0"}),(0,i.jsx)(n.td,{children:"7.1.0+"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Bundler"}),(0,i.jsx)(n.td,{children:"2.0.0"}),(0,i.jsx)(n.td,{children:"2.4.0+"})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"required-packages",children:"Required Packages"}),"\n",(0,i.jsxs)(n.p,{children:["Install the following necessary packages by ",(0,i.jsx)(n.code,{children:"gem install"})," or add it to ",(0,i.jsx)(n.code,{children:"Gemfile"}),"\nand run ",(0,i.jsx)(n.code,{children:"bundle install"}),"."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:"showLineNumbers",children:"gem 'opentelemetry-sdk'\ngem 'opentelemetry-exporter-otlp'\ngem 'opentelemetry-instrumentation-all'\n"})}),"\n",(0,i.jsx)(n.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsx)(n.p,{children:"OpenTelemetry Rails instrumentation can be configured using multiple approaches\ndepending on your deployment requirements and preferences. Choose the method\nthat best fits your application architecture."}),"\n","\n",(0,i.jsxs)(o(),{children:[(0,i.jsxs)(c(),{value:"initializer",label:"Initializer (Recommended)",default:!0,children:[(0,i.jsx)(n.p,{children:"The recommended approach is to create a dedicated OpenTelemetry initializer.\nThis provides the most flexibility and keeps configuration separate from your\napplication bootstrap."}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318')\n )\n )\n )\n\n c.use_all\nend\n\nTRACER = OpenTelemetry.tracer_provider.tracer('rails-app', '1.0.0')\n"})}),(0,i.jsx)(n.p,{children:"This configuration automatically instruments all supported Rails components and\ngems including:"}),(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Rails Core"}),": ActionPack, ActionView, ActiveRecord, ActiveJob, ActionMailer"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"HTTP Clients"}),": Net::HTTP, Faraday, HTTPClient, RestClient"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Databases"}),": PostgreSQL, MySQL, SQLite, MongoDB"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Caching"}),": Redis, Memcached"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Backgroun
1d Jobs"}),": Sidekiq, DelayedJob, Resque"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Web Servers"}),": Rack, Puma, Unicorn"]}),"\n"]})]}),(0,i.jsxs)(c(),{value:"environment",label:"Environment Config",children:[(0,i.jsxs)(n.p,{children:["For applications using ",(0,i.jsx)(n.code,{children:"config/environment.rb"})," for initialization, you can\nconfigure OpenTelemetry before Rails boots:"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/environment.rb"',children:"require_relative 'application'\nrequire 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.use_all\nend\n\nRails.application.initialize!\n"})}),(0,i.jsx)(n.p,{children:"This approach ensures OpenTelemetry is configured before any application code\nruns, which can be useful for capturing early initialization events."})]}),(0,i.jsxs)(c(),{value:"env-vars",label:"Environment Variables",children:[(0,i.jsx)(n.p,{children:"For containerized deployments or environments where configuration is managed\nexternally, you can rely entirely on environment variables:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.use_all\nend\n"})}),(0,i.jsx)(n.p,{children:"With this minimal configuration, use environment variables to control behavior:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",metastring:"showLineNumbers",children:"export OTEL_SERVICE_NAME=rails-app\nexport OTEL_SERVICE_VERSION=1.0.0\nexport OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318\nexport OTEL_TRACES_EXPORTER=otlp\nexport OTEL_METRICS_EXPORTER=otlp\nexport OTEL_LOGS_LEVEL=info\n"})})]}),(0,i.jsxs)(c(),{value:"selective",label:"Selective Instrumentation",children:[(0,i.jsx)(n.p,{children:"If you want to enable only specific instrumentations or disable certain gems,\nuse selective configuration:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n # Enable specific instrumentations only\n c.use 'OpenTelemetry::Instrumentation::Rails'\n c.use 'OpenTelemetry::Instrumentation::ActionPack'\n c.use 'OpenTelemetry::Instrumentation::ActiveRecord'\n c.use 'OpenTelemetry::Instrumentation::Redis'\n c.use 'OpenTelemetry::Instrumentation::Sidekiq'\nend\n"})}),(0,i.jsx)(n.p,{children:"To use all instrumentations except specific ones:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:"showLineNumbers",children:"OpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n # Use all but disable specific instrumentations\n c.use_all({\n 'OpenTelemetry::Instrumentation::ActionCable' => { enabled: false },\n 'OpenTelemetry::Instrumentation::MongoDB' => { enabled: false }\n })\nend\n"})})]})]}),"\n",(0,i.jsx)(n.h3,{id:"configuring-instrumentation-options",children:"Configuring Instrumentation Options"}),"\n",(0,i.jsx)(n.p,{children:"Many instrumentations support additional configuration options:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:"showLineNumbers",children:"OpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n c.use_all({\n 'OpenTelemetry::Instrumentation::ActiveRecord' => {\n enabled: true,\n enable_statement_obfuscation: true, # Sanitize SQL in spans\n db_statement_limit: 2000 # Limit SQL length in attributes\n },\n 'OpenTelemetry::Instrumentation::Redis' => {\n enabled: true,\n db_statement_limit: 500\n },\n 'OpenTelemetry::Instrumentation::Rack' => {\n enabled: true,\n untraced_endpoints: ['/health', '/metrics'] # Skip health checks\n }\n })\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"scout-collector-integration",children:"Scout Collector Integration"}),"\n",(0,i.jsx)(n.p,{children:"When using Scout Collector, configure your Rails application to send telemetry\ndata to the Scout Collector endpoint:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n # Scout Collector endpoint\n scout_endpoint = ENV.fetch('SCOUT_COLLECTOR_ENDPOINT', 'http://localhost:4318')\n\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: scout_endpoint,\n headers: {\n 'x-scout-api-key' => ENV['SCOUT_API_KEY']\n }.compact\n )\n )\n )\n\n c.use_all\nend\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Scout Dashboard Integration"}),": After configuration, your traces will appear\nin the Scout Dashboard. Navigate to the Traces section to view request flows,\nidentify performance bottlenecks, and analyze distributed transactions across\nyour Rails services."]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"production-configuration",children:"Production Configuration"}),"\n",(0,i.jsx)(n.p,{children:"Production deployments require additional configuration for optimal performance,\nreliability, and resource utilization. This section covers production-specific\nsettings and best practices."}),"\n",(0,i.jsx)(n.h3,{id:"batch-span-processor-recommended-for-production",children:"Batch Span Processor (Recommended for Production)"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"BatchSpanProcessor"})," is recommended for production environments as it\nreduces network overhead by batching span exports:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n # C
1onfigure batch span processor for production\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT')\n ),\n max_queue_size: 2048, # Maximum spans in queue\n schedule_delay: 5000, # Export every 5 seconds\n exporter_timeout: 30000, # 30 second timeout\n max_export_batch_size: 512 # Export up to 512 spans at once\n )\n )\n\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Benefits of BatchSpanProcessor:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Reduces network requests by up to 95%"}),"\n",(0,i.jsx)(n.li,{children:"Lower CPU overhead compared to SimpleSpanProcessor"}),"\n",(0,i.jsx)(n.li,{children:"Prevents network saturation during traffic spikes"}),"\n",(0,i.jsx)(n.li,{children:"Configurable batching for optimal throughput"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"resource-attributes",children:"Resource Attributes"}),"\n",(0,i.jsx)(n.p,{children:"Add rich context to all telemetry data with resource attributes:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n # Add resource attributes for production context\n c.resource = OpenTelemetry::SDK::Resources::Resource.create({\n 'environment' => Rails.env,\n 'service.namespace' => ENV.fetch('SERVICE_NAMESPACE', 'production'),\n 'service.instance.id' => Socket.gethostname,\n 'host.name' => Socket.gethostname,\n 'host.type' => ENV.fetch('HOST_TYPE', 'container'),\n 'cloud.provider' => ENV.fetch('CLOUD_PROVIDER', 'aws'),\n 'cloud.region' => ENV.fetch('AWS_REGION', 'us-east-1'),\n 'k8s.pod.name' => ENV['K8S_POD_NAME'],\n 'k8s.namespace.name' => ENV['K8S_NAMESPACE']\n }.compact)\n\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT')\n )\n )\n )\n\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"These attributes help you:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Filter traces by environment, region, or instance"}),"\n",(0,i.jsx)(n.li,{children:"Correlate issues with specific deployments"}),"\n",(0,i.jsx)(n.li,{children:"Analyze performance across different infrastru
1cture"}),"\n",(0,i.jsx)(n.li,{children:"Debug production incidents faster"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"environment-based-configuration",children:"Environment-Based Configuration"}),"\n",(0,i.jsx)(n.p,{children:"Use environment variables to manage configuration across deployments:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n # Service identification\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n # Resource attributes\n c.resource = OpenTelemetry::SDK::Resources::Resource.create({\n 'environment' => Rails.env,\n 'service.instance.id' => Socket.gethostname\n })\n\n # Span processor selection based on environment\n exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318'),\n compression: ENV.fetch('OTEL_EXPORTER_OTLP_COMPRESSION', 'gzip'),\n timeout: ENV.fetch('OTEL_EXPORTER_OTLP_TIMEOUT', '10').to_i\n )\n\n if Rails.env.production?\n # Use batch processor for production\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n exporter,\n max_queue_size: ENV.fetch('OTEL_BSP_MAX_QUEUE_SIZE', '2048').to_i,\n schedule_delay: ENV.fetch('OTEL_BSP_SCHEDULE_DELAY', '5000').to_i,\n max_export_batch_size: ENV.fetch('OTEL_BSP_MAX_EXPORT_BATCH_SIZE', '512').to_i\n )\n )\n else\n # Use simple processor for development (immediate export)\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)\n )\n end\n\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"production-environment-variables",children:"Production Environment Variables"}),"\n",(0,i.jsx)(n.p,{children:"Create a production environment configuration file:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",metastring:'showLineNumbers title=".env.production"',children:"# Service Configuration\nOTEL_SERVICE_NAME=rails-app\nAPP_VERSION=2.1.3\nSERVICE_NAMESPACE=production\n\n# Scout Collector Endpoint\nOTEL_EXPORTER_OTLP_ENDPOINT=https://scout-collector.example.com:4318\nSCOUT_API_KEY=your-scout-api-key\n\n# Batch Processor Settings\nOTEL_BSP_MAX_QUEUE_SIZE=2048\nOTEL_BSP_SCHEDULE_DELAY=5000\nOTEL_BSP_MAX_EXPORT_BATCH_SIZE=512\n\n# Exporter Settings\nOTEL_EXPORTER_OTLP_COMPRESSION=gzip\nOTEL_EXPORTER_OTLP_TIMEOUT=30\n\n# Infrastructure Context\nCLOUD_PROVIDER=aws\nAWS_REGION=us-east-1\nHOST_TYPE=container\n"})}),"\n",(0,i.jsx)(n.h3,{id:"docker-production-configuration",children:"Docker Production Configuration"}),"\n",(0,i.jsx)(n.p,{children:"For containerized Rails applications, configure OpenTelemetry in your Docker setup:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-docker",metastring:'showLineNumbers title="Dockerfile"',children:'FROM ruby:4.0-alpine\n\nWORKDIR /app\n\n# Install dependencies\nCOPY Gemfile Gemfile.lock ./\nRUN bundle install --without development test\n\n# Copy application code\nCOPY . .\n\n# Set production environment\nENV RAILS_ENV=production\nENV OTEL_SERVICE_NAME=rails-app\nENV OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318\n\n# Precompile assets\nRUN bundle exec rails assets:precompile\n\nEXPOSE 3000\n\nCMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]\n'})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",metastring:'showLineNumbers title="docker-compose.yml"',children:'version: \'3.8\'\n\nservices:\n rails-app:\n build: .\n environment:\n OTEL_SERVICE_NAME: rails-app\n APP_VERSION: ${APP_VERSION:-1.0.0}\n OTEL_EXPORTER_OTLP_ENDPOINT: http://scout-collector:4318\n DATABASE_URL: postgres://user:pass@postgres:5432/rails_production\n depends_on:\n - postgres\n - scout-collector\n ports:\n - "3000:3000"\n\n scout-collector:\n image: base14/scout-collector:latest\n ports:\n - "4318:4318"\n\n postgres:\n image: postgres:15-alpine\n environment:\n POSTGRES_PASSWORD: password\n'})}),"\n",(0,i.jsx)(n.h2,{id:"metrics",children:"Metrics"}),"\n",(0,i.jsx)(n.p,{children:"In addition to traces, OpenTelemetry can collect metrics from your Rails\napplication to monitor resource utilization, request rates, error counts, and\ncustom business metrics."}),"\n",(0,i.jsx)(n.h3,{id:"automatic-http-metrics",children:"Automatic HTTP Metrics"}),"\n",(0,i.jsx)(n.p,{children:"The Rails instrumentation automatically collects HTTP-related metrics when you\nconfigure the metrics exporter:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\nrequire 'opentelemetry/instrumentation/all'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n # Configure trace export\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318')\n )\n )\n )\n\n # Enable all instrumentations including metrics\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Automatic metrics include:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"http.server.duration"})," - HTTP request duration histogram"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"http.server.active_requests"})," - Currently active requests"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"http.server.request.size"})," - HTTP request body size"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"http.server.response.size"})," - HTTP response body size"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"custom-business-metrics",children:"Custom Business Metrics"}),"\n",(0,i.jsx)(n.p,{children:"Create custom metrics to track business-specific events and KPIs:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/services/order_service.rb"',children:"class OrderService\n def initialize\n @meter = OpenTelemetry.meter_provider.meter('order-service', '1.0.0')\n\n # Create custom metrics\n @orders_created = @meter.create_counter(\n 'orders.created',\n unit: 'orders',\n description: 'Total number of orders created'\n )\n\n @order_value = @meter.create_histogram(\n 'orders.value',\n unit: 'USD',\n description: 'Distribution of order values'\n )\n\n @active_orders = @meter.create_up_down_counter(\n 'orders.active',\n unit: 'orders',\n description: 'Currently active orders'\n )\n end\n\n def create_order(params)\n order = Order.create!(params)\n\n # Increment orders created counter\n @orders_created.add(1, attributes: {\n 'order.type' => order.order_type,\n 'user.tier' => order.user.tier\n })\n\n # Record order value\n @order_value.record(order.total_amount, attributes: {\n 'order.type' => order.order_type\n })\n\n # Increment active orders\n @active_orders.add(1)\n\n order\n rescue => e\n @orders_created.add(1, attributes: {\n 'order.status' => 'failed',\n 'error.type' => e.class.name\n })\n raise\n end\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"viewing-metrics-in-scout-dashboard",children:"Viewing Metrics in Scout Dashboard"}),"\n",(0,i.jsx)(n.p,{children:"After configuring metrics export, navigate to the Scout Dashboard to:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"View HTTP request rate and latency percentiles (p50, p95, p99)"}),"\n",(0,i.jsx)(n.li,{children:"Monitor error rates and status code distributions"}),"\n",(0,i.jsx)(n.li,{children:"Track custom business metrics in real-time"}),"\n",(0,i.jsx)(n.li,{children:"Create alerts based on metric thresholds"}),"\n",(0,i.jsx)(n.li,{children:"Build custom dashboards combining metrics and traces"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"activerecord-database-monitoring",children:"ActiveRecord Database Monitoring"}),"\n",(0,i.jsx)(n.p,{children:"OpenTelemetry automatically instruments ActiveRecord to provide comprehensive\ndatabase query monitoring and performance insights."}),"\n",(0,i.jsx)(n.h3,{id:"automatic-query-tracing",children:"Automatic Query Tracing"}),"\n",(0,i.jsx)(n.p,{children:"Once configured, all ActiveRecord queries are automatically traced with detailed\ninformation:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"# This query is automatically instrumented\nusers = User.where(active: true).includes(:posts).limit(10)\n\n# The trace will show:\n# - SQL query statement\n# - Database name and operation\n# - Query duration\n# - Connection pool metrics\n"})}),"\n",(0,i.jsx)(n.h3,{id:"configuring-activerecord-instrumentation",children:"Configuring ActiveRecord Instrumentation"}),"\n",(0,i.jsx)(n.p,{children:"Fine-tune ActiveRecord instrumentation for security and performance:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n c.use_all({\n 'OpenTelemetry::Instrumentation::ActiveRecord' => {\n enabled: true,\n # Obfuscate SQL parameter values for security\n enable_statement_obfuscation: true,\n # Limit SQL statement length in spans\n db_statement_limit: 2000,\n # Include SQL comments in traces\n enable_sql_obfuscation: false\n }\n })\n\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(\n OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT')\n )\n )\n )\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"ActiveRecord span attributes include:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.system"})," - Database type (postgresql, mysql, sqlite)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.name"})," - Database name"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.statement"})," - SQL query"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.operation"})," - Operation type (SELECT, INSERT, UPDATE, DELETE)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.sql.table"})," - Table name"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"db.connection.pool.name"})," - Connection pool identifier"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"detecting-n1-queries",children:"Detecting N+1 Queries"}),"\n",(0,i.jsx)(n.p,{children:"Use OpenTelemetry traces to identify and fix N+1 query problems:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"# Bad: N+1 query pattern (visible in traces as multiple DB spans)\nposts = Post.limit(10)\nposts.each do |post|\n puts post.author.name # Triggers 10 additional queries\nend\n\n# Good: Optimized with eager loading (single query in trace)\nposts = Post.includes(:author).limit(10)\nposts.each do |post|\n puts post.author.name # No additional queries\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"In Scout Dashboard, N+1 queries will appear as:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Multiple identical database spans within a single request trace"}),"\n",(0,i.jsx)(n.li,{children:"High span count for simple operations"}),"\n",(0,i.jsx)(n.li,{children:"Repeated query patterns with different parameters"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"custom-database-spans",children:"Custom Database Spans"}),"\n",(0,i.jsx)(n.p,{children:"Add custom instrumentation for complex database operations:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/services/report_generator.rb"',children:"class ReportGenerator\n def initialize\n @tracer = OpenTelemetry.tracer_provider.tracer('report-generator', '1.0.0')\n end\n\n def generate_monthly_report(month)\n @tracer.in_span('generate_monthly_report',\n attributes: { 'report.month' => month },\n kind: :internal) do |span|\n\n @tracer.in_span('aggregate_sales_data') do\n sales_data = aggregate_sales(month)\n span.add_event('Sales data aggregated', attributes: {\n 's
1ales.total' => sales_data.sum,\n 'sales.count' => sales_data.count\n })\n end\n\n @tracer.in_span('generate_charts') do\n charts = generate_charts(month)\n span.add_event('Charts generated', attributes: {\n 'charts.count' => charts.length\n })\n end\n\n span.set_status(OpenTelemetry::Trace::Status.ok)\n span.add_attributes({ 'report.generated_at' => Time.current.iso8601 })\n end\n end\nend\n"})}),"\n",(0,i.jsx)(n.h2,{id:"custom-manual-instrumentation",children:"Custom Manual Instrumentation"}),"\n",(0,i.jsx)(n.p,{children:"While automatic instrumentation covers most Rails components, you can add\ncustom instrumentation for business logic, external API calls, or\nperformance-critical code paths."}),"\n",(0,i.jsx)(n.h3,{id:"creating-custom-spans-for-business-logic",children:"Creating Custom Spans for Business Logic"}),"\n",(0,i.jsx)(n.p,{children:"Instrument important business operations in controllers and services:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/controllers/orders_controller.rb"',children:"class OrdersController < ApplicationController\n before_action :set_tracer\n\n def create\n @tracer.in_span('create_order',\n attributes: {\n 'user.id' => current_user.id,\n 'order.items_count' => params[:items].length\n },\n kind: :server) do |span|\n\n span.add_event('Validating order data')\n\n @order = Order.new(order_params)\n\n if @order.save\n span.add_event('Order saved successfully', attributes: {\n 'order.id' => @order.id,\n 'order.total' => @order.total_amount\n })\n\n @tracer.in_span('process_payment') do |payment_span|\n payment_result = PaymentService.charge(current_user, @order.total_amount)\n payment_span.add_attributes({\n 'payment.provider' => payment_result.provider,\n 'payment.status' => payment_result.status\n })\n end\n\n @tracer.in_span('send_confirmation_email') do\n OrderMailer.confirmation(@order).deliver_later\n end\n\n span.set_status(OpenTelemetry::Trace::Status.ok)\n render json: @order, status: :created\n else\n span.add_event('Order validation failed', attributes: {\n 'validation.errors' => @order.errors.full_messages\n })\n span.set_status(\n OpenTelemetry::Trace::Status.error(\"Validation failed: #{@order.errors.full_messages.join(', ')}\")\n )\n render json: @order.errors, status: :unprocessable_entity\n end\n end\n end\n\n private\n\n def set_tracer\n @tracer = OpenTelemetry.tracer_provider.tracer('orders-controller', '1.0.0')\n end\n\n def order_params\n params.require(:order).permit(:items, :shipping_address, :payment_method)\n end\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"adding-attributes-to-current-spans",children:"Adding Attributes to Current Spans"}),"\n",(0,i.jsx)(n.p,{children:"Enrich existing spans with additional context:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/controllers/application_controller.rb"',children:"class ApplicationController < ActionController::Base\n before_action :add_user_context_to_trace\n\n private\n\n def add_user_context_to_trace\n return unless current_user\n\n # Get the current span\n current_span = OpenTelemetry::Trace.current_span\n\n # Add user context attributes\n current_span.add_attributes({\n 'user.id' => current_user.id,\n 'user.email' => current_user.email,\n 'user.tier' => current_user.subscription_tier,\n 'user.authenticated' => true\n })\n end\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"exception-handling-and-error-tracking",children:"Exception Handling and Error Tracking"}),"\n",(0,i.jsx)(n.p,{children:"Capture exceptions in custom spans:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/services/external_api_client.rb"',children:"class ExternalApiClient\n def initialize\n @tracer = OpenTelemetry.tracer_provider.tracer('external-api-cl
1ient', '1.0.0')\n end\n\n def fetch_data(endpoint)\n @tracer.in_span('external_api_call',\n attributes: {\n 'http.url' => endpoint,\n 'http.method' => 'GET'\n },\n kind: :client) do |span|\n\n begin\n response = HTTP.get(endpoint)\n\n span.add_attributes({\n 'http.status_code' => response.code,\n 'http.response_size' => response.body.length\n })\n\n if response.code == 200\n span.set_status(OpenTelemetry::Trace::Status.ok)\n JSON.parse(response.body)\n else\n span.set_status(\n OpenTelemetry::Trace::Status.error(\"HTTP #{response.code}\")\n )\n raise \"API request failed with status #{response.code}\"\n end\n\n rescue => e\n span.record_exception(e)\n span.set_status(\n OpenTelemetry::Trace::Status.error(\"Exception: #{e.message}\")\n )\n raise\n end\n end\n end\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"using-semantic-conventions",children:"Using Semantic Conventions"}),"\n",(0,i.jsx)(n.p,{children:"Follow OpenTelemetry semantic conventions for consistent attribute naming:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:"showLineNumbers",children:"# HTTP semantic conventions\nspan.add_attributes({\n 'http.method' => 'POST',\n 'http.url' => 'https://api.example.com/users',\n 'http.status_code' => 201,\n 'http.request.header.content_type' => 'application/json'\n})\n\n# Database semantic conventions\nspan.add_attributes({\n 'db.system' => 'postgresql',\n 'db.name' => 'production',\n 'db.statement' => 'SELECT * FROM users WHERE active = true',\n 'db.operation' => 'SELECT'\n})\n\n# Messaging semantic conventions\nspan.add_attributes({\n 'messaging.system' => 'sidekiq',\n 'messaging.destination' => 'orders_queue',\n 'messaging.operation' => 'process'\n})\n"})}),"\n",(0,i.jsx)(n.h2,{id:"running-your-instrumented-application",children:"Running Your Instrumented Application"}),"\n",(0,i.jsx)(n.h3,{id:"development-mode",children:"Development Mode"}),"\n",(0,i.jsx)(n.p,{children:"For local development, use console output to verify instrumentation:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app-dev'\n\n if Rails.env.development?\n # Use console exporter for debugging\n require 'opentelemetry/exporter/otlp'\n\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(\n OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new\n )\n )\n end\n\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"Start your Rails server:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"bundle exec rails server\n"})}),"\n",(0,i.jsx)(n.p,{children:"You'll see span output in the console for each request:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:'#<struct OpenTelemetry::SDK::Trace::SpanData\n name="GET /users",\n kind=:server,\n status=#<OpenTelemetry::Trace::Status:0x00007f8b1c0a3e80 @code=1, @description="">,\n attributes={"http.method"=>"GET", "http.target"=>"/users", "http.status_code"=>200}>\n'})}),"\n",(0,i.jsx)(n.h3,{id:"production-mode",children:"Production Mode"}),"\n",(0,i.jsx)(n.p,{children:"For production deployments, ensure the Scout Collector endpoint is properly configured:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Set environment variables\nexport OTEL_SERVICE_NAME=rails-app-production\nexport APP_VERSION=2.1.0\nexport OTEL_EXPORTER_OTLP_ENDPOINT=https://scout-collector.example.com:4318\nexport SCOUT_API_KEY=your-scout-api-key\nexport RAILS_ENV=production\n\n# Start Rails server\nbundle exec puma -C config/puma.rb\n"})}),"\n",(0,i.jsx)(n.h3,{id:"docker-deployment",children:"Docker Deployment"}),"\n",(0,i.jsx)(n.p,{children:"Run your instrumented Rails application in Docker:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Build the image\ndocker build -t rails-app:latest .\n\n# Run with Scout Collector\ndocker run -d \\\n --name rails-app \\\n -e OTEL_SERVICE_NAME=rails-app \\\n -e OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318 \\\n -e DATABASE_URL=postgres://user:pass@db:5432/production \\\n -p 3000:3000 \\\n rails-app:latest\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Or use Docker Compose (see ",(0,i.jsx)(n.a,{href:"#production-configuration",children:"Production Configuration"}),"\nsection for complete example)."]}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,i.jsx)(n.h3,{id:"verifying-opentelemetry-installation",children:"Verifying OpenTelemetry Installation"}),"\n",(0,i.jsx)(n.p,{children:"Test your OpenTelemetry configuration in the Rails console:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"# Start Rails console\nbundle exec rails console\n\n# Create a test span\ntracer = OpenTelemetry.tracer_provider.tracer('test')\n\ntracer.in_span('test_span') do |span|\n span.add_attributes({'test' => 'value'})\n puts \"OpenTelemetry is working!\"\n puts \"Tracer provider: #{OpenTelemetry.tracer_provider.class}\"\n puts \"Active span: #{span.name}\"\nend\n\n# Check instrumented libraries\nOpenTelemetry.instrumentation_registry.each do |instrumentation|\n puts \"#{instrumentation.name}: #{instrumentation.installed? ? 'INSTALLED' : 'NOT INSTALLED'}\"\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"Expected output:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"OpenTelemetry is working!\nTracer provider: OpenTelemetry::SDK::Trace::TracerProvider\nActive span: test_span\nOpenTelemetry::Instrumentation::ActionPack: INSTALLED\nOpenTelemetry::Instrumentation::ActiveRecord: INSTALLED\nOpenTelemetry::Instrumentation::Rails: INSTALLED\n"})}),"\n",(0,i.jsx)(n.h3,{id:"health-check-endpoint",children:"Health Check Endpoint"}),"\n",(0,i.jsx)(n.p,{children:"Create a health check endpoint to verify telemetry export:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/routes.rb"',children:"Rails.application.routes.draw do\n get '/health', to: 'health#check'\n get '/health/telemetry', to: 'health#telemetry'\nend\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="app/controllers/health_controller.rb"',children:"class HealthController < ApplicationController\n def check\n render json: {\n status: 'ok',\n timestamp: Time.current,\n environment: Rails.env\n }\n end\n\n def telemetry\n tracer = OpenTelemetry.tracer_provider.tracer('health_check')\n\n tracer.in_span('telemetry_health_check') do |span|\n span.add_attributes({\n 'service.name' => ENV.fetch('OTEL_SERVICE_NAME', 'rails-app'),\n 'service.version' => ENV.fetch('APP_VERSION', '1.0.0'),\n 'rails.environment' => Rails.env,\n 'ruby.version' => RUBY_VERSION\n })\n\n render json: {\n status: 'ok',\n telemetry: {\n tracer_provider: OpenTelemetry.tracer_provider.class.name,\n instrumented_gems: instrumented_gems_list\n }\n }\n end\n end\n\n private\n\n def instrumented_gems_list\n OpenTelemetry.instrumentation_registry.map do |i|\n { name: i.name, installed: i.installed? }\n end\n end\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"Test the endpoint:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"curl http://localhost:3000/health/telemetry\n"})}),"\n",(0,i.jsx)(n.h3,{id:"debug-mode",children:"Debug Mode"}),"\n",(0,i.jsx)(n.p,{children:"Enable debug logging to troubleshoot instrumentation issues:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"export OTEL_LOG_LEVEL=debug\nbundle exec rails server\n"})}),"\n",(0,i.jsx)(n.p,{children:"Or configure in the initializer:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\n\n# Enable debug logging\nOpenTelemetry.logger.level = Logger::DEBUG if Rails.env.development?\n\nOpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n c.use_all\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,i.jsx)(n.h4,{id:"issue-no-traces-appearing-in-scout-dashboard",children:"Issue: No traces appearing in Scout Dashboard"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Solutions:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Verify Scout Collector endpoint is reachable:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"curl -v http://scout-collector:4318/v1/traces\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check environment variables:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"echo $OTEL_EXPORTER_OTLP_ENDPOINT\necho $OTEL_SERVICE_NAME\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Enable debug logging and check for export errors"}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Verify network connectivity between Rails app and Scout Collector"}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"issue-missing-database-query-spans",children:"Issue: Missing database query spans"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Solutions:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Ensure ",(0,i.jsx)(n.code,{children:"opentelemetry-instrumentation-active_record"})," is installed"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Verify ActiveRecord instrumentation is enabled:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"OpenTelemetry.instrumentation_registry.lookup('OpenTelemetry::Instrumentation::ActiveRecord').installed?\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Check that ",(0,i.jsx)(n.code,{children:"c.use_all"})," or specific ActiveRecord instrumentation is configured"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"issue-high-memory-usage",children:"Issue: High memory usage"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Solutions:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["Use ",(0,i.jsx)(n.code,{children:"BatchSpanProcessor"})," instead of ",(0,i.jsx)(n.code,{children:"SimpleSpanProcessor"})]}),"\n",(0,i.jsxs)(n.li,{children:["Reduce ",(0,i.jsx)(n.code,{children:"max_queue_size"})," in BatchSpanProcessor configuration"]}),"\n",(0,i.jsxs)(n.li,{children:["Limit span attribute sizes with ",(0,i.jsx)(n.code,{children:"db_statement_limit"})]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"issue-performance-degradation",children:"Issue: Performance degradation"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Solutions:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["Use ",(0,i.jsx)(n.code,{children:"enable_statement_obfuscation"})," to reduce attribute processing"]}),"\n",(0,i.jsxs)(n.li,{children:["Skip health check endpoints with ",(0,i.jsx)(n.code,{children:"untraced_endpoints"})]}),"\n",(0,i.jsx)(n.li,{children:"Verify BatchSpanProcessor is configured (not SimpleSpanProcessor)"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"security-considerations",children:"Security Considerations"}),"\n",(0,i.jsx)(n.h3,{id:"protecting-sensitive-data",children:"Protecting Sensitive Data"}),"\n",(0,i.jsx)(n.p,{children:"Avoid adding sensitive information to span attributes:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"# Bad - exposes sensitive data\nspan.add_attributes({\n 'user.password' => user.password, # Never include passwords!\n 'credit_card.number' => params[:cc_number], # Never include payment data!\n 'user.ssn' => user.social_security_number # Never include PII!\n})\n\n# Good - uses safe identifiers\nspan.add_attributes({\n 'user.id' => user.id,\n 'user.role' => user.role,\n 'payment.provider' => 'stripe',\n 'payment.status' => 'completed'\n})\n"})}),"\n",(0,i.jsx)(n.h3,{id:"sanitizing-sql-statements",children:"Sanitizing SQL Statements"}),"\n",(0,i.jsx)(n.p,{children:"Enable SQL obfuscation to remove sensitive parameter values:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializer
1s/opentelemetry.rb"',children:"OpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n c.use_all({\n 'OpenTelemetry::Instrumentation::ActiveRecord' => {\n enabled: true,\n # Obfuscate SQL parameters\n enable_statement_obfuscation: true,\n # Limit SQL statement length\n db_statement_limit: 2000\n }\n })\nend\n"})}),"\n",(0,i.jsx)(n.p,{children:"Before obfuscation:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sql",children:"SELECT * FROM users WHERE email = '[email protected]' AND password = 'secret123'\n"})}),"\n",(0,i.jsx)(n.p,{children:"After obfuscation:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sql",children:"SELECT * FROM users WHERE email = ? AND password = ?\n"})}),"\n",(0,i.jsx)(n.h3,{id:"filtering-sensitive-http-headers",children:"Filtering Sensitive HTTP Headers"}),"\n",(0,i.jsx)(n.p,{children:"Avoid capturing sensitive HTTP headers:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'showLineNumbers title="config/initializers/opentelemetry.rb"',children:"OpenTelemetry::SDK.configure do |c|\n c.service_name = 'rails-app'\n\n c.use_all({\n 'OpenTelemetry::Instrumentation::Rack' => {\n enabled: true,\n # Don't capture these headers\n untraced_endpoints: ['/health', '/metrics'],\n # Additional security configuration\n allowed_request_headers: ['content-type', 'accept'],\n allowed_response_headers: ['content-type']\n }\n })\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"compliance-considerations",children:"Compliance Considerations"}),"\n",(0,i.jsx)(n.p,{children:"For applications handling regulated data (GDPR, HIPAA, PCI-DSS):"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Never include personally identifiable information (PII) in spans"}),"\n",(0,i.jsx)(n.li,{children:"Use hashed or anonymized user identifiers"}),"\n",(0,i.jsx)(n.li,{children:"Implement data retention policies in Scout Dashboard"}),"\n",(0,i.jsx)(n.li,{children:"Configure SQL obfuscation for all database queries"}),"\n",(0,i.jsx)(n.li,{children:"Audit span attributes regularly for sensitive data leaks"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"performance-considerations",children:"Performance Considerations"}),"\n",(0,i.jsx)(n.h3,{id:"expected-performance-impact",children:"Expected Performance Impact"}),"\n",(0,i.jsx)(n.p,{children:"OpenTelemetry instrumentation adds minimal overhead to Rails applications:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Average latency increase"}),": 1-3ms per request"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"CPU overhead"}),": Less than 2% in production with BatchSpanProcessor"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Memory overhead"}),": ~50-100MB depending on queue size and traffic"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Impact varies based on:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Number of enabled instrumentations"}),"\n",(0,i.jsx)(n.li,{children:"Span processor type (Batch vs Simple)"}),"\n",(0,i.jsx)(n.li,{children:"Application request volume"}),"\n",(0,i.jsx)(n.li,{children:"Complexity of database queries"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"optimization-best-practices",children:"Optimization Best Practices"}),"\n",(0,i.jsx)(n.h4,{id:"1-use-batchspanprocessor-in-production",children:"1. Use BatchSpanProcessor in Production"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"# Good - batches exports, low overhead\nc.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)\n)\n\n# Bad - exports every span immediately, high overhead\nc.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)\n)\n"})}),"\n",(0,i.jsx)(n.h4,{id:"2-skip-non-critical-endpoints",children:"2. Skip Non-Critical Endpoints"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"c.use_all({\n 'OpenTelemetry::Instrumentation::Rack' => {\n untraced_endpoints: ['/health', '/metrics', '/favicon.ico']\n }\n})\n"})}),"\n",(0,i.jsx)(n.h4,{id:"3-conditional-span-recording",children:"3. Conditional Span Recording"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"span = OpenTelemetry::Trace.current_span\n\n# Only add expensive attributes if span is being recorded\nif span.recording?\n span.add_attributes(expensive_computation())\nend\n"})}),"\n",(0,i.jsx)(n.h4,{id:"4-limit-attribute-sizes",children:"4. Limit Attribute Sizes"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"c.use_all({\n 'OpenTelemetry::Instrumentation::ActiveRecord' => {\n db_statement_limit: 2000 # Truncate long SQL statements\n }\n})\n"})}),"\n",(0,i.jsx)(n.h2,{id:"frequently-asked-questions",children:"Frequently Asked Questions"}),"\n",(0,i.jsx)(n.h3,{id:"what-is-the-performance-impact-of-opentelemetry-on-rails-apps",children:"What is the performance impact of OpenTelemetry on Rails apps?"}),"\n",(0,i.jsxs)(n.p,{children:["With ",(0,i.jsx)(n.code,{children:"BatchSpanProcessor"}),", expect roughly 1-3ms per request, a small CPU\nincrease, and 10-30MB of additional memory. Sampling reduces it further in\nhigh-traffic apps."]}),"\n",(0,i.jsx)(n.h3,{id:"which-rails-versions-are-supported",children:"Which Rails versions are supported?"}),"\n",(0,i.jsxs)(n.p,{children:["OpenTelemetry supports Rails 6.0+ with Ruby 3.1+ (latest gems). Ruby 3.0\nrequires pinned gem versions - see the\n",(0,i.jsx)(n.a,{href:"/instrument/apps/auto-instrumentation/rails-legacy#ruby-30--rails-61",children:"Legacy Guide"}),". Rails 7.0+ with\nRuby 4.0 is recommended for optimal compatibility and performance. See\nthe ",(0,i.jsx)(n.a,{href:"#prerequisites",children:"Prerequisites"})," section for detailed version\ncompatibility."]}),"\n",(0,i.jsx)(n.h3,{id:"can-i-use-opentelemetry-with-sidekiq-or-other-background-job-processors",children:"Can I use OpenTelemetry with Sidekiq or other background job processors?"}),"\n",(0,i.jsxs)(n.p,{children:["Yes! The ",(0,i.jsx)(n.code,{children:"opentelemetry-instrumentation-all"})," gem includes automatic instrumentation\nfor Sidekiq, DelayedJob, and Resque. Backgroun
1d jobs are traced automatically,\nand you can see the complete trace from HTTP request through asynchronous job\nprocessing in Scout Dashboard."]}),"\n",(0,i.jsx)(n.h3,{id:"is-opentelemetry-compatible-with-rack-middleware",children:"Is OpenTelemetry compatible with Rack middleware?"}),"\n",(0,i.jsx)(n.p,{children:"Yes, OpenTelemetry instruments at the Rack level, making it compatible with all\nRack-based frameworks and middleware. Custom Rack middleware will appear in\ntraces automatically."}),"\n",(0,i.jsx)(n.h3,{id:"can-i-use-opentelemetry-alongside-other-apm-tools",children:"Can I use OpenTelemetry alongside other APM tools?"}),"\n",(0,i.jsx)(n.p,{children:"Yes, OpenTelemetry can run alongside tools like New Relic or DataDog during\nmigration periods. However, running multiple APM agents simultaneously will\nmultiply the performance overhead, so plan your migration carefully."}),"\n",(0,i.jsx)(n.h3,{id:"how-do-i-handle-multi-tenant-rails-applications",children:"How do I handle multi-tenant Rails applications?"}),"\n",(0,i.jsx)(n.p,{children:"Add tenant context to spans using attributes:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"current_span.add_attributes({\n 'tenant.id' => current_tenant.id,\n 'tenant.name' => current_tenant.name\n})\n"})}),"\n",(0,i.jsx)(n.p,{children:"Then filter traces by tenant in Scout Dashboard."}),"\n",(0,i.jsx)(n.h3,{id:"whats-the-difference-between-traces-and-metrics",children:"What's the difference between traces and metrics?"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Traces"})," show the complete request flow through your application with timing\ndetails for each operation. Use traces to debug slow requests and understand\ndistributed transactions."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Metrics"})," provide aggregated statistics over time (request rate, error rate,\nlatency percentiles). Use metrics for monitoring overall application health\nand setting alerts."]}),"\n",(0,i.jsx)(n.h3,{id:"how-do-i-monitor-n1-database-queries",children:"How do I monitor N+1 database queries?"}),"\n",(0,i.jsx)(n.p,{children:"OpenTelemetry traces automatically expose N+1 queries as multiple database\nspans within a single request trace. In Scout Dashboard, look for repeated\nquery patterns or high span counts for simple operations."}),"\n",(0,i.jsx)(n.h3,{id:"can-i-customize-which-gems-are-instrumented",children:"Can I customize which gems are instrumented?"}),"\n",(0,i.jsxs)(n.p,{children:["Yes! Use selective instrumentation instead of ",(0,i.jsx)(n.code,{children:"c.use_all"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"c.use 'OpenTelemetry::Instrumentation::Rails'\nc.use 'OpenTelemetry::Instrumentation::ActiveRecord'\nc.use 'OpenTelemetry::Instrumentation::Redis'\n"})}),"\n",(0,i.jsx)(n.p,{children:"Or disable specific instrumentations:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",children:"c.use_all({\n 'OpenTelemetry::Instrumentation::MongoDB' => { enabled: false }\n})\n"})}),"\n",(0,i.jsx)(n.h3,{id:"can-opentelemetry-detect-n1-queries-in-rails",children:"Can OpenTelemetry detect N+1 queries in Rails?"}),"\n",(0,i.jsx)(n.p,{children:"Yes. OpenTelemetry traces each ActiveRecord query as a separate span. An N+1\nquery shows up in base14 Scout as many sequential database spans under a\nsingle parent span."}),"\n",(0,i.jsx)(n.h2,{id:"whats-next",children:"What's Next?"}),"\n",(0,i.jsx)(n.p,{children:"Now that your Rails application is instrumented with OpenTelemetry, explore\nthese resources to maximize your observability:"}),"\n",(0,i.jsx)(n.h3,{id:"advanced-topics",children:"Advanced Topics"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/instrument/apps/custom-instrumentation/ruby",children:"Custom Ruby Instrumentation"})})," - Deep dive\ninto manual tracing, custom spans, and advanced instrumentation patterns"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/instrument/component/collecting-postgres-telemetry",children:"PostgreSQL Monitoring Best Practices"})})," - Optimize\ndatabase observability with connection pooling metrics and query performance analysis"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/instrument/component/collecting-redis-telemetry",children:"Redis Instrumentation"})})," - Monitor caching\nperformance and identify slow Redis operations"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"scout-platform-features",children:"Scout Platform Features"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/guides/creating-alerts-with-logx",children:"Creating Alerts"})})," - Set up\nintelligent alerts for error rates, latency thresholds, and custom metrics"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/guides/create-your-first-dashboard",children:"Dashboard Creation"})})," - Build\ncustom dashboards combining traces, metrics, and business KPIs"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"deployment-and-operations",children:"Deployment and Operations"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.a,{href:"/instrument/collector-setup/docker-compose-example",children:"Docker Compose Setup"})})," -\nSet up Scout Collector for local development and testing"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"complete-example",children:"Complete Example"}),"\n",(0,i.jsx)(n.p,{children:"Here's a complete working example of a Rails 7 application with OpenTelemetry instrumentation:"}),"\n",(0,i.jsx)(n.h3,{id:"gemfile",children:"Gemfile"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'title="Gemfile"',children:"source 'https://rubygems.org'\n\nruby '3.2.0'\n\ngem 'rails', '~> 7.1.0'\ngem 'pg', '~> 1.5'\ngem 'puma', '~> 6.0'\n\n# OpenTelemetry gems\ngem 'opentelemetry-sdk'\ngem 'opentelemetry-exporter-otlp'\ngem 'opentelemetry-instrumentation-all'\n\ngroup :development, :test do\n gem 'debug'\n gem 'rspec-rails'\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"opentelemetry-initializer",children:"OpenTelemetry Initializer"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'title="config/initializer
1s/opentelemetry.rb"',children:"require 'opentelemetry/sdk'\nrequire 'opentelemetry/exporter/otlp'\n\nOpenTelemetry::SDK.configure do |c|\n # Service identification\n c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'rails-app')\n c.service_version = ENV.fetch('APP_VERSION', '1.0.0')\n\n # Resource attributes\n c.resource = OpenTelemetry::SDK::Resources::Resource.create({\n 'environment' => Rails.env,\n 'service.instance.id' => Socket.gethostname\n })\n\n # Configure exporter\n exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(\n endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318')\n )\n\n # Use batch processor for production, simple for development\n if Rails.env.production?\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)\n )\n else\n c.add_span_processor(\n OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)\n )\n end\n\n # Enable all instrumentations\n c.use_all\nend\n\n# Create global tracer\nTRACER = OpenTelemetry.tracer_provider.tracer('rails-app', '1.0.0')\n"})}),"\n",(0,i.jsx)(n.h3,{id:"instrumented-controller",children:"Instrumented Controller"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-ruby",metastring:'title="app/controllers/api/v1/orders_controller.rb"',children:"module Api\n module V1\n class OrdersController < ApplicationController\n before_action :set_tracer\n\n def create\n @tracer.in_span('create_order') do |span|\n span.add_attributes({\n 'user.id' => current_user.id,\n 'order.items_count' => order_params[:items].length\n })\n\n @order = Order.create!(order_params)\n\n span.add_event('Order created', attributes: {\n 'order.id' => @order.id,\n 'order.total' => @order.total_amount\n })\n\n render json: @order, status: :created\n end\n rescue => e\n OpenTelemetry::Trace.current_span.record_exception(e)\n render json: { error: e.message }, status: :unprocessable_entity\n end\n\n private\n\n def set_tracer\n @tracer = OpenTelemetry.tracer_provider.tracer('api', '1.0.0')\n end\n\n def order_params\n params.require(:order).permit(:items, :total_amount)\n end\n end\n end\nend\n"})}),"\n",(0,i.jsx)(n.h3,{id:"environment-variables",children:"Environment Variables"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",metastring:'title=".env.production"',children:"OTEL_SERVICE_NAME=rails-app-production\nAPP_VERSION=1.0.0\nOTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318\nRAILS_ENV=production\nDATABASE_URL=postgres://user:pass@db:5432/production\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This complete example is available in our ",(0,i.jsx)(n.a,{href:"https://github.com/base-14/examples/tree/main/ruby/rails8-sqlite",children:"GitHub examples repository"}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"references",children:"References"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.a,{href:"https://opentelemetry.io/
1docs/concepts/signals/traces/",children:"Official Traces Documentation"})}),"\n",(0,i.jsx)(n.h2,{id:"related-guides",children:"Related Guides"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/instrument/collector-setup/docker-compose-example",children:"Docker Compose Setup"})," - Set\nup collector for local development"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/instrument/apps/custom-instrumentation/ruby",children:"Ruby Custom Instrumentation"})," - Manual\nspans and advanced patterns"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/instrument/apps/auto-instrumentation/",children:"All framework guides"})," -\nAuto-instrumentation overview for every language"]}),"\n"]})]})}function g(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},98362(e,n,r){var t=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(e){const n=(0,a.default)(),r=(0,c.useTabsContextValue)(e);return i.default.createElement(c.TabsProvider,{value:r,key:String(n)},i.default.createElement(m,{className:e.className},(0,c.sanitizeTabsChildren)(e.children)))};const i=t(r(96540)),s=r(95068),a=t(r(92303)),o=t(r(71508)),l=r(80237),c=r(7034),d=t(r(53970));function u({className:e}){const{selectedValue:n,selectValue:r,tabValues:t,block:s}=(0,c.useTabs)(),a=[],{blockElementScrollPositionUntilNextRender:u}=(0,l.useScrollPositionBlocker)(),p=e=>{const i=e.currentTarget,s=a.indexOf(i),o=t[s].value;o!==n&&(u(i),r(o))},m=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const r=a.indexOf(e.currentTarget)+1;n=a[r]??a[0];break}case"ArrowLeft":{const r=a.indexOf(e.currentTarget)-1;n=a[r]??a[a.length-1];break}}n?.focus()};return i.default.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.default)("tabs",{"tabs--block":s},e)},t.map(({value:e,label:r,attributes:t})=>i.default.createElement("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,key:e,ref:e=>{a.push(e)},onKeyDown:m,onClick:p,...t,className:(0,o.default)("tabs__item",d.default.tabItem,t?.className,{"tabs__item--active":n===e})},r??e)))}function p({children:e}){return i.default.createElement("div",{className:"margin-top--md"},e)}function m({className:e,children:n}){return i.default.createElement("div",{className:(0,o.default)(s.ThemeClassNames.tabs.container,"tabs-container",d.default.tabList)},i.default.createElement(u,{className:e}),i.default.createElement(p,null,n))}},53970(e,n,r){r.r(n);r.d(n,["default",0,{tabList:"tabList_zI5C",tabItem:"tabItem_yeRP"}])}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.