Verified Commit 846969df authored by Hercules Merscher's avatar Hercules Merscher 🌴
Browse files

feat: Labkit::CoveredExperience::Configuration

parent 63cd9222
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ require 'labkit/covered_experience/error'
require 'labkit/covered_experience/experience'
require 'labkit/covered_experience/null'
require 'labkit/covered_experience/registry'
require 'labkit/logging/json_logger'

module Labkit
  # Labkit::CoveredExperience namespace module.
@@ -12,7 +13,24 @@ module Labkit
  # specific events or activities within the application that are measured
  # and reported for performance monitoring and analysis.
  module CoveredExperience
    # Configuration class for CoveredExperience
    class Configuration
      attr_accessor :logger

      def initialize
        @logger = Labkit::Logging::JsonLogger.new($stdout)
      end
    end

    class << self
      def configuration
        @configuration ||= Configuration.new
      end

      def configure
        yield(configuration) if block_given?
      end

      def registry
        @registry ||= Registry.new
      end
+14 −0
Original line number Diff line number Diff line
@@ -4,6 +4,20 @@ This module covers the definition for Covered Experiences, as described in the [

## Configuration

### Logger Configuration

By default, `Labkit::CoveredExperience` uses `Labkit::Logging::JsonLogger.new($stdout)` for logging. You can configure a custom logger:

```ruby
Labkit::CoveredExperience.configure do |config|
  config.logger = Labkit::Logging::JsonLogger.new($stdout)
end
```

This configuration affects all Covered Experience instances and their logging output.

### Covered Experience Definitions

Covered experience definitions will be lazy loaded from the default directory (`config/covered_experiences`).

Create a new covered experience file in the registry directory, e.g. config/covered_experiences/merge_request_creation.yaml
+1 −2
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@

require 'labkit/context'
require 'labkit/covered_experience/error'
require 'labkit/logging/json_logger'

module Labkit
  module CoveredExperience
@@ -186,7 +185,7 @@ module Labkit
      end

      def logger
        @logger ||= Labkit::Logging::JsonLogger.new($stdout)
        Labkit::CoveredExperience.configuration.logger
      end
    end
  end
+1 −2
Original line number Diff line number Diff line
@@ -4,7 +4,6 @@ require 'forwardable'
require 'json-schema'
require 'pathname'
require 'yaml'
require 'labkit/logging/json_logger'

module Labkit
  module CoveredExperience
@@ -99,7 +98,7 @@ module Labkit
      end

      def logger
        @logger ||= Labkit::Logging::JsonLogger.new($stdout)
        Labkit::CoveredExperience.configuration.logger
      end
    end
  end
+38 −26
Original line number Diff line number Diff line
# frozen_string_literal: true

require 'json'

# RSpec matchers for testing Labkit CoveredExperience functionality
#
# This file must be explicitly required in your test setup:
@@ -32,27 +34,43 @@ module Labkit
        end

        def logger
          ::RSpec.current_example.metadata[:logger] ||= begin
            instance = Labkit::Logging::JsonLogger.new($stdout)
            allow(Labkit::Logging::JsonLogger).to receive(:new).with($stdout).and_return(instance) # rubocop:disable CodeReuse/ActiveRecord -- false positive
            instance
          end
          Labkit::CoveredExperience.configuration.logger
        end

        def allow_logger_call(covered_experience_id, checkpoint_type, extra = {})
          return unless logger
        def expect_log_for(covered_experience_id, event_type, extra_args = {})
          # Get the current logger's output (assumes StringIO for testing)
          log_output = logger.instance_variable_get(:@logdev).dev
          raise 'Logger does not using StringIO for test' unless log_output.respond_to?(:string)

          log_content = log_output.string

          expected_log_data = {
            **extra,
            checkpoint: checkpoint_type,
            # always present values
          expected_attrs = {
            checkpoint: event_type,
            **attributes(covered_experience_id),
            start_time: be_a(Time),
            elapsed: be_a(Numeric),
            urgency_threshold_in_seconds: be_a(Numeric)
            **extra_args
          }

          allow(logger).to receive(:info).with(hash_including(expected_log_data)).and_call_original # rubocop:disable CodeReuse/ActiveRecord -- false positive
          # Parse each log line and check if the log line with the set of expected attributes
          # is present and matches the expected attributes
          log_content.split("\n").any? do |line|
            next if line.strip.empty?

            begin
              log_entry = JSON.parse(line, symbolize_names: true)
              expected_attrs.all? do |key, value|
                case value
                when Class
                  log_entry[key].is_a?(value)
                when Regexp
                  log_entry[key]&.match?(value)
                else
                  log_entry[key] == value
                end
              end
            rescue JSON::ParserError
              false
            end
          end
        end
      end
    end
@@ -81,15 +99,13 @@ RSpec::Matchers.define :start_covered_experience do |covered_experience_id|

    checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i

    allow_logger_call(covered_experience_id, "start")

    actual.call

    checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i

    @checkpoint_change = checkpoint_after - checkpoint_before

    @checkpoint_change == 1
    @checkpoint_change == 1 && expect_log_for(covered_experience_id, "start")
  end

  failure_message do
@@ -120,14 +136,12 @@ RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id|

    checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i

    allow_logger_call(covered_experience_id, "intermediate", checkpoint_time: be_a(Time))

    actual.call

    checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
    @checkpoint_change = checkpoint_after - checkpoint_before

    @checkpoint_change == 1
    @checkpoint_change == 1 && expect_log_for(covered_experience_id, "intermediate")
  end

  failure_message do
@@ -182,10 +196,6 @@ RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, e
    total_before = total_counter&.get(labels.merge(error: error)).to_i
    apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i

    attrs = { end_time: be_a(Time) }
    attrs.merge!(error: true, error_message: be_a(String)) if error
    allow_logger_call(covered_experience_id, "end", attrs)

    actual.call

    checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
@@ -195,7 +205,9 @@ RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, e
    @total_change = total_after - total_before
    @apdex_change = apdex_after - apdex_before

    @checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1)
    expected_log_attrs = error ? { error: true } : {}

    @checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1) && expect_log_for(covered_experience_id, "end", expected_log_attrs)
  end

  failure_message do
Loading