epsagon 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 036b9458b78db168edb2cb90dd4898f812eaa81a3e057abd69fc073fef9552d2
4
+ data.tar.gz: 1b2b2dce82f55073377db72400737a1243f8dab08426780d59f6a5faf62a2a13
5
+ SHA512:
6
+ metadata.gz: 5bbbc9237c61ad7eb0818803eea213ed8b1003210b199c3c29355f1536b6dc4b7e990b628b92b8c015f4fab1c623eb92f07352d6c6cb5b6a00d0aeb12aa0dfc8
7
+ data.tar.gz: 81da4b0e55ecd53569564c22cfe1a986dce0835970bf3a522a7bc100e5b5a6c373275c6ac2bccd9d0a4500dddb8c26dca54d402d499116fa90201ba8de5edef5
data/lib/epsagon.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'net/http'
5
+ require 'bundler/setup'
6
+ require 'opentelemetry'
7
+
8
+ require_relative 'instrumentation/sinatra'
9
+ require_relative 'instrumentation/net_http'
10
+ require_relative 'instrumentation/faraday'
11
+ require_relative 'util'
12
+
13
+ Bundler.require
14
+
15
+ def metadata_only?
16
+ ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false'
17
+ end
18
+
19
+ def debug?
20
+ ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true'
21
+ end
22
+
23
+ # #config opentelemetry with epsaon extensions:
24
+ OpenTelemetry::SDK.configure do |c|
25
+ c.use 'EpsagonSinatraInstrumentation'
26
+ c.use 'EpsagonNetHTTPInstrumentation'
27
+ c.use 'EpsagonFaradayInstrumentation'
28
+ if ENV['EPSAGON_BACKEND']
29
+ c.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
30
+ OpenTelemetry::Exporter::OTLP::Exporter.new(headers: {
31
+ epasgon_token: ENV['EPSAGON_TOKEN'],
32
+ epasgon_app_name: ENV['EPSAGON_APP_NAME']
33
+ },
34
+ endpoint: ENV['EPSAGON_BACKEND'],
35
+ insecure: false)
36
+ )
37
+ else
38
+ c.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
39
+ OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'net/http'
5
+ require 'bundler/setup'
6
+ require 'opentelemetry'
7
+
8
+ require_relative 'instrumentation/sinatra'
9
+ require_relative 'instrumentation/net_http'
10
+ require_relative 'instrumentation/faraday'
11
+ require_relative 'util'
12
+
13
+ Bundler.require
14
+
15
+ def metadata_only?
16
+ ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false'
17
+ end
18
+
19
+ def debug?
20
+ ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true'
21
+ end
22
+
23
+ # #config opentelemetry with epsaon extensions:
24
+ OpenTelemetry::SDK.configure do |c|
25
+ c.use 'EpsagonSinatraInstrumentation'
26
+ c.use 'EpsagonNetHTTPInstrumentation'
27
+ c.use 'EpsagonFaradayInstrumentation'
28
+ if debug?
29
+ c.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
30
+ OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new
31
+ )
32
+ else
33
+ c.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
34
+ EpsagonExporter.new(headers: {
35
+ epasgon_token: ENV['EPSAGON_TOKEN'],
36
+ epasgon_app_name: ENV['EPSAGON_APP_NAME']
37
+ },
38
+ endpoint: '7fybd5lgpc.execute-api.us-east-2.amazonaws.com:443/dev/trace',
39
+ insecure: false)
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require_relative '../util'
5
+
6
+ class EpsagonFaradayMiddleware < ::Faraday::Middleware
7
+ HTTP_METHODS_SYMBOL_TO_STRING = {
8
+ connect: 'CONNECT',
9
+ delete: 'DELETE',
10
+ get: 'GET',
11
+ head: 'HEAD',
12
+ options: 'OPTIONS',
13
+ patch: 'PATCH',
14
+ post: 'POST',
15
+ put: 'PUT',
16
+ trace: 'TRACE'
17
+ }.freeze
18
+
19
+ def call(env)
20
+ http_method = HTTP_METHODS_SYMBOL_TO_STRING[env.method]
21
+ path, path_params = env.url.path.split(';')
22
+
23
+ attributes = {
24
+ 'type' => 'http',
25
+ 'operation' => http_method,
26
+ 'http.scheme' => env.url.scheme,
27
+ 'http.request.path' => path
28
+ }
29
+
30
+ unless metadata_only?
31
+ attributes.merge!(Util.epsagon_query_attributes(env.url.query))
32
+ attributes.merge!({
33
+ 'http.request.path_params' => path_params,
34
+ 'http.request.headers' => env.request_headers.to_json,
35
+ 'http.request.body' => env.body,
36
+ 'http.request.headers.User-Agent' => env.request_headers['User-Agent']
37
+ })
38
+ end
39
+
40
+ tracer.in_span(
41
+ env.url.host,
42
+ attributes: attributes,
43
+ kind: :client
44
+ ) do |span|
45
+ OpenTelemetry.propagation.http.inject(env.request_headers)
46
+
47
+ app.call(env).on_complete { |req| trace_response(span, req.response) }
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :app
54
+
55
+ def tracer
56
+ EpsagonFaradayInstrumentation.instance.tracer
57
+ end
58
+
59
+ def trace_response(span, response)
60
+ span.set_attribute('http.status_code', response.status)
61
+
62
+ unless metadata_only?
63
+ span.set_attribute('http.response.headers', response.headers.to_json)
64
+ span.set_attribute('http.response.body', response.body)
65
+ end
66
+ span.status = OpenTelemetry::Trace::Status.http_to_status(
67
+ response.status
68
+ )
69
+ end
70
+ end
71
+
72
+ module EpsagonFaradayPatch
73
+ def adapter(*args)
74
+ use(:epsagon_open_telemetry) unless @handlers.any? do |handler|
75
+ handler.klass == EpsagonFaradayMiddleware
76
+ end
77
+
78
+ super
79
+ end
80
+ end
81
+
82
+ class EpsagonFaradayInstrumentation < OpenTelemetry::Instrumentation::Base
83
+ install do |_config|
84
+ ::Faraday::Middleware.register_middleware(
85
+ epsagon_open_telemetry: EpsagonFaradayMiddleware
86
+ )
87
+ ::Faraday::RackBuilder.prepend(EpsagonFaradayPatch)
88
+ end
89
+
90
+ present do
91
+ defined?(::Faraday)
92
+ end
93
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry'
4
+
5
+ require_relative '../util'
6
+
7
+ module EpsagonNetHTTPExtension
8
+ HTTP_METHODS_TO_SPAN_NAMES = Hash.new { |h, k| h[k] = "HTTP #{k}" }
9
+ USE_SSL_TO_SCHEME = { false => 'http', true => 'https' }.freeze
10
+
11
+ def request(req, body = nil, &block)
12
+ # Do not trace recursive call for starting the connection
13
+ return super(req, body, &block) unless started?
14
+
15
+ attributes = Hash[OpenTelemetry::Common::HTTP::ClientContext.attributes]
16
+ path_with_params, query = req.path.split('?')
17
+ path, path_params = path_with_params.split(';')
18
+ attributes.merge!({
19
+ 'type' => 'http',
20
+ 'operation' => req.method,
21
+ 'http.scheme' => USE_SSL_TO_SCHEME[use_ssl?],
22
+ 'http.request.path' => path
23
+ })
24
+
25
+ unless metadata_only?
26
+ headers = Hash[req.each_header.to_a]
27
+ attributes.merge!({
28
+ 'http.request.path_params' => path_params,
29
+ 'http.request.body' => body,
30
+ 'http.request.headers' => headers.to_json,
31
+ 'http.request.headers.User-Agent' => headers['user-agent']
32
+ })
33
+ attributes.merge!(Util.epsagon_query_attributes(query))
34
+ end
35
+ tracer.in_span(
36
+ @address,
37
+ attributes: attributes,
38
+ kind: :Client
39
+ ) do |span|
40
+ OpenTelemetry.propagation.http.inject(req)
41
+
42
+ super(req, body, &block).tap do |response|
43
+ annotate_span_with_response!(span, response)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def annotate_span_with_response!(span, response)
51
+ return unless response&.code
52
+
53
+ status_code = response.code.to_i
54
+
55
+ span.set_attribute('http.status_code', status_code)
56
+ unless metadata_only?
57
+ span.set_attribute('http.response.headers', Hash[response.each_header.to_a].to_json)
58
+ span.set_attribute('http.response.body', response.body)
59
+ end
60
+ span.status = OpenTelemetry::Trace::Status.http_to_status(
61
+ status_code
62
+ )
63
+ end
64
+
65
+ def tracer
66
+ EpsagonSinatraInstrumentation.instance.tracer
67
+ end
68
+ end
69
+
70
+ class EpsagonNetHTTPInstrumentation < OpenTelemetry::Instrumentation::Base
71
+ install do |_|
72
+ ::Net::HTTP.prepend(EpsagonNetHTTPExtension)
73
+ end
74
+
75
+ present do
76
+ defined?(::Net::HTTP)
77
+ end
78
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'opentelemetry'
5
+
6
+ require_relative '../util'
7
+
8
+ class EpsagonTracerMiddleware
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ request = Rack::Request.new(env)
15
+ path, path_params = request.path.split(';')
16
+ request_headers = JSON.generate(Hash[*env.select { |k, _v| k.start_with? 'HTTP_' }
17
+ .collect { |k, v| [k.sub(/^HTTP_/, ''), v] }
18
+ .collect { |k, v| [k.split('_').collect(&:capitalize).join('-'), v] }
19
+ .sort
20
+ .flatten])
21
+
22
+ attributes = {
23
+ 'operation' => env['REQUEST_METHOD'],
24
+ 'type' => 'http',
25
+ 'http.scheme' => env['PATH_INFO'],
26
+ 'http.request.path' => path,
27
+ 'http.request.headers' => request_headers
28
+ }
29
+
30
+ unless metadata_only?
31
+ request.body.rewind
32
+ request_body = request.body.read
33
+ request.body.rewind
34
+
35
+ attributes.merge!(Util.epsagon_query_attributes(request.query_string))
36
+
37
+ attributes.merge!({
38
+ 'http.request.body' => request_body,
39
+ 'http.request.path_params' => path_params,
40
+ 'http.request.headers.User-Agent' => env['HTTP_USER_AGENT']
41
+ })
42
+ end
43
+
44
+ tracer.in_span(
45
+ env['HTTP_HOST'],
46
+ attributes: attributes,
47
+ kind: :Server,
48
+ with_parent: parent_context(env)
49
+ ) do |http_span|
50
+ tracer.in_span('sinatra') do |framework_span|
51
+ app.call(env).tap { |resp| trace_response(http_span, framework_span, env, resp) }
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :app
59
+
60
+ def parent_context(env)
61
+ OpenTelemetry.propagation.http.extract(env)
62
+ end
63
+
64
+ def tracer
65
+ EpsagonSinatraInstrumentation.instance.tracer
66
+ end
67
+
68
+ def trace_response(http_span, framework_span, env, resp)
69
+ status, headers, response_body = resp
70
+
71
+ unless metadata_only?
72
+ http_span.set_attribute('http.response.headers', JSON.generate(headers))
73
+ http_span.set_attribute('http.response.body', response_body.join)
74
+ end
75
+
76
+ http_span.set_attribute('http.status_code', status)
77
+ framework_span.set_attribute('http.route', env['sinatra.route'].split.last) if env['sinatra.route']
78
+ http_span.status = OpenTelemetry::Trace::Status.http_to_status(status)
79
+ end
80
+ end
81
+
82
+ module EpsagonTracerExtension
83
+ # Sinatra hook after extension is registered
84
+ def self.registered(app)
85
+ # Create tracing `render` method
86
+ ::Sinatra::Base.module_eval do
87
+ def render(_engine, data, *)
88
+ template_name = data.is_a?(Symbol) ? data : :literal
89
+
90
+ Sinatra::Instrumentation.instance.tracer.in_span(
91
+ 'sinatra.render_template',
92
+ attributes: { 'sinatra.template_name' => template_name.to_s }
93
+ ) do
94
+ super
95
+ end
96
+ end
97
+ end
98
+
99
+ app.use EpsagonTracerMiddleware
100
+ end
101
+ end
102
+
103
+ class EpsagonSinatraInstrumentation < OpenTelemetry::Instrumentation::Base
104
+ install do |_|
105
+ ::Sinatra::Base.register EpsagonTracerExtension
106
+ end
107
+
108
+ present do
109
+ defined?(::Sinatra)
110
+ end
111
+ end
data/lib/util.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module Util
6
+ def self.epsagon_query_attributes(query_string)
7
+ if query_string&.include? '='
8
+ { 'http.request.query_params' => CGI.parse(query_string).to_json }
9
+ else
10
+ { 'http.request.query' => query_string }
11
+ end
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epsagon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Assaf Paneth
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Epsagon for ruby
14
+ email: assaf.paneth@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/epsagon.rb
20
+ - lib/epsagon_opentelemetry.rb
21
+ - lib/instrumentation/faraday.rb
22
+ - lib/instrumentation/net_http.rb
23
+ - lib/instrumentation/sinatra.rb
24
+ - lib/util.rb
25
+ homepage: https://rubygems.org/gems/epsagon
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.1.4
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Epsagon for ruby
48
+ test_files: []