diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..c2f47762 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,247 @@ +# GitHub Copilot Instructions for solarwinds_apm + +## Priority Guidelines + +When generating code for this repository: + +1. **Version Compatibility**: Always detect and respect the exact versions of Ruby, OpenTelemetry, and dependent gems used in this project +2. **Context Files**: Prioritize patterns and standards defined in the .github/instructions directory when available +3. **Codebase Patterns**: When context files don't provide specific guidance, scan the codebase for established patterns +4. **Architectural Consistency**: Maintain the modular architecture with clear separation between API, config, sampling, and instrumentation layers +5. **Code Quality**: Prioritize maintainability, performance, and security in all generated code + +## Technology Version Detection + +Before generating code, scan the codebase to identify: + +1. **Language Versions**: + - Ruby version: **>= 3.1.0** (as specified in solarwinds_apm.gemspec) + - Never use Ruby features beyond version 3.1 unless the gemspec is updated + - Always include `# frozen_string_literal: true` as the first line in every Ruby file + +2. **Framework Versions**: + - [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/sdk): **>= 1.2.0** + - [OpenTelemetry Instrumentation All](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/all): **>= 0.31.0** + - [OpenTelemetry OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp): **>= 0.29.1** + - [OpenTelemetry Metrics OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp-metrics): **>= 0.3.0** + - [OpenTelemetry Logs OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp-logs): **>= 0.2.1** + - [OpenTelemetry Metrics SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_sdk): **>= 0.2.0** + - [OpenTelemetry Logs SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/logs_sdk): **>= 0.4.0** + - Respect version constraints when generating code + - Never suggest OpenTelemetry features not available in the detected versions + +3. **Library Versions**: + - OpenTelemetry Resource Detectors ([AWS](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/resources/aws): **>= 0.1.0**, [Azure](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/resources/azure): **>= 0.2.0**, [Container](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/resources/container): **>= 0.2.0**) + - Test framework: Minitest (version **< 5.27.0** for compatibility) + - Generate code compatible with these specific versions + - Never use APIs or features not available in the detected versions + +## Context Files + +Prioritize the following files in .github/instructions directory (if they exist): + +- **/*.instructions.md**: File-type specific instructions for various file types (Ruby, YAML, etc.) +- **architecture.md**: System architecture guidelines +- **tech-stack.md**: Technology versions and framework details +- **coding-standards.md**: Code style and formatting standards +- **folder-structure.md**: Project organization guidelines +- **exemplars.md**: Exemplary code patterns to follow + +## Codebase Scanning Instructions + +When context files don't provide specific guidance: + +1. Identify similar files to the one being modified or created +2. Analyze patterns for: + - Naming conventions (module names, class names, method names) + - Code organization (module structure, class hierarchy) + - Error handling (logging patterns, exception handling) + - Logging approaches (SolarWindsAPM.logger usage) + - Documentation style (YARD documentation format) + - Testing patterns (Minitest describe/it blocks) + +3. Follow the most consistent patterns found in the codebase +4. When conflicting patterns exist, prioritize patterns in newer files or files with higher test coverage +5. Never introduce patterns not found in the existing codebase + +## Architecture and Module Organization + +This project follows a layered architecture with clear module boundaries: + +### Core Modules + +- **SolarWindsAPM**: Root module for the gem +- **SolarWindsAPM::API**: Public API surface exposed to users + - `TransactionName`: Custom transaction naming + - `CurrentTraceInfo`: Trace context information + - `Tracing`: Readiness checks + - `OpenTelemetry`: OpenTelemetry integration helpers + - `CustomMetrics`: Custom metrics (deprecated in 7.0.0+) + - `Tracer`: Custom instrumentation helpers +- **SolarWindsAPM::Config**: Configuration management +- **SolarWindsAPM::OTelConfig**: OpenTelemetry configuration and initialization +- **SolarWindsAPM::Sampling**: Sampling algorithms and trace decisions + - `OboeSampler`: Main sampling logic with dice roll, parent-based, and trigger trace algorithms + - `TokenBucket`: Rate limiting for trace sampling + - `Dice`: Probabilistic sampling decisions +- **SolarWindsAPM::Support**: Utility classes + - `ServiceKeyChecker`: Service key validation + - `ResourceDetector`: Resource attribute detection + - `TransactionSettings`: URL-based sampling configuration + - `OtlpEndpoint`: OTLP endpoint URL construction +- **SolarWindsAPM::Patch**: Instrumentation patches + - Tag SQL functionality for MySQL2 and PostgreSQL + +### File Organization + +- **lib/solarwinds_apm.rb**: Main entry point, handles initialization and configuration +- **lib/solarwinds_apm/**: Module implementations organized by function +- **test/**: Test files mirroring the lib/ structure +- **lib/rails/generators/**: Rails generator templates + +## Code Quality Standards + +### Maintainability + +- Write self-documenting code with clear naming following Ruby conventions +- Use descriptive method names with underscores (e.g., `set_transaction_name`, `should_sample?`) +- Follow the naming pattern: `ModuleName::ClassName.method_name` or `module_name/file_name.rb` +- Keep functions focused on single responsibilities +- Limit method complexity - extract complex logic into private methods +- Use private methods for internal implementation details (e.g., `private_class_method :compile_settings`) +- Organize code with clear module boundaries as seen in existing structure + +### Performance + +- Use efficient Ruby idioms (e.g., `dig` for nested hash access, `fetch` with defaults) +- Cache expensive computations (see token bucket implementation) +- Use mutexes (`::Mutex`) for thread-safe operations when necessary +- Follow existing patterns for asynchronous operations with OpenTelemetry +- Optimize regex compilation (compile once, use many times) +- Use `freeze` for constants and immutable objects (e.g., `SW_LOG_LEVEL_MAPPING.freeze`) + +### Security + +- Validate all external inputs (see `TraceOptions.validate_signature` pattern) +- Sanitize SQL queries through parameterization (see tag_sql patch) +- Use environment variables for sensitive configuration (`ENV.fetch` with defaults) +- Follow established authentication patterns for trigger trace validation +- Handle sensitive data (service keys, signatures) according to existing patterns +- Log security-relevant events at appropriate levels + +### Error Handling + +- Use explicit error handling with rescue blocks +- Log errors with appropriate severity levels using `SolarWindsAPM.logger` +- Provide meaningful error messages with context (module/method name in logs) +- Return status booleans or appropriate values indicating success/failure +- Use `StandardError` as base rescue class unless more specific is needed +- Follow the pattern: catch errors, log them, and handle gracefully (see lib/solarwinds_apm.rb) + +## Documentation Requirements + +Follow the YARD documentation format found in the codebase: + +- Document all public API methods with YARD syntax +- Include `@param` tags for all parameters with type and description +- Include `@return` tags with return type and description +- Document exceptions that may be raised +- Include usage examples in documentation blocks (see API::TransactionName) +- Use inline comments for non-obvious logic, prefixed with `#` +- Document configuration options with their expected types and defaults + +## Ruby Coding Conventions + +For comprehensive Ruby coding standards including: + +- File headers and frozen string literals +- Module and class organization +- Naming conventions (methods, variables, constants) +- Documentation with YARD +- Error handling and logging patterns +- Environment variable handling +- Testing with Minitest +- OpenTelemetry integration +- Configuration management +- Thread safety and performance optimization + +**Refer to [.github/instructions/ruby.instructions.md](.github/instructions/ruby.instructions.md)** which is automatically applied to all Ruby files based on the `applyTo` glob pattern. + +## Versioning and Releases + +This project uses Semantic Versioning: + +- Version defined in `lib/solarwinds_apm/version.rb` +- Format: `MAJOR.MINOR.PATCH` (with optional PRE for pre-releases) +- MAJOR: Breaking changes +- MINOR: New features, backward compatible +- PATCH: Bug fixes, backward compatible +- Document changes in CHANGELOG.md + +## File-Type Specific Instructions + +For file-type specific guidance (Ruby files, YAML, Markdown, etc.), refer to the instructions in `.github/instructions/` directory. These provide detailed patterns for: + +- Ruby source files (`.rb`) - `ruby.instructions.md` +- Ruby gemspec files (`.gemspec`) - `ruby.instructions.md` +- Test files (`*_test.rb`) - `ruby.instructions.md` +- Configuration files (YAML, JSON) - `coding.instructions.md` + +**Note**: These instruction files are automatically applied by GitHub Copilot based on the `applyTo` glob patterns specified in their frontmatter. + +## General Best Practices + +- Always include `# frozen_string_literal: true` at the top of Ruby files +- Follow Ruby community style guide with project-specific adaptations +- Use meaningful variable names that reflect their purpose +- Keep methods short and focused (aim for < 25 lines) +- Extract complex conditions into well-named private methods +- Use guard clauses to reduce nesting +- Prefer explicit returns for clarity, though implicit returns are acceptable +- Use symbols for hash keys in new code +- Avoid modifying frozen objects +- Thread safety: use mutexes for shared mutable state +- Performance: avoid unnecessary object allocations in hot paths + +## Project-Specific Conventions + +### Initialization and Lifecycle + +- Entry point: `lib/solarwinds_apm.rb` +- Automatic initialization: controlled by `SW_APM_AUTO_CONFIGURE` (default: enabled) +- Manual initialization: `SolarWindsAPM::OTelConfig.initialize` or `initialize_with_config` +- Check if enabled: `ENV.fetch('SW_APM_ENABLED', 'true')` +- Noop mode: require 'solarwinds_apm/noop' when disabled + +### Sampling Algorithm Selection + +Follow the decision tree (see `OboeSampler#should_sample?`): + +1. Local spans: trust parent decision +2. If tracestate present and valid: parent-based algorithm +3. If SAMPLE_START flag set: + - With X-Trace-Options: trigger trace algorithm + - Without X-Trace-Options: dice roll algorithm +4. Otherwise: disabled algorithm + +### Attribute Naming + +Use consistent attribute names: + +- `SWKeys`: Custom keys from trigger trace +- `SampleRate`: Sample rate used +- `SampleSource`: Source of sampling decision +- `BucketCapacity`: Token bucket capacity +- `BucketRate`: Token bucket rate +- `TriggeredTrace`: Boolean for triggered traces +- `sw.tracestate_parent_id`: Parent span ID from tracestate + +## When in Doubt + +- Scan the codebase thoroughly before generating any code +- Respect existing architectural boundaries without exception +- Match the style and patterns of surrounding code +- Prioritize consistency with existing code over external best practices +- If a pattern appears consistently across multiple files, follow it +- If unsure about OpenTelemetry API usage, check existing instrumentation patterns +- Consult README.md and CONFIGURATION.md for user-facing guidance diff --git a/.github/instructions/coding.instructions.md b/.github/instructions/coding.instructions.md new file mode 100644 index 00000000..89d34b3c --- /dev/null +++ b/.github/instructions/coding.instructions.md @@ -0,0 +1,39 @@ +--- +applyTo: "**/*.rb,**/*.yml,**/*.sh,Rakefile" +--- + +# Coding Instructions + +## General code style and readability +- Write code that is readable, understandable, and maintainable for future readers. +- Aim to create software that is not only functional but also readable, maintainable, and efficient throughout its lifecycle. +- Prioritize clarity to make reading, understanding, and modifying code easier. +- Adhere to established coding standards and write well-structured code to reduce errors. +- Regularly review and refactor code to improve structure, readability, and maintainability. Always leave the codebase cleaner than you found it. + +## Naming conventions +- Choose names for variables, functions, and classes that reflect their purpose and behavior. +- A name should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent. +- Use specific names that provide a clearer understanding of what the variables represent and how they are used. + +## DRY principle +- Follow the DRY (Don't Repeat Yourself) Principle and Avoid Duplicating Code or Logic. +- Avoid writing the same code more than once. Instead, reuse your code using functions, classes, modules, libraries, or other abstractions. +- Modify code in one place if you need to change or update it. + +## Function length and responsibility +- Write short functions that only do one thing. +- Follow the single responsibility principle (SRP), which means that a function should have one purpose and perform it effectively. +- If a function becomes too long or complex, consider breaking it into smaller, more manageable functions. + +## Comments usage +- Use comments sparingly, and when you do, make them meaningful. +- Don't comment on obvious things. Excessive or unclear comments can clutter the codebase and become outdated. +- Use comments to convey the "why" behind specific actions or explain unusual behavior and potential pitfalls. +- Provide meaningful information about the function's behavior and explain unusual behavior and potential pitfalls. + +## Conditional encapsulation +- One way to improve the readability and clarity of functions is to encapsulate nested if/else statements into other functions. +- Encapsulating such logic into a function with a descriptive name clarifies its purpose and simplifies code comprehension. + + \ No newline at end of file diff --git a/.github/instructions/ruby.instructions.md b/.github/instructions/ruby.instructions.md new file mode 100644 index 00000000..39feed6a --- /dev/null +++ b/.github/instructions/ruby.instructions.md @@ -0,0 +1,303 @@ +--- +applyTo: "**/*.rb,**/*.gemspec,**/Gemfile,**/Rakefile" +description: 'Ruby coding conventions and best practices for the solarwinds_apm gem' +--- + +# Ruby Coding Instructions + +## File Header and Frozen String Literal + +- **Always** include `# frozen_string_literal: true` as the **first line** of every Ruby file +- Add the copyright header after the frozen string literal: + +```ruby +# frozen_string_literal: true + +# © 2026 SolarWinds Worldwide, LLC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +``` + +## Module and Class Organization + +- Use proper module nesting with `::` notation: `SolarWindsAPM::API::TransactionName` +- Organize related functionality into modules (e.g., `SolarWindsAPM::API`, `SolarWindsAPM::Config`, `SolarWindsAPM::Sampling`) +- Place classes in appropriately named files matching the class name in snake_case +- Use `self.included(base)` pattern for module inclusion with class methods: + +```ruby +module SolarWindsAPM + module API + module Tracer + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + # class methods here + end + end + end +end +``` + +## Naming Conventions + +### Methods +- Use snake_case for method names: `set_transaction_name`, `should_sample?`, `parent_based_algo` +- Use `?` suffix for predicate methods that return boolean: `ready?`, `valid?`, `running?`, `boolean?` +- Use `!` suffix sparingly for destructive methods or methods with side effects +- Use `=` suffix for setter methods: `capacity=`, `tokens=`, `interval=` + +### Variables +- Use snake_case: `sample_state`, `trace_flags`, `parent_span`, `service_name` +- Use descriptive names that convey purpose +- Instance variables: `@logger`, `@settings`, `@buckets`, `@timer` +- Class variables: `@@config` (use sparingly, prefer class instance variables) + +### Constants +- Use SCREAMING_SNAKE_CASE: `SAMPLE_RATE_ATTRIBUTE`, `MAX_INTERVAL`, `OTEL_SAMPLING_DECISION` +- Freeze constant collections: `SW_LOG_LEVEL_MAPPING.freeze`, `EXEC_ISH_METHODS.freeze` +- Group related constants at the top of the class or module + +### Modules and Classes +- Use CamelCase: `SolarWindsAPM`, `OboeSampler`, `TokenBucket`, `ServiceKeyChecker` +- Use descriptive names that reflect purpose and responsibility + +## Attribute Accessors + +- Use `attr_reader` for read-only attributes: `attr_reader :token, :service_name` +- Use `attr_accessor` for read-write attributes: `attr_accessor :logger` +- Use `attr_writer` for write-only attributes (rare) +- Define custom setters when validation or side effects are needed: + +```ruby +def capacity=(capacity) + @capacity = [0, capacity].max +end + +def tokens=(tokens) + @tokens = tokens.clamp(0, @capacity) +end +``` + +## Method Visibility + +- Mark private methods with `private` keyword +- Use `private_class_method :method_name` for private class methods +- Place `private` keyword before the private methods section +- Public methods should come before private methods + +## Documentation with YARD + +- Document all public API methods with YARD syntax +- Use `#` for instance methods and `.` for class methods in documentation +- Include `@param` tags with types and descriptions +- Include `@return` tags with return types +- Add usage examples in documentation blocks + +Example: +```ruby +# Provide a custom transaction name +# +# === Argument: +# +# * +custom_name+ - A non-empty string with the custom transaction name +# +# === Example: +# +# SolarWindsAPM::API.set_transaction_name(custom_name) +# +# === Returns: +# * Boolean +# +def set_transaction_name(custom_name = nil) + # implementation +end +``` + +## Error Handling + +- Use explicit `rescue` blocks with specific exception types +- Log errors with appropriate severity using `SolarWindsAPM.logger` +- Include module/class and method context in log messages: `"[#{self.class}/#{__method__}] message"` +- Return status booleans or appropriate values indicating success/failure +- Use `StandardError` as base rescue class unless more specific is needed: + +```ruby +begin + # code +rescue StandardError => e + SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] Error: #{e.message}" } +end +``` + +## Logging Patterns + +- Always use `SolarWindsAPM.logger` for all logging +- Use block syntax for expensive operations: `SolarWindsAPM.logger.debug { "message" }` +- Include context in log messages: `"[#{self.class}/#{__method__}] message"` +- Use appropriate log levels: `debug`, `info`, `warn`, `error`, `fatal` +- Use variable inspection for debugging: `#{variable.inspect}` + +Example: +```ruby +SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] sample_state: #{sample_state.inspect}" } +SolarWindsAPM.logger.warn { "[#{self.class}/#{__method__}] Service Name transformed from #{old} to #{new}" } +``` + +## Environment Variables + +- Use `ENV.fetch('VAR_NAME', 'default')` for optional variables with defaults +- Use `ENV['VAR_NAME']` for checking presence +- Document all environment variables in CONFIGURATION.md +- Priority order: ENV > config file > defaults +- Convert strings to appropriate types (integers, booleans, symbols) + +Example: +```ruby +if ENV.fetch('SW_APM_ENABLED', 'true') == 'false' + # handle disabled case +end + +log_level = (ENV['SW_APM_DEBUG_LEVEL'] || SolarWindsAPM::Config[:debug_level] || 3).to_i +``` + +## String Handling + +- Use single quotes for simple strings: `'message'` +- Use double quotes for string interpolation: `"Value: #{value}"` +- Use heredocs for multi-line strings with proper indentation +- Use `String#freeze` for string constants +- Prefer string interpolation over concatenation for readability + +## Hash and Symbol Usage + +- Use symbols for hash keys: `{ key: value }` or `{ :key => value }` +- Access config hashes with symbols: `SolarWindsAPM::Config[:key]` +- Use `Hash#dig` for safe nested access: `SW_LOG_LEVEL_MAPPING.dig(level, :stdlib)` +- Use `Hash#fetch` with defaults: `hash.fetch(:key, default_value)` + +## Ruby Idioms and Best Practices + +- Use guard clauses to reduce nesting: +```ruby +return unless condition +return if early_exit_condition +# main logic +``` + +- Use `||=` for memoization: +```ruby +@settings ||= load_settings +``` + +- Use `&.` (safe navigation operator) for conditional method calls: +```ruby +value = object&.method&.another_method +``` + +- Prefer `unless` over `if !` for negative conditions (when readable) +- Use `each_with_object` for transforming collections +- Use `clamp` for range limiting: `value.clamp(min, max)` +- Use range operators efficiently: `value.between?(min, max)` + +## Thread Safety + +- Use `::Mutex` for protecting shared mutable state: +```ruby +@settings_mutex = ::Mutex.new + +@settings_mutex.synchronize do + @settings = new_settings +end +``` + +- Document thread safety considerations in comments +- Avoid race conditions when accessing shared state +- Use atomic operations where possible + +## Performance Optimization + +- Cache expensive computations and regex compilations +- Use `freeze` on constants and immutable objects +- Avoid unnecessary object allocations in hot paths +- Use efficient collection methods (`map`, `select`, `reject` over `each`) +- Prefer `&:method_name` syntax for simple blocks: `array.map(&:to_s)` + +## Testing with Minitest + +- Use Minitest's spec-style DSL with `describe` and `it` blocks +- Structure tests: `describe 'ClassName' do ... end` +- Name tests descriptively: `it 'does something specific' do ... end` +- Use `let` blocks for test fixtures +- Use Minitest expectations: `assert`, `refute`, `assert_equal` +- Prefer `assert` and `refute` over `assert_equal true/false` + +Example: +```ruby +describe 'SolarWindsAPM::TokenBucket' do + it 'starts full' do + bucket = SolarWindsAPM::TokenBucket.new(settings) + assert bucket.consume(2) + end + + it "can't consume more than it contains" do + bucket = SolarWindsAPM::TokenBucket.new(settings) + refute bucket.consume(2) + end +end +``` + +## OpenTelemetry Integration + +- Use `::OpenTelemetry` prefix for OpenTelemetry SDK classes +- Access current span: `::OpenTelemetry::Trace.current_span` +- Work with context: `::OpenTelemetry::Context.current` +- Use proper span context validation: `span.context.valid?` +- Follow established patterns for span creation and attribute setting + +## Configuration Management + +- Access config with symbols: `SolarWindsAPM::Config[:key]` +- Set config with validation: `SolarWindsAPM::Config[:key] = value` +- Use symbol values for enabled/disabled states: `:enabled`, `:disabled` +- Validate configuration values in setters +- Log warnings for invalid configurations + +## Code Organization + +- Keep methods focused and under 25 lines when possible +- Extract complex logic into private methods with descriptive names +- Use meaningful variable names that reflect purpose +- Group related methods together +- Separate public API from implementation details +- Organize require statements at the top of the file + +## Struct and Data Classes + +- Use `Struct.new` for simple data containers: +```ruby +TokenBucketSettings = Struct.new(:capacity, :rate, :interval, :type) +``` + +- Add methods to Struct subclasses when needed +- Use keyword arguments for Struct initialization when clarity is needed + +## Return Values + +- Prefer explicit `return` for early returns and clarity +- Implicit returns are acceptable for simple one-line methods +- Return status booleans for operations that can succeed or fail +- Return `nil` explicitly when no meaningful value exists + +## Compatibility + +- Target Ruby >= 3.1.0 as specified in gemspec +- Avoid using features from newer Ruby versions +- Test compatibility with minimum supported Ruby version +- Document any version-specific behavior + + diff --git a/.github/workflows/ci-markdownlint.yml b/.github/workflows/ci-markdownlint.yml index 4449ffda..b35dfe16 100644 --- a/.github/workflows/ci-markdownlint.yml +++ b/.github/workflows/ci-markdownlint.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v6 - # equivalent cli: markdownlint-cli2 "**/*.md" "#lambda/.aws-sam/**" "#.github/pull_request_template.md" "#.github/ISSUE_TEMPLATE/bug-or-feature-request.md" --config .markdownlint.json + # equivalent cli: markdownlint-cli2 "**/*.md" "#lambda/.aws-sam/**" "#.github/pull_request_template.md" "#.github/ISSUE_TEMPLATE/bug-or-feature-request.md" "#.github/instructions/**" --config .markdownlint.json - name: "Markdown Lint Check" uses: DavidAnson/markdownlint-cli2-action@v21 with: @@ -19,3 +19,4 @@ jobs: !lambda/.aws-sam/** !.github/pull_request_template.md !.github/ISSUE_TEMPLATE/bug-or-feature-request.md + !.github/instructions/** diff --git a/.markdownlint.json b/.markdownlint.json index 42882384..fea10c35 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -10,5 +10,6 @@ "no-trailing-spaces": true, "custom-rules-below-this-point": false, "trim-code-block-and-unindent": true, - "single-title/single-h1": false + "single-title/single-h1": false, + "table-column-style": false } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..ac2a9510 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,282 @@ +# AGENTS.md + +## Project Overview + +The `solarwinds_apm` gem is an OpenTelemetry Ruby distribution that provides automatic instrumentation and observability features for Ruby applications. It's built on top of OpenTelemetry SDK >= 1.2.0 and supports Ruby >= 3.1.0. + +**Key Technologies:** + +- Ruby >= 3.1.0 +- [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/sdk): **>= 1.2.0** +- [OpenTelemetry Instrumentation All](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/all): **>= 0.31.0** +- [OpenTelemetry OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp): **>= 0.29.1** +- [OpenTelemetry Metrics OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp-metrics): **>= 0.3.0** +- [OpenTelemetry Logs OTLP Exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/otlp-logs): **>= 0.2.1** +- [OpenTelemetry Metrics SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_sdk): **>= 0.2.0** +- [OpenTelemetry Logs SDK](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/logs_sdk): **>= 0.4.0** +- Minitest for testing +- RuboCop for code quality + +**Architecture:** + +- Modular design with clear separation: API, Config, Sampling, Support, Patch +- Entry point: `lib/solarwinds_apm.rb` +- Test suite mirrors lib/ structure + +## Setup Commands + +For detailed setup instructions, see [CONTRIBUTING.md](CONTRIBUTING.md#development-environment-setup). + +## Development Workflow + +For complete development workflow, see [CONTRIBUTING.md](CONTRIBUTING.md#development-workflow). + +### File Organization + +- **Source code**: `lib/` + - `solarwinds_apm.rb` - Main entry point + - `solarwinds_apm/` - Core implementation + - `api/` - Public API methods (TransactionName, CurrentTraceInfo, Tracing, OpenTelemetry) + - `api.rb` - API module loader + - `config.rb` - Configuration management + - `otel_config.rb` - OpenTelemetry configuration and initialization + - `sampling/` - Sampling algorithms (OboeSampler, TokenBucket, Dice, TraceOptions) + - `sampling.rb` - Sampling module loader + - `support/` - Utility classes (ServiceKeyChecker, ResourceDetector, TransactionSettings, OtlpEndpoint) + - `support.rb` - Support module loader + - `patch/` - Instrumentation patches (MySQL2, PostgreSQL SQL tagging) + - `opentelemetry/` - OpenTelemetry extensions (propagator, processor) + - `noop/` - No-op implementations when disabled + - `version.rb` - Gem version + - `constants.rb` - Shared constants + - `logger.rb` - Logger configuration + - `rails/generators/` - Rails generator templates +- **Tests**: `test/` (mirrors lib/ structure) + - `minitest_helper.rb` - Test setup and helpers + - `run_tests.sh` - Test execution script + - `api/` - API tests (custom instrumentation, transaction naming, tracing readiness) + - `sampling/` - Sampling tests (dice, sampler, token bucket, trace options) + - `opentelemetry/` - OpenTelemetry tests (propagator, processor) + - `support/` - Support tests (service key checker, resource detector, OTLP endpoint) + - `patch/` - Patch tests (MySQL2, PostgreSQL) + - `solarwinds_apm/` - Core functionality tests +- **Configuration**: + - `solarwinds_apm.gemspec` - Gem specification + - `Gemfile` - Development dependencies + - `Rakefile` - Build and test tasks + - `.rubocop.yml` - RuboCop configuration +- **Documentation**: + - `README.md` - User-facing documentation + - `CONTRIBUTING.md` - Contribution guidelines + - `CONFIGURATION.md` - Configuration reference + - `CHANGELOG.md` - Version history + - `AGENTS.md` - This file (AI agent instructions) + - `SECURITY.md` - Security policy +- **Build artifacts**: + - `builds/` - Built gem files + - `doc/` - Generated YARD documentation + - `coverage/` - Test coverage reports + - `log/` - Test execution logs + +## Testing Instructions + +For comprehensive testing instructions, see [CONTRIBUTING.md](CONTRIBUTING.md#testing-your-changes). + +### Test File Patterns + +- All test files end with `_test.rb` +- Use Minitest spec-style DSL: `describe` and `it` blocks +- Test structure: `describe 'ClassName' do ... end` +- Assertions: Use `assert`, `refute`, `_(value).must_equal expected` + +## Code Style + +### Ruby Conventions + +- **Always** start files with `# frozen_string_literal: true` +- Include copyright header after frozen string literal +- Use snake_case for methods: `set_transaction_name`, `should_sample?` +- Use `?` suffix for predicate methods: `ready?`, `valid?` +- Use CamelCase for modules/classes: `SolarWindsAPM`, `OboeSampler` +- Use SCREAMING_SNAKE_CASE for constants: `SAMPLE_RATE_ATTRIBUTE` +- Freeze constant collections: `.freeze` + +### Module Organization + +- Use proper nesting: `SolarWindsAPM::API::TransactionName` +- File names match class names in snake_case +- Place classes in: `lib/solarwinds_apm/module_name/class_name.rb` + +### Logging Patterns + +Always use `SolarWindsAPM.logger` with context: + +```ruby +SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] message" } +SolarWindsAPM.logger.warn { "[#{self.class}/#{__method__}] warning: #{details}" } +``` + +Use block syntax for expensive operations to avoid evaluation when not needed. + +### Error Handling + +```ruby +begin + # code +rescue StandardError => e + SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] Error: #{e.message}" } + false # return status boolean +end +``` + +### Linting + +See [CONTRIBUTING.md](CONTRIBUTING.md#code-quality) for details. + +**All linting issues must be resolved before submitting PR.** + +## Build and Deployment + +### Build Gem Locally + +For local testing: + +```bash +bundle exec rake build_gem +``` + +Output: `builds/solarwinds_apm-X.Y.Z.gem` + +The script shows SHA256 checksum and lists the last 5 built gems. + +### Build for GitHub Packages + +```bash +bundle exec rake build_gem_for_github_package[7.1.0] +``` + +### Push to GitHub Packages + +Requires credentials in `~/.gem/credentials`: + +```bash +bundle exec rake push_gem_to_github_package[7.1.0] +``` + +### Build and Publish to RubyGems + +**For maintainers only:** + +```bash +bundle exec rake build_and_publish_gem +``` + +Requires `GEM_HOST_API_KEY` environment variable and gem >= 3.0.5. + +## Pull Request Guidelines + +For complete PR guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). + +**Before submitting:** + +1. Run all checks: `bundle exec rake rubocop && test/run_tests.sh` +2. All RuboCop issues resolved +3. All tests passing +4. Add tests for changes + +**PR titles:** Use descriptive titles that explain the change (e.g., `Fix sampling decision for parent-based traces`) + +## Additional Context + +### Version Compatibility + +- **Ruby**: >= 3.1.0 (specified in gemspec) +- **OpenTelemetry SDK**: >= 1.2.0 +- Never use features from newer versions without updating requirements + +### Documentation Standards + +- Public API methods must have YARD documentation +- Include `@param`, `@return`, and usage examples +- Document configuration options in CONFIGURATION.md +- Update README.md for user-facing changes + +### OpenTelemetry Integration Patterns + +Access current span: + +```ruby +current_span = ::OpenTelemetry::Trace.current_span +``` + +Create spans: + +```ruby +tracer.in_span('span_name', attributes: {...}, kind: :span_kind) do |span| + # your code +end +``` + +Work with context: + +```ruby +::OpenTelemetry::Context.with_current(context) do + # code with context +end +``` + +### Configuration Access + +```ruby +# Read config +value = SolarWindsAPM::Config[:key] + +# Set config +SolarWindsAPM::Config[:key] = value + +# Environment variables take precedence +ENV['SW_APM_ENABLED'] || SolarWindsAPM::Config[:enabled] +``` + +### Thread Safety + +Use mutexes for shared mutable state: + +```ruby +@mutex = ::Mutex.new +@mutex.synchronize do + @shared_state = new_value +end +``` + +### Debugging Tips + +- Use `bundle exec irb -Ilib -r solarwinds_apm` for quick testing +- Check `log/testrun_*.log` for test execution details +- Set `SW_APM_DEBUG_LEVEL=5` for verbose logging +- Use Docker containers to test against specific Ruby versions +- Run single test files to isolate issues + +### Common Gotchas + +- Tests require `SW_APM_SERVICE_KEY` environment variable +- Some integration tests need actual service keys (contact maintainers) +- Alpine containers may have different behavior than Debian-based +- Always run RuboCop before committing +- Test logs accumulate in `log/` directory + +### File Locations + +- Main entry: `lib/solarwinds_apm.rb` +- Configuration: `lib/solarwinds_apm/config.rb` +- Version: `lib/solarwinds_apm/version.rb` +- Gemspec: `solarwinds_apm.gemspec` +- Test helper: `test/minitest_helper.rb` +- CI workflows: `.github/workflows/` + +### Related Documentation + +- [README.md](README.md) - User-facing documentation +- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines +- [CONFIGURATION.md](CONFIGURATION.md) - Configuration reference +- [CHANGELOG.md](CHANGELOG.md) - Version history diff --git a/test/patch/sw_pg_patch_integrate_test.rb b/test/patch/sw_pg_patch_integrate_test.rb index c1e4070d..6b1a029c 100644 --- a/test/patch/sw_pg_patch_integrate_test.rb +++ b/test/patch/sw_pg_patch_integrate_test.rb @@ -47,6 +47,7 @@ def pg_dbo_integration_verification(sql, finished_spans) _(client_ancestors[2]).must_equal PG::Connection pg_client = PG::Connection.new + exporter.reset args = ['SELECT * FROM ABC;']