From 1fafc461c4c7aa19b7c26016cd3f6008008dc217 Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Thu, 4 Dec 2025 11:47:38 -0500 Subject: [PATCH 1/2] NH-125037: add copilot instructions --- .github/copilot-instructions.md | 378 ++++++++++++++++++ .github/instructions/coding.instructions.md | 39 ++ .github/instructions/jira.instructions.md | 7 + .github/instructions/markdown.instructions.md | 54 +++ .github/instructions/ruby.instructions.md | 303 ++++++++++++++ AGENTS.md | 358 +++++++++++++++++ 6 files changed, 1139 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/instructions/coding.instructions.md create mode 100644 .github/instructions/jira.instructions.md create mode 100644 .github/instructions/markdown.instructions.md create mode 100644 .github/instructions/ruby.instructions.md create mode 100644 AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..440f1fc --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,378 @@ +# 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/copilot 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: **>= 1.2.0** + - OpenTelemetry Instrumentation All: **>= 0.31.0** + - OpenTelemetry OTLP Exporter: **>= 0.29.1** + - OpenTelemetry Metrics SDK: **>= 0.2.0** + - OpenTelemetry 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: **>= 0.1.0**, Azure: **>= 0.2.0**, 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/copilot directory (if they exist): + +- **instructions/*.md**: File-type specific generic 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 +- Add copyright headers to all new files following this pattern: + +```ruby +# frozen_string_literal: true + +# © 2023 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. +``` + +## Testing Approach + +### Unit Testing with Minitest + +- Use Minitest's spec-style DSL with `describe` and `it` blocks +- Structure: `describe 'ClassName or feature' do ... end` +- Test naming: `it 'describes_what_the_test_does' do ... end` +- Use `let` blocks for test fixtures and shared setup +- Use `before` and `after` hooks for setup and teardown +- Follow the AAA pattern (Arrange, Act, Assert) within test blocks + +### Test Organization + +- Mirror the lib/ directory structure in test/ +- Group related tests in describe blocks +- Use nested describe blocks for method-specific tests +- Name test files with `_test.rb` suffix (e.g., `config_test.rb`) +- Place integration tests in appropriate subdirectories (e.g., test/patch/) + +### Assertions + +- Use Minitest expectations: `_(value).must_equal expected` +- Common patterns: + - `_(result).must_equal expected_value` + - `_(collection).must_include item` + - `_(lambda { code }).must_raise ExceptionClass` + - `_(value).must_be_nil` + - `_(result).must_be_instance_of ClassName` + +### Test Setup + +- Use `minitest_helper.rb` for common test configuration +- Set required environment variables in test setup (e.g., `SW_APM_SERVICE_KEY`) +- Use custom test helpers like `CustomInMemorySpanExporter` for OpenTelemetry testing +- Create helper methods for common test operations (e.g., `create_span`, `create_context`) +- Use `skip` directive when tests require specific conditions not met + +### Mocking and Stubbing + +- Use the mocha gem for mocking (included in test dependencies) +- Follow patterns like `Object.stub(:method, return_value) do ... end` +- Mock external dependencies (HTTP requests, file I/O) in integration tests +- Use `before` blocks to set up mocks and stubs + +## OpenTelemetry Integration Patterns + +### Span Creation + +- Use `::OpenTelemetry.tracer_provider.tracer(name)` to obtain tracers +- Create spans with `tracer.in_span(name, attributes: {...}, kind: :span_kind) do ... end` +- Access current span with `::OpenTelemetry::Trace.current_span` +- Add attributes: `span.add_attributes({key: value})` +- Record exceptions: `span.record_exception(exception)` + +### Context Propagation + +- Work with context: `::OpenTelemetry::Context.current` +- Set context: `OpenTelemetry::Context.with_current(context) do ... end` +- Extract span context from current span: `span.context` +- Check validity: `span.context.valid?` + +### Sampling Decisions + +- Return `OTEL_SAMPLING_RESULT.new(decision:, tracestate:, attributes:)` +- Decision types: + - `OTEL_SAMPLING_DECISION::RECORD_AND_SAMPLE` - trace and export + - `OTEL_SAMPLING_DECISION::RECORD_ONLY` - trace but don't export + - `OTEL_SAMPLING_DECISION::DROP` - don't trace +- Manage tracestate: `::OpenTelemetry::Trace::Tracestate.from_hash({...})` + +## Configuration Management Patterns + +### Environment Variable Handling + +- Use `ENV.fetch('VAR_NAME', 'default')` for optional variables +- Use `ENV['VAR_NAME']` for checking presence +- Document all environment variables in CONFIGURATION.md +- Priority: ENV > config file > defaults +- Convert strings to appropriate types (integers, booleans, symbols) + +### Config Hash Access + +- Access config with symbols: `SolarWindsAPM::Config[:key]` +- Set config: `SolarWindsAPM::Config[:key] = value` +- Validate config values in the setter +- Log warnings for invalid configurations + +### Boolean and Symbol Validation + +- Use helper methods: `true?`, `boolean?`, `symbol?` +- Convert strings: `'true'.casecmp('true').zero?` +- Validate enabled/disabled: `:enabled` or `:disabled` symbols + +## Logging Patterns + +### Logger Usage + +- Use `SolarWindsAPM.logger` for all logging +- Log levels: `debug`, `info`, `warn`, `error`, `fatal` +- Use blocks for expensive log operations: `SolarWindsAPM.logger.debug { "message" }` +- Include module/method context: `"[#{name}/#{__method__}] message"` +- Use structured logging with variable inspection: `#{variable.inspect}` + +### Log Level Mapping + +- Respect `SW_APM_DEBUG_LEVEL` environment variable +- Map to both stdlib Logger and OpenTelemetry log levels +- Default log level: INFO (3) +- Level -1: disables logging by setting logger to `Logger.new(nil)` + +## Naming Conventions + +### Modules and Classes + +- Use CamelCase: `SolarWindsAPM`, `OboeSampler`, `TokenBucket` +- Nest modules logically: `SolarWindsAPM::API::TransactionName` +- Use descriptive names that reflect purpose + +### Methods + +- Use snake_case: `set_transaction_name`, `should_sample?`, `parent_based_algo` +- Use `?` suffix for predicate methods: `ready?`, `boolean?`, `valid?` +- Use `!` suffix for destructive methods or methods with side effects (sparingly) +- Private methods: mark with `private` keyword or `private_class_method :method_name` + +### Constants + +- Use SCREAMING_SNAKE_CASE: `SAMPLE_RATE_ATTRIBUTE`, `OTEL_SAMPLING_DECISION` +- Freeze constant arrays and hashes: `.freeze` +- Group related constants in modules + +### Variables + +- Use snake_case: `sample_state`, `trace_flags`, `parent_span` +- Use descriptive names avoiding abbreviations unless conventional +- Instance variables: `@logger`, `@settings`, `@buckets` +- Class variables: `@@config` (use sparingly, prefer class instance variables) + +## 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 generic instructions in `.github/copilot/instructions/` directory. These provide detailed patterns for: + +- Ruby source files (`.rb`) +- Ruby gemspec files (`.gemspec`) +- Test files (`*_test.rb`) +- Configuration files (YAML, JSON) +- Documentation files (Markdown) + +**Note**: The `.github/copilot/instructions/` folder should be created using the `/agent-create-or-update-generic-instructions.prompt.md` prompt before using these repository-specific instructions. + +## 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 0000000..89d34b3 --- /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/jira.instructions.md b/.github/instructions/jira.instructions.md new file mode 100644 index 0000000..35a711d --- /dev/null +++ b/.github/instructions/jira.instructions.md @@ -0,0 +1,7 @@ +--- +applyTo: "**" +--- +Whenever you are provided with Jira issue key in following format "-XXXXX" or "https://swicloud.atlassian.net/browse/-XXXXX", the first thing you should do is to use `jira_` tools (if provided) to read the Jira issue description and if needed related Jira issues, comments or confluence documents, and take its context into account when answering the question. +If you are not provided with `jira_` tools, just mention that you cannot access Jira issues and provide a general answer based on your knowledge. + + \ No newline at end of file diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md new file mode 100644 index 0000000..0b49b0b --- /dev/null +++ b/.github/instructions/markdown.instructions.md @@ -0,0 +1,54 @@ +--- +applyTo: "**/*.md" +description: 'Documentation and content creation standards' +--- + +## Markdown Content Rules + +The following markdown content rules are enforced in the validators: + +1. **Headings**: Use appropriate heading levels (H2, H3, etc.) to structure your content. Do not use an H1 heading, as this will be generated based on the title. +2. **Lists**: Use bullet points or numbered lists for lists. Ensure proper indentation and spacing. +3. **Code Blocks**: Use fenced code blocks for code snippets. Specify the language for syntax highlighting. +4. **Links**: Use proper markdown syntax for links. Ensure that links are valid and accessible. +5. **Images**: Use proper markdown syntax for images. Include alt text for accessibility. +6. **Tables**: Use markdown tables for tabular data. Ensure proper formatting and alignment. +7. **Line Length**: Limit line length to 400 characters for readability. +8. **Whitespace**: Use appropriate whitespace to separate sections and improve readability. +9. **Front Matter**: Include YAML front matter at the beginning of the file with required metadata fields. + +## Formatting and Structure + +Follow these guidelines for formatting and structuring your markdown content: + +- **Headings**: Use `##` for H2 and `###` for H3. Ensure that headings are used in a hierarchical manner. Recommend restructuring if content includes H4, and more strongly recommend for H5. +- **Lists**: Use `-` for bullet points and `1.` for numbered lists. Indent nested lists with two spaces. +- **Code Blocks**: Use triple backticks (```) to create fenced code blocks. Specify the language after the opening backticks for syntax highlighting (e.g., ```csharp). +- **Links**: Use `[link text](URL)` for links. Ensure that the link text is descriptive and the URL is valid. +- **Images**: Use `![alt text](image URL)` for images. Include a brief description of the image in the alt text. +- **Tables**: Use `|` to create tables. Ensure that columns are properly aligned and headers are included. +- **Line Length**: Break lines at 80 characters to improve readability. Use soft line breaks for long paragraphs. +- **Whitespace**: Use blank lines to separate sections and improve readability. Avoid excessive whitespace. + +## Validation Requirements + +Ensure compliance with the following validation requirements: + +- **Front Matter**: Include the following fields in the YAML front matter: + + - `post_title`: The title of the post. + - `author1`: The primary author of the post. + - `post_slug`: The URL slug for the post. + - `microsoft_alias`: The Microsoft alias of the author. + - `featured_image`: The URL of the featured image. + - `categories`: The categories for the post. These categories must be from the list in /categories.txt. + - `tags`: The tags for the post. + - `ai_note`: Indicate if AI was used in the creation of the post. + - `summary`: A brief summary of the post. Recommend a summary based on the content when possible. + - `post_date`: The publication date of the post. + +- **Content Rules**: Ensure that the content follows the markdown content rules specified above. +- **Formatting**: Ensure that the content is properly formatted and structured according to the guidelines. +- **Validation**: Run the validation tools to check for compliance with the rules and guidelines. + + \ 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 0000000..4d83ba3 --- /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 + +# © 2023 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/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9992394 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,358 @@ +# 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 >= 1.2.0 +- OpenTelemetry Instrumentation All >= 0.31.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 + +```bash +# Install Ruby version manager +# See https://github.com/rbenv/rbenv#installation + +# Install Ruby (minimum 3.1.0) +rbenv install 3.1.2 +rbenv local 3.1.2 + +# Install dependencies with isolated vendoring +gem install bundler +bundle install --path vendor/bundle + +# Verify setup +bundle exec rake -T +``` + +## Development Workflow + +### Quick Interactive Testing + +Load your code changes without building a gem: + +```bash +bundle exec irb -Ilib -r solarwinds_apm +``` + +This is fastest for rapid iteration and testing changes to the library code. + +### Environment Variables for Testing + +Some tests require a service key. Set this before running tests: + +```bash +export SW_APM_SERVICE_KEY=dummy-token-for-testing:test-service +# Or for actual integration tests: +export APM_RUBY_TEST_KEY=your_actual_service_key +``` + +### File Organization + +- **Source code**: `lib/solarwinds_apm/` + - `api/` - Public API methods + - `config.rb` - Configuration management + - `sampling/` - Sampling algorithms + - `support/` - Utility classes + - `patch/` - Instrumentation patches +- **Tests**: `test/` (mirrors lib/ structure) + - `api/*_test.rb` - API tests + - `sampling/*_test.rb` - Sampling tests + - `support/*_test.rb` - Support tests + +## Testing Instructions + +### Run Single Test File + +```bash +bundle exec ruby -I test test/opentelemetry/solarwinds_propagator_test.rb +``` + +### Run Single Test Case + +Use minitest's `-n` flag with a pattern: + +```bash +bundle exec ruby -I test test/opentelemetry/solarwinds_propagator_test.rb -n /trace_state_header/ +``` + +### Run All Tests Locally + +```bash +# Set service key first +export APM_RUBY_TEST_KEY=your_service_key + +# Run all tests +test/run_tests.sh +``` + +The script provides detailed logging and saves results to `log/testrun_*.log`. + +### Run Tests in Docker + +Test against specific Ruby versions in containers: + +```bash +# Default: Ruby 3.1-bullseye +bundle exec rake 'docker_tests[,,,APM_RUBY_TEST_KEY=your_service_key]' + +# Specific Ruby version +bundle exec rake 'docker_tests[3.3-rc,,,APM_RUBY_TEST_KEY=your_service_key]' + +# Alpine variant for specific architecture +bundle exec rake 'docker_tests[3.2-alpine,,linux/amd64,APM_RUBY_TEST_KEY=your_service_key]' + +# Interactive mode (skip tests, get shell) +bundle exec rake 'docker_tests[3.1-bullseye,false,,APM_RUBY_TEST_KEY=your_service_key]' +``` + +### Development Container + +For persistent development environment with all tools: + +```bash +# Start dev container +bundle exec rake docker_dev + +# Inside container: +rbenv global 3.1.2 +bundle install +# Make changes, run tests, etc. +``` + +To resume an existing dev container: + +```bash +bundle exec rake docker_con +``` + +### 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 + +Run RuboCop to check code style: + +```bash +# Check style +bundle exec rake rubocop + +# Auto-fix safe issues +bundle exec rake rubocop auto-safe + +# Auto-fix all issues (use with caution) +bundle exec rake rubocop auto-all +``` + +**All linting issues must be resolved before submitting PR.** + +Configuration: `.rubocop.yml` with custom rules for this project. + +## 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 + +### Before Submitting + +1. **Run all checks:** + ```bash + bundle exec rake rubocop + test/run_tests.sh + ``` + +2. **All RuboCop issues resolved** - No warnings or errors +3. **All tests passing** - 100% success rate required +4. **Add tests for changes** - Even if not specifically requested + +### PR Title Format + +Use descriptive titles that explain the change: +- `Fix sampling decision for parent-based traces` +- `Add support for custom transaction naming` +- `Update OpenTelemetry SDK to 1.2.0` + +### Commit Messages + +- Use clear, descriptive commit messages +- Reference issue numbers when applicable: `Fixes #123` +- Explain the "why" not just the "what" + +## 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 From b5ba65f5f43d2f55d2157c5df7791a3fc2905787 Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Thu, 4 Dec 2025 12:35:15 -0500 Subject: [PATCH 2/2] fix test case --- .github/workflows/ci-markdownlint.yml | 1 + .markdownlint.json | 3 ++- test/patch/sw_pg_patch_integrate_test.rb | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-markdownlint.yml b/.github/workflows/ci-markdownlint.yml index 4449ffd..867833c 100644 --- a/.github/workflows/ci-markdownlint.yml +++ b/.github/workflows/ci-markdownlint.yml @@ -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 4288238..fea10c3 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/test/patch/sw_pg_patch_integrate_test.rb b/test/patch/sw_pg_patch_integrate_test.rb index c1e4070..6b1a029 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;']