{"id":31581195,"url":"https://github.com/joyfulprogramming/observable","last_synced_at":"2025-10-05T21:54:42.960Z","repository":{"id":235057461,"uuid":"789990448","full_name":"JoyfulProgramming/observable","owner":"JoyfulProgramming","description":"Instrument method calls with OpenTelemetry","archived":false,"fork":false,"pushed_at":"2025-10-04T22:20:55.000Z","size":126,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-04T23:33:07.378Z","etag":null,"topics":["open-telemetry","opentelemetry","rails","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoyfulProgramming.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-22T04:02:36.000Z","updated_at":"2025-10-04T22:20:58.000Z","dependencies_parsed_at":"2025-09-27T08:47:16.797Z","dependency_job_id":null,"html_url":"https://github.com/JoyfulProgramming/observable","commit_stats":null,"previous_names":["joyfulprogramming/observable"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/JoyfulProgramming/observable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoyfulProgramming%2Fobservable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoyfulProgramming%2Fobservable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoyfulProgramming%2Fobservable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoyfulProgramming%2Fobservable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoyfulProgramming","download_url":"https://codeload.github.com/JoyfulProgramming/observable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoyfulProgramming%2Fobservable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278526245,"owners_count":26001326,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["open-telemetry","opentelemetry","rails","ruby"],"created_at":"2025-10-05T21:54:41.975Z","updated_at":"2025-10-05T21:54:42.950Z","avatar_url":"https://github.com/JoyfulProgramming.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Observable\n\nAutomatic OpenTelemetry instrumentation for Ruby methods with configurable serialization, PII filtering, and argument tracking.\n\n## Getting Started\n\n```bash\nbundle add observable\n```\n\nOr\n\n```ruby\n# Gemfile\ngem 'observable'\n```\n\nBasic usage:\n\n```ruby\nrequire 'observable'\n\nclass UserService\n  def initialize\n    @instrumenter = Observable::Instrumenter.new\n  end\n\n  def create_user(name, email)\n    @instrumenter.instrument(binding) do\n      User.create(name: name, email: email)\n    end\n  end\nend\n```\n\nOpenTelemetry spans are automatically created with method names, arguments, return values, and exceptions.\n\n## Configuration\n\nConfigure globally or per-instrumenter:\n\n```ruby\n# Global configuration\nObservable::Configuration.configure do |config|\n  config.tracer_name = \"my_app\"\n  config.transport = :otel\n  config.app_namespace = \"my_app\"\n  config.attribute_namespace = \"my_app\"\n  config.track_return_values = true\n  config.serialization_depth = {default: 2, \"MyClass\" =\u003e 3}\n  config.formatters = {default: :to_h, \"MyClass\" =\u003e :to_formatted_h}\n  config.pii_filters = [/password/i, /secret/i]\nend\n\n# Per-instrumenter configuration\nconfig = Observable::Configuration.new\nconfig.track_return_values = false\ninstrumenter = Observable::Instrumenter.new(config: config)\n```\n\n### Configuration Options\n\n- `tracer_name`: `\"observable\"` - Name for the OpenTelemetry tracer\n- `transport`: `:otel` - Uses OpenTelemetry SDK\n- `app_namespace`: `\"app\"` - Namespace for application-specific attributes\n- `attribute_namespace`: `\"app\"` - Namespace for span attributes\n- `track_return_values`: `true` - Captures method return values in spans\n- `serialization_depth`: `{default: 2}` - Per-class serialization depth limits (Hash or Integer for backward compatibility)\n- `formatters`: `{default: :to_h}` - Object serialization methods by class name\n- `pii_filters`: `[]` - Regex patterns to filter sensitive data from spans\n\n## OpenTelemetry Integration\n\nThis library seamlessly integrates with OpenTelemetry, the industry-standard observability framework. Spans are automatically created with standardized naming (`Class#method` or `Class.method`) and include rich metadata about method invocations, making your Ruby applications immediately observable without manual instrumentation.\n\n## Custom Formatters\n\nControl how domain objects are serialized in spans by configuring custom formatters.\n\n```ruby\nObservable::Configuration.configure do |config|\n  config.formatters = {\n    default: :to_h,\n    'YourCustomClass' =\u003e :to_formatted_h\n  }\n  config.serialization_depth = {\n    default: 2,\n    'YourCustomClass' =\u003e 3\n  }\nend\n```\n\n## Example\n\nA domain object `Customer` has an `Invoice`.\n\n### Objective \n\nOnly send the invoice ID to the trace to save data.\n\n### Background\n\nImagine domain objects are `Dry::Struct` value objects:\n\n```ruby\nclass Customer \u003c Dry::Struct\n  attribute :id, Dry.Types::String\n  attribute :name, Dry.Types::String\n  attribute :Invoice, Invoice\nend\n\nclass Invoice \u003c Dry::Struct\n  attribute :id, Dry.Types::String\n  attribute :status, Dry.Types::String\n  attribute :line_items, Dry.Types::Array\nend\n```\n\n### Solution\n\n1. Define custom formatting method - `#to_formatted_h`\n  \n```diff\n class Customer \u003c Dry::Struct\n   attribute :id, Dry.Types::String\n   attribute :name, Dry.Types::String\n   attribute :Invoice, Invoice\n\n+  def to_formatted_h\n+    {\n+      id: id,\n+      name: name,\n+      invoice: {\n+       id: invoice.id\n+      }\n+    }\n+  end\n end\n```\n\n2. Configure observable:\n\n```ruby\nObservable::Configuration.configure do |config|\n  config.formatters = {\n    default: :to_h,\n    'Customer' =\u003e :to_formatted_h\n  }\n  config.serialization_depth = {\n    default: 2,\n    'Customer' =\u003e 3\n  }\nend\n```\n\nThe instrumenter tries class-specific formatters first, then falls back to the default formatter, then `to_s`.\n\n## Benefits\n\nWhy use this library? Why not write Otel attributes manually?\n\n* **Zero-touch instrumentation** - Wrap any method call without modifying existing code or manually creating spans\n* **Production-ready safety** - Built-in PII filtering, serialization depth limits, and exception handling prevent common observability pitfalls\n* **Standardized telemetry** - Consistent span naming, attribute structure, and OpenTelemetry compliance across your entire application\n\n## License\n\nMIT License. See LICENSE file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoyfulprogramming%2Fobservable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoyfulprogramming%2Fobservable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoyfulprogramming%2Fobservable/lists"}