FEATURE: Support Amazon Application Load Balancer request tracing header (#150)

FEATURE: Support Amazon Application Load Balancer request tracing header (#150)

HTTP_X_AMZN_TRACE_ID can be used to track delays between app and load balancer

diff --git a/README.md b/README.md
index c883ad4..138ac67 100644
--- a/README.md
+++ b/README.md
@@ -505,7 +505,7 @@ Request Queueing is defined as the time it takes for a request to reach your app
 
 As this metric starts before `prometheus_exporter` can handle the request, you must add a specific HTTP header as early in your infrastructure as possible (we recommend your load balancer or reverse proxy).
 
-Configure your HTTP server / load balancer to add a header `X-Request-Start: t=<MSEC>` when passing the request upstream. For more information, please consult your software manual.
+The Amazon Application Load Balancer [request tracing header](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html) is natively supported. If you are using another upstream entrypoint, you may configure your HTTP server / load balancer to add a header `X-Request-Start: t=<MSEC>` when passing the request upstream. For more information, please consult your software manual.
 
 Hint: we aim to be API-compatible with the big APM solutions, so if you've got requests queueing time configured for them, it should be expected to also work with `prometheus_exporter`.
 
diff --git a/lib/prometheus_exporter/middleware.rb b/lib/prometheus_exporter/middleware.rb
index 2242895..ee8d92a 100644
--- a/lib/prometheus_exporter/middleware.rb
+++ b/lib/prometheus_exporter/middleware.rb
@@ -90,19 +90,24 @@ class PrometheusExporter::Middleware
     Process.clock_gettime(Process::CLOCK_REALTIME)
   end
 
-  # get the content of the x-queue-start or x-request-start header
+  # determine queue start from well-known trace headers
   def queue_start(env)
+
+    # get the content of the x-queue-start or x-request-start header
     value = env['HTTP_X_REQUEST_START'] || env['HTTP_X_QUEUE_START']
     unless value.nil? || value == ''
-      convert_header_to_ms(value.to_s)
+      # nginx returns time as milliseconds with 3 decimal places
+      # apache returns time as microseconds without decimal places
+      # this method takes care to convert both into a proper second + fractions timestamp
+      value = value.to_s.gsub(/t=|\./, '')
+      return "#{value[0, 10]}.#{value[10, 13]}".to_f
     end
-  end
 
-  # nginx returns time as milliseconds with 3 decimal places
-  # apache returns time as microseconds without decimal places
-  # this method takes care to convert both into a proper second + fractions timestamp
-  def convert_header_to_ms(str)
-    str = str.gsub(/t=|\./, '')
-    "#{str[0, 10]}.#{str[10, 13]}".to_f
+    # get the content of the x-amzn-trace-id header
+    # see also: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html
+    value = env['HTTP_X_AMZN_TRACE_ID']
+    value&.split('Root=')&.last&.split('-')&.fetch(1)&.to_i(16)
+
   end
+
 end
diff --git a/test/middleware_test.rb b/test/middleware_test.rb
index 6d97282..7fd5242 100644
--- a/test/middleware_test.rb
+++ b/test/middleware_test.rb
@@ -39,35 +39,47 @@ class PrometheusExporterMiddlewareTest < Minitest::Test
     end
   end
 
-  def test_converting_apache_request_start
-    now_microsec = '1234567890123456'
-
-    header 'X-Request-Start', "t=#{now_microsec}"
+  def assert_valid_headers_response(delta = 0.5)
     get '/'
     assert last_response.ok?
-
     refute_nil client.last_send
     refute_nil client.last_send[:queue_time]
-    assert_in_delta 1, client.last_send[:queue_time], 0.05
+    assert_in_delta 1, client.last_send[:queue_time], delta
   end
 
-  def test_converting_nginx_request_start
-    now = '1234567890.123'
-    header 'X-Request-Start', "t=#{now}"
+  def assert_invalid_headers_response
     get '/'
     assert last_response.ok?
-
     refute_nil client.last_send
-    refute_nil client.last_send[:queue_time]
-    assert_in_delta 1, client.last_send[:queue_time], 0.05
+    assert_nil client.last_send[:queue_time]
   end
 
-  def test_a_header_in_wrong_format
+  def test_converting_apache_request_start
+    now_microsec = '1234567890123456'
+    header 'X-Request-Start', "t=#{now_microsec}"
+    assert_valid_headers_response
+  end
+
+  def test_converting_nginx_request_start
+    now = '1234567890.123'
+    header 'X-Request-Start', "t=#{now}"
+    assert_valid_headers_response
+  end
+
+  def test_request_start_in_wrong_format
     header 'X-Request-Start', ""
-    get '/'
-    assert last_response.ok?
+    assert_invalid_headers_response
+  end
 
-    refute_nil client.last_send
-    assert_nil client.last_send[:queue_time]
+  def test_converting_amzn_trace_id_start
+    now = '1234567890'
+    header 'X-Amzn-Trace-Id', "Root=1-#{now.to_i.to_s(16)}-abc123"
+    assert_valid_headers_response
   end
+
+  def test_amzn_trace_id_in_wrong_format
+    header 'X-Amzn-Trace-Id', ""
+    assert_invalid_headers_response
+  end
+
 end

GitHub sha: a7bea7ae

This commit appears in #150 which was merged by SamSaffron.