diff --git a/.github/workflows/continuous-build.yml b/.github/workflows/continuous-build.yml index 072485e..788dc25 100644 --- a/.github/workflows/continuous-build.yml +++ b/.github/workflows/continuous-build.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: - - macos-latest + - macos-13 - ubuntu-latest ruby: - 2.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0995140..2353699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +0.16.0 (2024-09-05) +------------------- +* Update - Fix issue: Wrong duration for DB transaction event on ROR 7.1 [PR #96](https://github.com/aws/aws-xray-sdk-ruby/pull/96) + +0.15.0 (2023-10-18) +------------------- +* Update - Add ECS metadata allowing cloudwatch-logs to be linked with traces [PR #93](https://github.com/aws/aws-xray-sdk-ruby/pull/93) + 0.14.0 (2023-04-05) ------------------- * Added - Allow list TopicArn for SNS PublishBatch request [PR #82](https://github.com/aws/aws-xray-sdk-ruby/pull/82). diff --git a/lib/aws-xray-sdk/facets/rails/active_record.rb b/lib/aws-xray-sdk/facets/rails/active_record.rb index 678a554..2f238b1 100644 --- a/lib/aws-xray-sdk/facets/rails/active_record.rb +++ b/lib/aws-xray-sdk/facets/rails/active_record.rb @@ -28,9 +28,11 @@ def record(transaction) subsegment = XRay.recorder.begin_subsegment name, namespace: 'remote' # subsegment is nil in case of context missing return if subsegment.nil? - subsegment.start_time = transaction.time.to_f / 1000.0 + # Rails 7.1 introduced time measurement in milliseconds instead seconds of causing xray-sdk to report wrong duration for transaction calls. + # This is being handled in rails 7.2 and later. https://github.com/rails/rails/pull/50779 + subsegment.start_time = (::Rails::VERSION::MAJOR == 7 and ::Rails::VERSION::MINOR == 1) ? transaction.time.to_f/1000 : transaction.time.to_f subsegment.sql = sql - XRay.recorder.end_subsegment end_time: transaction.end.to_f / 1000.0 + XRay.recorder.end_subsegment end_time: (::Rails::VERSION::MAJOR == 7 and ::Rails::VERSION::MINOR == 1) ? transaction.end.to_f/1000 : transaction.end.to_f end private diff --git a/lib/aws-xray-sdk/plugins/ecs.rb b/lib/aws-xray-sdk/plugins/ecs.rb index b511b90..bfe25fe 100644 --- a/lib/aws-xray-sdk/plugins/ecs.rb +++ b/lib/aws-xray-sdk/plugins/ecs.rb @@ -3,19 +3,82 @@ module XRay module Plugins - # Due to lack of ECS container metadata service, the only host information - # available is the host name. module ECS include Logging ORIGIN = 'AWS::ECS::Container'.freeze + # Only compatible with v4! + # The v3 metadata url does not contain cloudwatch informations + METADATA_ENV_KEY = 'ECS_CONTAINER_METADATA_URI_V4' + def self.aws - @@aws ||= begin - { ecs: { container: Socket.gethostname } } + metadata = get_metadata() + + begin + metadata[:ecs][:container] = Socket.gethostname rescue StandardError => e - @@aws = {} Logging.logger.warn %(cannot get the ecs container hostname due to: #{e.message}.) + metadata[:ecs][:container] = nil + end + + @@aws = { + ecs: metadata[:ecs], + cloudwatch_logs: metadata[:cloudwatch_logs] + } + end + + private + + def self.get_metadata() + begin + metadata_uri = URI(ENV[METADATA_ENV_KEY]) + req = Net::HTTP::Get.new(metadata_uri) + metadata_json = do_request(req) + return parse_metadata(metadata_json) + rescue StandardError => e + Logging.logger.warn %(cannot get the ecs instance metadata due to: #{e.message}. Make sure you are using Fargate platform version >=1.4.0) + { ecs: {}, cloudwatch_logs: {} } + end + end + + def self.parse_metadata(json_str) + data = JSON(json_str) + + metadata = { + ecs: { + container_arn: data['ContainerARN'], + }, + cloudwatch_logs: { + log_group: data["LogOptions"]['awslogs-group'], + log_region: data["LogOptions"]['awslogs-region'], + arn: data['ContainerARN'] + } + } + metadata + end + + def self.do_request(request) + begin + response = Net::HTTP.start(request.uri.hostname, read_timeout: 1) { |http| + http.request(request) + } + + if response.code == '200' + return response.body + else + raise(StandardError.new('Unsuccessful response::' + response.code + '::' + response.message)) + end + rescue StandardError => e + # Two attempts in total to complete the request successfully + @retries ||= 0 + if @retries < 1 + @retries += 1 + retry + else + Logging.logger.warn %(Failed to complete request due to: #{e.message}.) + raise e + end end end end diff --git a/lib/aws-xray-sdk/version.rb b/lib/aws-xray-sdk/version.rb index c417bc9..6d6cd95 100644 --- a/lib/aws-xray-sdk/version.rb +++ b/lib/aws-xray-sdk/version.rb @@ -1,3 +1,3 @@ module XRay - VERSION = '0.14.0' + VERSION = '0.16.0' end diff --git a/test/aws-xray-sdk/tc_plugin.rb b/test/aws-xray-sdk/tc_plugin.rb index a9becb3..bbb6ad2 100644 --- a/test/aws-xray-sdk/tc_plugin.rb +++ b/test/aws-xray-sdk/tc_plugin.rb @@ -25,6 +25,7 @@ def test_get_runtime_context WebMock.reset! end + # EC2 Plugin def test_ec2_metadata_v2_successful dummy_json = '{\"availabilityZone\" : \"us-east-2a\", \"imageId\" : \"ami-03cca83dd001d4666\", \"instanceId\" : \"i-07a181803de94c666\", \"instanceType\" : \"t3.xlarge\"}' @@ -43,6 +44,7 @@ def test_ec2_metadata_v2_successful ami_id: 'ami-03cca83dd001d4666' } } + # We should probably use `assert_equal` here ? Always true otherwise... assert expected, XRay::Plugins::EC2.aws WebMock.reset! end @@ -80,4 +82,53 @@ def test_ec2_metadata_fail assert expected, XRay::Plugins::EC2.aws WebMock.reset! end + + # ECS Plugin + def test_ecs_metadata_successful + dummy_metadata_uri = 'http://169.254.170.2/v4/a_random_id' + dummy_json = { + "ContainerARN"=>"arn:aws:ecs:eu-central-1:an_id:container/a_cluster/a_cluster_id/a_task_id", + "LogOptions"=>{"awslogs-group"=>"/ecs/a_service_name", "awslogs-region"=>"eu-central-1", "awslogs-stream"=>"ecs/a_service_name/a_task_id"}, + } + + ENV[XRay::Plugins::ECS::METADATA_ENV_KEY] = dummy_metadata_uri + stub_request(:get, dummy_metadata_uri) + .to_return(status: 200, body: dummy_json.to_json, headers: {}) + + expected = { + ecs: { + container: Socket.gethostname, + container_arn: 'arn:aws:ecs:eu-central-1:an_id:container/a_cluster/a_cluster_id/a_task_id', + }, + cloudwatch_logs: {:log_group=>"/ecs/a_service_name", :log_region=>"eu-central-1", :arn=>"arn:aws:ecs:eu-central-1:an_id:container/a_cluster/a_cluster_id/a_task_id"} + } + assert_equal expected, XRay::Plugins::ECS.aws + WebMock.reset! + ENV.delete(XRay::Plugins::ECS::METADATA_ENV_KEY) + end + + def test_ecs_metadata_fail + dummy_metadata_uri = 'http://169.254.170.2/v4/a_random_id' + ENV['ECS_CONTAINER_METADATA_URI_V4'] = dummy_metadata_uri + + stub_request(:get, dummy_metadata_uri) + .to_raise(StandardError) + + expected = { + ecs: {container: Socket.gethostname}, + cloudwatch_logs: {} + } + assert_equal expected, XRay::Plugins::ECS.aws + WebMock.reset! + ENV.delete(XRay::Plugins::ECS::METADATA_ENV_KEY) + end + + def test_ecs_metadata_not_defined + expected = { + ecs: {container: Socket.gethostname}, + cloudwatch_logs: {} + } + assert_equal expected, XRay::Plugins::ECS.aws + WebMock.reset! + end end