hypertrace-agent 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +346 -0
- data/LICENSE +201 -0
- data/README.md +27 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/build_proto.sh +3 -0
- data/examples/sinatra/app.rb +7 -0
- data/lib/hypertrace/config/config.rb +77 -0
- data/lib/hypertrace/config/config_pb.rb +78 -0
- data/lib/hypertrace/config/default.rb +31 -0
- data/lib/hypertrace/config/environment.rb +142 -0
- data/lib/hypertrace/config.rb +8 -0
- data/lib/hypertrace/env_var_settings.rb +14 -0
- data/lib/hypertrace/instrumentation/data_capture.rb +57 -0
- data/lib/hypertrace/instrumentation/faraday_patch.rb +40 -0
- data/lib/hypertrace/instrumentation/http_patch.rb +41 -0
- data/lib/hypertrace/instrumentation/net_http_patch.rb +46 -0
- data/lib/hypertrace/instrumentation/rack.rb +58 -0
- data/lib/hypertrace/instrumentation/rack_compatible.rb +65 -0
- data/lib/hypertrace/instrumentation/rest_client_patch.rb +61 -0
- data/lib/hypertrace/instrumentation/sinatra.rb +46 -0
- data/lib/hypertrace/instrumentation.rb +4 -0
- data/lib/hypertrace/logging.rb +28 -0
- data/lib/hypertrace/ruby_agent.rb +156 -0
- data/lib/hypertrace/version.rb +5 -0
- data/lib/hypertrace.rb +13 -0
- data/rubyagent.gemspec +53 -0
- data/sig/rubyagent.rbs +4 -0
- metadata +288 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
class Hypertrace::Config::Config
|
3
|
+
include Hypertrace::Logging
|
4
|
+
attr_reader :config
|
5
|
+
def initialize
|
6
|
+
@config = load_config
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_config
|
10
|
+
# Order of loading:
|
11
|
+
# 1.) Defaults
|
12
|
+
# 2.) Overriden by config file
|
13
|
+
# 3.) Overriden by env vars
|
14
|
+
config = Marshal.load(Marshal.dump(DEFAULT_AGENT_CONFIG))
|
15
|
+
|
16
|
+
file_config = load_file
|
17
|
+
config = merge_config(config, file_config)
|
18
|
+
|
19
|
+
env_config = Hypertrace::Config::Environment.load_config_from_env
|
20
|
+
config = merge_config(config, env_config)
|
21
|
+
|
22
|
+
log.info "Finalized config loaded"
|
23
|
+
log.info config
|
24
|
+
|
25
|
+
proto_config = Hypertrace::Agent::Config::V1::AgentConfig.new
|
26
|
+
enabled = Google::Protobuf::BoolValue.new({value: config[:enabled]})
|
27
|
+
proto_config.enabled = enabled
|
28
|
+
proto_config.propagation_formats += config[:propagation_formats]
|
29
|
+
service_name = Google::Protobuf::StringValue.new({value: config[:service_name]})
|
30
|
+
proto_config.service_name = service_name
|
31
|
+
|
32
|
+
proto_config.reporting = Hypertrace::Agent::Config::V1::Reporting.new
|
33
|
+
endpoint = Google::Protobuf::StringValue.new({value: config[:reporting][:endpoint]})
|
34
|
+
proto_config.reporting.endpoint = endpoint
|
35
|
+
secure = Google::Protobuf::BoolValue.new({value: config[:reporting][:secure]})
|
36
|
+
proto_config.reporting.secure = secure
|
37
|
+
|
38
|
+
proto_config.reporting.trace_reporter_type = config[:reporting][:trace_reporter_type]
|
39
|
+
|
40
|
+
proto_config.data_capture = Hypertrace::Agent::Config::V1::DataCapture.new
|
41
|
+
%i[http_headers http_body rpc_metadata rpc_body].each do |field|
|
42
|
+
message_instance = Hypertrace::Agent::Config::V1::Message.new
|
43
|
+
message_instance.request = Google::Protobuf::BoolValue.new({value:config[:data_capture][field][:request]})
|
44
|
+
message_instance.response = Google::Protobuf::BoolValue.new({value:config[:data_capture][field][:response]})
|
45
|
+
proto_config.data_capture[field.to_s] = message_instance
|
46
|
+
end
|
47
|
+
proto_config.data_capture.body_max_size_bytes = Google::Protobuf::Int32Value.new({value: config[:data_capture][:body_max_size_bytes]})
|
48
|
+
|
49
|
+
proto_config.resource_attributes.merge(config[:resource_attributes])
|
50
|
+
proto_config
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_file
|
54
|
+
file_path = Hypertrace::EnvVarSettings.env_value('CONFIG_FILE')
|
55
|
+
return {} if file_path.nil?
|
56
|
+
|
57
|
+
begin
|
58
|
+
yaml_content = File.read(file_path)
|
59
|
+
config_file_contents = YAML.load(yaml_content, symbolize_names: true)
|
60
|
+
rescue => e
|
61
|
+
log.warn "failed to load config file: #{file_path}"
|
62
|
+
end
|
63
|
+
config_file_contents || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge_config(base_config, overriding_config)
|
67
|
+
overriding_config.each_key do |key|
|
68
|
+
if base_config.key?(key) && base_config[key].instance_of?(Hash)
|
69
|
+
base_config[key] = merge_config(base_config[key], overriding_config[key])
|
70
|
+
else
|
71
|
+
base_config[key] = overriding_config[key]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
base_config
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: config.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
require 'google/protobuf/wrappers_pb'
|
7
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
8
|
+
add_file("config.proto", :syntax => :proto3) do
|
9
|
+
add_message "hypertrace.agent.config.v1.AgentConfig" do
|
10
|
+
optional :service_name, :message, 1, "google.protobuf.StringValue"
|
11
|
+
optional :reporting, :message, 2, "hypertrace.agent.config.v1.Reporting"
|
12
|
+
optional :data_capture, :message, 3, "hypertrace.agent.config.v1.DataCapture"
|
13
|
+
repeated :propagation_formats, :enum, 4, "hypertrace.agent.config.v1.PropagationFormat"
|
14
|
+
optional :enabled, :message, 5, "google.protobuf.BoolValue"
|
15
|
+
optional :javaagent, :message, 6, "hypertrace.agent.config.v1.JavaAgent"
|
16
|
+
map :resource_attributes, :string, :string, 7
|
17
|
+
end
|
18
|
+
add_message "hypertrace.agent.config.v1.Reporting" do
|
19
|
+
optional :endpoint, :message, 1, "google.protobuf.StringValue"
|
20
|
+
optional :secure, :message, 2, "google.protobuf.BoolValue"
|
21
|
+
optional :token, :message, 3, "google.protobuf.StringValue"
|
22
|
+
optional :trace_reporter_type, :enum, 5, "hypertrace.agent.config.v1.TraceReporterType"
|
23
|
+
optional :cert_file, :message, 6, "google.protobuf.StringValue"
|
24
|
+
optional :metric_endpoint, :message, 7, "google.protobuf.StringValue"
|
25
|
+
optional :metric_reporter_type, :enum, 8, "hypertrace.agent.config.v1.MetricReporterType"
|
26
|
+
end
|
27
|
+
add_message "hypertrace.agent.config.v1.Message" do
|
28
|
+
optional :request, :message, 1, "google.protobuf.BoolValue"
|
29
|
+
optional :response, :message, 2, "google.protobuf.BoolValue"
|
30
|
+
end
|
31
|
+
add_message "hypertrace.agent.config.v1.DataCapture" do
|
32
|
+
optional :http_headers, :message, 1, "hypertrace.agent.config.v1.Message"
|
33
|
+
optional :http_body, :message, 2, "hypertrace.agent.config.v1.Message"
|
34
|
+
optional :rpc_metadata, :message, 3, "hypertrace.agent.config.v1.Message"
|
35
|
+
optional :rpc_body, :message, 4, "hypertrace.agent.config.v1.Message"
|
36
|
+
optional :body_max_size_bytes, :message, 5, "google.protobuf.Int32Value"
|
37
|
+
optional :body_max_processing_size_bytes, :message, 6, "google.protobuf.Int32Value"
|
38
|
+
end
|
39
|
+
add_message "hypertrace.agent.config.v1.JavaAgent" do
|
40
|
+
repeated :filter_jar_paths, :message, 1, "google.protobuf.StringValue"
|
41
|
+
end
|
42
|
+
add_enum "hypertrace.agent.config.v1.PropagationFormat" do
|
43
|
+
value :B3, 0
|
44
|
+
value :TRACECONTEXT, 1
|
45
|
+
end
|
46
|
+
add_enum "hypertrace.agent.config.v1.TraceReporterType" do
|
47
|
+
value :UNSPECIFIED, 0
|
48
|
+
value :ZIPKIN, 1
|
49
|
+
value :OTLP, 2
|
50
|
+
value :LOGGING, 3
|
51
|
+
value :NONE, 4
|
52
|
+
end
|
53
|
+
add_enum "hypertrace.agent.config.v1.MetricReporterType" do
|
54
|
+
value :METRIC_REPORTER_TYPE_UNSPECIFIED, 0
|
55
|
+
value :METRIC_REPORTER_TYPE_OTLP, 1
|
56
|
+
value :METRIC_REPORTER_TYPE_PROMETHEUS, 2
|
57
|
+
value :METRIC_REPORTER_TYPE_LOGGING, 3
|
58
|
+
value :METRIC_REPORTER_TYPE_NONE, 4
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Hypertrace
|
64
|
+
module Agent
|
65
|
+
module Config
|
66
|
+
module V1
|
67
|
+
AgentConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.AgentConfig").msgclass
|
68
|
+
Reporting = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.Reporting").msgclass
|
69
|
+
Message = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.Message").msgclass
|
70
|
+
DataCapture = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.DataCapture").msgclass
|
71
|
+
JavaAgent = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.JavaAgent").msgclass
|
72
|
+
PropagationFormat = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.PropagationFormat").enummodule
|
73
|
+
TraceReporterType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.TraceReporterType").enummodule
|
74
|
+
MetricReporterType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hypertrace.agent.config.v1.MetricReporterType").enummodule
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
DEFAULT_AGENT_CONFIG = {
|
2
|
+
'enabled': true,
|
3
|
+
'propagation_formats': ['TRACECONTEXT'],
|
4
|
+
'service_name': 'rubyagent',
|
5
|
+
'reporting': {
|
6
|
+
'endpoint': 'http://localhost:4318/v1/traces',
|
7
|
+
'secure': false,
|
8
|
+
'trace_reporter_type': 'OTLP',
|
9
|
+
'token': '',
|
10
|
+
},
|
11
|
+
'data_capture': {
|
12
|
+
'http_headers': {
|
13
|
+
'request': true,
|
14
|
+
'response': true,
|
15
|
+
},
|
16
|
+
'http_body': {
|
17
|
+
'request': true,
|
18
|
+
'response': true,
|
19
|
+
},
|
20
|
+
'rpc_metadata': {
|
21
|
+
'request': true,
|
22
|
+
'response': true,
|
23
|
+
},
|
24
|
+
'rpc_body': {
|
25
|
+
'request': true,
|
26
|
+
'response': true,
|
27
|
+
},
|
28
|
+
'body_max_size_bytes': 131072
|
29
|
+
},
|
30
|
+
'resource_attributes': {}
|
31
|
+
}.freeze
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Hypertrace::Config::Environment
|
2
|
+
class << self
|
3
|
+
include Hypertrace::Logging
|
4
|
+
|
5
|
+
def load_config_from_env
|
6
|
+
config = {}
|
7
|
+
|
8
|
+
service_name = Hypertrace::EnvVarSettings.env_value('SERVICE_NAME')
|
9
|
+
if service_name
|
10
|
+
log.debug "[env] Loaded SERVICE_NAME from env"
|
11
|
+
config[:service_name] = service_name
|
12
|
+
end
|
13
|
+
|
14
|
+
# Reporting
|
15
|
+
config[:reporting] = {}
|
16
|
+
reporting_endpoint = Hypertrace::EnvVarSettings.env_value('REPORTING_ENDPOINT')
|
17
|
+
if reporting_endpoint
|
18
|
+
log.debug "[env] Loaded REPORTING_ENDPOINT from env"
|
19
|
+
config[:reporting][:endpoint] = reporting_endpoint
|
20
|
+
end
|
21
|
+
|
22
|
+
reporter_type = Hypertrace::EnvVarSettings.env_value('REPORTING_TRACE_REPORTER_TYPE')
|
23
|
+
if reporter_type
|
24
|
+
log.debug "[env] Loaded REPORTING_TRACE_REPORTER_TYPE from env"
|
25
|
+
config[:reporting][:trace_reporter_type] = reporter_type
|
26
|
+
end
|
27
|
+
|
28
|
+
reporting_secure = Hypertrace::EnvVarSettings.env_value('REPORTING_SECURE')
|
29
|
+
if reporting_secure
|
30
|
+
log.debug "[env] Loaded REPORTING_SECURE from env"
|
31
|
+
config[:reporting][:secure] = is_true(reporting_secure)
|
32
|
+
end
|
33
|
+
|
34
|
+
reporting_token = Hypertrace::EnvVarSettings.env_value('REPORTING_TOKEN')
|
35
|
+
if reporting_token
|
36
|
+
log.debug("[env] Loaded REPORTING_TOKEN from env")
|
37
|
+
config[:reporting][:token] = reporting_token
|
38
|
+
end
|
39
|
+
|
40
|
+
config.delete(:reporting) if config[:reporting].empty?
|
41
|
+
|
42
|
+
# Data capture
|
43
|
+
config[:data_capture] = {}
|
44
|
+
|
45
|
+
config[:data_capture][:http_headers] = {}
|
46
|
+
headers_request = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_HTTP_HEADERS_REQUEST')
|
47
|
+
if headers_request
|
48
|
+
log.debug("[env] Loaded DATA_CAPTURE_HTTP_HEADERS_REQUEST from env")
|
49
|
+
config[:data_capture][:http_headers][:request] = is_true(headers_request)
|
50
|
+
end
|
51
|
+
|
52
|
+
headers_response = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_HTTP_HEADERS_RESPONSE')
|
53
|
+
if headers_response
|
54
|
+
log.debug("[env] Loaded DATA_CAPTURE_HTTP_HEADERS_RESPONSE from env")
|
55
|
+
config[:data_capture][:http_headers][:response] = is_true(headers_response)
|
56
|
+
end
|
57
|
+
|
58
|
+
config[:data_capture].delete(:http_headers) if config[:data_capture][:http_headers].empty?
|
59
|
+
|
60
|
+
config[:data_capture][:http_body] = {}
|
61
|
+
body_request = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_HTTP_BODY_REQUEST')
|
62
|
+
if body_request
|
63
|
+
log.debug("[env] Loaded DATA_CAPTURE_HTTP_BODY_REQUEST from env")
|
64
|
+
config[:data_capture][:http_body][:request] = is_true(body_request)
|
65
|
+
end
|
66
|
+
|
67
|
+
body_response = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_HTTP_BODY_RESPONSE')
|
68
|
+
if body_response
|
69
|
+
log.debug("[env] Loaded DATA_CAPTURE_HTTP_BODY_RESPONSE from env")
|
70
|
+
config[:data_capture][:http_body][:response] = is_true(body_response)
|
71
|
+
end
|
72
|
+
|
73
|
+
config[:data_capture].delete(:http_body) if config[:data_capture][:http_body].empty?
|
74
|
+
|
75
|
+
config[:data_capture][:rpc_metadata] = {}
|
76
|
+
rpc_metadata_request = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_RPC_METADATA_REQUEST')
|
77
|
+
if rpc_metadata_request
|
78
|
+
log.debug("[env] Loaded DATA_CAPTURE_RPC_METADATA_REQUEST from env")
|
79
|
+
config[:data_capture][:rpc_metadata][:request] = is_true(rpc_metadata_request)
|
80
|
+
end
|
81
|
+
|
82
|
+
rpc_metadata_response = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_RPC_METADATA_RESPONSE')
|
83
|
+
if rpc_metadata_response
|
84
|
+
log.debug("[env] Loaded DATA_CAPTURE_RPC_METADATA_RESPONSE from env")
|
85
|
+
config[:data_capture][:rpc_metadata][:response] = is_true(rpc_metadata_response)
|
86
|
+
end
|
87
|
+
config[:data_capture].delete(:rpc_metadata) if config[:data_capture][:rpc_metadata].empty?
|
88
|
+
|
89
|
+
config[:data_capture][:rpc_body] = {}
|
90
|
+
rpc_body_request = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_RPC_BODY_REQUEST')
|
91
|
+
if rpc_body_request
|
92
|
+
log.debug("[env] Loaded DATA_CAPTURE_RPC_BODY_REQUEST from env")
|
93
|
+
config[:data_capture][:rpc_body][:request] = is_true(rpc_body_request)
|
94
|
+
end
|
95
|
+
|
96
|
+
rpc_body_response = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_RPC_BODY_RESPONSE')
|
97
|
+
if rpc_body_response
|
98
|
+
log.debug("[env] Loaded DATA_CAPTURE_RPC_BODY_RESPONSE from env")
|
99
|
+
config[:data_capture][:rpc_body][:response] = is_true(rpc_body_response)
|
100
|
+
end
|
101
|
+
config[:data_capture].delete(:rpc_body) if config[:data_capture][:rpc_body].empty?
|
102
|
+
|
103
|
+
body_max_size_bytes = Hypertrace::EnvVarSettings.env_value('DATA_CAPTURE_BODY_MAX_SIZE_BYTES')
|
104
|
+
if body_max_size_bytes
|
105
|
+
log.debug("[env] Loaded DATA_CAPTURE_BODY_MAX_SIZE_BYTES from env")
|
106
|
+
config[:data_capture][:body_max_size_bytes] = body_max_size_bytes.to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
config.delete(:data_capture) if config[:data_capture].empty?
|
110
|
+
|
111
|
+
propagation_formats = Hypertrace::EnvVarSettings.env_value(:propagation_formats)
|
112
|
+
if propagation_formats && propagation_formats.length > 0
|
113
|
+
log.debug("[env] Loaded PROPAGATION_FORMATS from env")
|
114
|
+
config[:propagation_formats] = propagation_formats.split(',')
|
115
|
+
end
|
116
|
+
|
117
|
+
enabled = Hypertrace::EnvVarSettings.env_value(:enabled)
|
118
|
+
if enabled
|
119
|
+
log.debug("[env] Loaded ENABLED from env")
|
120
|
+
config[:enabled] = is_true(enabled)
|
121
|
+
end
|
122
|
+
|
123
|
+
resource_attributes = Hypertrace::EnvVarSettings.env_value(:resource_attributes)
|
124
|
+
if resource_attributes
|
125
|
+
config[:resource_attributes] = {}
|
126
|
+
log.debug('[env] Loaded RESOURCE_ATTRIBUTES from env')
|
127
|
+
groups = resource_attributes.split(',')
|
128
|
+
groups.each do |group|
|
129
|
+
key, value = group.split('=')
|
130
|
+
config[:resource_attributes][key] = value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
return config
|
135
|
+
end
|
136
|
+
|
137
|
+
def is_true value
|
138
|
+
value.downcase == 'true'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hypertrace::EnvVarSettings
|
4
|
+
PREFIXES = ['HT']
|
5
|
+
class << self
|
6
|
+
def env_value target_key
|
7
|
+
PREFIXES.each do |prefix|
|
8
|
+
key = "#{prefix}_#{target_key}"
|
9
|
+
return ENV[key] if ENV.key?(key)
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Hypertrace::Instrumentation::DataCapture
|
2
|
+
TYPE_REQUEST = 'request'
|
3
|
+
TYPE_RESPONSE = 'response'
|
4
|
+
CONTENT_TYPE_SUBSTRINGS = %w[json x-www-form-urlencoded]
|
5
|
+
|
6
|
+
def self.headers_to_attribute_keys header_hash, type, &block
|
7
|
+
return {} unless header_allowed_by_config?(type)
|
8
|
+
|
9
|
+
attrs = {}
|
10
|
+
header_hash.each do |header_key, header_value|
|
11
|
+
attr_key = "http.#{type}.header.#{header_key.downcase}"
|
12
|
+
header_value = header_value.join(',') if header_value.is_a?(Array)
|
13
|
+
if block_given?
|
14
|
+
yield attr_key, header_value
|
15
|
+
else
|
16
|
+
attrs[attr_key] = header_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
return if block_given?
|
20
|
+
return attrs
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.capturable_body body_object
|
24
|
+
max_capture = Hypertrace::RubyAgent.config.data_capture.body_max_size_bytes.value
|
25
|
+
if body_object.is_a?(String)
|
26
|
+
return body_object.byteslice(0..max_capture)
|
27
|
+
elsif body_object.is_a?(StringIO)
|
28
|
+
result = body_object.read(max_capture)
|
29
|
+
body_object.rewind
|
30
|
+
return result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.can_capture?(content_type, type)
|
35
|
+
return false unless content_type
|
36
|
+
return false unless body_allowed_by_config?(type)
|
37
|
+
|
38
|
+
content_type = content_type.downcase
|
39
|
+
CONTENT_TYPE_SUBSTRINGS.each do |substring|
|
40
|
+
if content_type.include?(substring)
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.header_allowed_by_config? type
|
48
|
+
return Hypertrace::RubyAgent.config.data_capture.http_headers.request.value if type == TYPE_REQUEST
|
49
|
+
Hypertrace::RubyAgent.config.data_capture.http_headers.response.value
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.body_allowed_by_config? type
|
53
|
+
return Hypertrace::RubyAgent.config.data_capture.http_body.request.value if type == TYPE_REQUEST
|
54
|
+
Hypertrace::RubyAgent.config.data_capture.http_body.response.value
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class OpenTelemetry::Instrumentation::Faraday::Middlewares::TracerMiddleware < ::Faraday::Middleware
|
2
|
+
def call(env)
|
3
|
+
http_method = HTTP_METHODS_SYMBOL_TO_STRING[env.method]
|
4
|
+
attributes = span_creation_attributes(
|
5
|
+
http_method: http_method, url: env.url
|
6
|
+
)
|
7
|
+
request_headers = env.request_headers.to_h
|
8
|
+
header_attrs = Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(request_headers,
|
9
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
10
|
+
|
11
|
+
content_type = request_headers['Content-Type']
|
12
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
13
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(env&.request_body&.to_s)
|
14
|
+
attributes['http.request.body'] = body_cap if body_cap
|
15
|
+
end
|
16
|
+
|
17
|
+
attributes.merge!(header_attrs)
|
18
|
+
tracer.in_span(
|
19
|
+
"HTTP #{http_method}", attributes: attributes, kind: :client
|
20
|
+
) do |span|
|
21
|
+
OpenTelemetry.propagation.inject(env.request_headers)
|
22
|
+
|
23
|
+
app.call(env).on_complete do |resp|
|
24
|
+
resp = Faraday::Response.new(resp)
|
25
|
+
resp_headers = resp.headers.to_h
|
26
|
+
Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(resp_headers,
|
27
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE) do |k, v|
|
28
|
+
span.set_attribute(k, v)
|
29
|
+
end
|
30
|
+
content_type = resp_headers['content-type']
|
31
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
32
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(resp.body)
|
33
|
+
span.set_attribute('http.response.body', body_cap) if body_cap
|
34
|
+
end
|
35
|
+
|
36
|
+
trace_response(span, resp)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module OpenTelemetry::Instrumentation::HTTP::Patches::Client
|
2
|
+
def perform(req, options)
|
3
|
+
uri = req.uri
|
4
|
+
request_method = req.verb.to_s.upcase
|
5
|
+
|
6
|
+
headers = req.headers.to_h
|
7
|
+
attrs = Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(headers,
|
8
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
9
|
+
content_type = headers['Content-Type']
|
10
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
11
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(req.body.source)
|
12
|
+
attrs['http.request.body'] = body_cap if body_cap
|
13
|
+
end
|
14
|
+
|
15
|
+
attributes = {
|
16
|
+
'http.method' => request_method,
|
17
|
+
'http.scheme' => uri.scheme,
|
18
|
+
'http.target' => uri.path,
|
19
|
+
'http.url' => uri.to_s,
|
20
|
+
'net.peer.name' => uri.host,
|
21
|
+
'net.peer.port' => uri.port
|
22
|
+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes).merge!(attrs)
|
23
|
+
|
24
|
+
tracer.in_span("HTTP #{request_method}", attributes: attributes, kind: :client) do |span|
|
25
|
+
OpenTelemetry.propagation.inject(req.headers)
|
26
|
+
super.tap do |response|
|
27
|
+
response_headers = response.headers.to_h
|
28
|
+
Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(response_headers,
|
29
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE) do |k, v|
|
30
|
+
span.set_attribute(k, v)
|
31
|
+
end
|
32
|
+
content_type = response_headers['Content-Type']
|
33
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
34
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(response.body.to_s)
|
35
|
+
span.set_attribute('http.response.body', body_cap) if body_cap
|
36
|
+
end
|
37
|
+
annotate_span_with_response!(span, response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module OpenTelemetry::Instrumentation::Net::HTTP::Patches::Instrumentation
|
2
|
+
def request(req, body = nil, &block)
|
3
|
+
# Do not trace recursive call for starting the connection
|
4
|
+
return super(req, body, &block) unless started?
|
5
|
+
|
6
|
+
scheme = use_ssl? ? "https://" : "http"
|
7
|
+
url = "#{scheme}#{@address}:#{@port}#{req.path}"
|
8
|
+
attributes = {
|
9
|
+
OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => req.method,
|
10
|
+
OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => USE_SSL_TO_SCHEME[use_ssl?],
|
11
|
+
OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => req.path,
|
12
|
+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => @address,
|
13
|
+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => @port,
|
14
|
+
OpenTelemetry::SemanticConventions::Trace::HTTP_URL => url
|
15
|
+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
|
16
|
+
|
17
|
+
header_map = req.instance_variable_get(:@header)
|
18
|
+
ht_attributes = Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(header_map,
|
19
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
20
|
+
content_type = header_map['content-type']&.first
|
21
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
22
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(req.body)
|
23
|
+
ht_attributes['http.request.body'] = body_cap if body_cap
|
24
|
+
end
|
25
|
+
tracer.in_span(
|
26
|
+
HTTP_METHODS_TO_SPAN_NAMES[req.method],
|
27
|
+
attributes: attributes.merge!(ht_attributes),
|
28
|
+
kind: :client
|
29
|
+
) do |span|
|
30
|
+
OpenTelemetry.propagation.inject(req)
|
31
|
+
|
32
|
+
super(req, body, &block).tap do |response|
|
33
|
+
response_headers = response.instance_variable_get(:@header)
|
34
|
+
Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(response_headers,
|
35
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE) do |k, v|
|
36
|
+
span.set_attribute(k, v)
|
37
|
+
end
|
38
|
+
content_type = response_headers['content-type']&.first
|
39
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
40
|
+
span.set_attribute('http.response.body', Hypertrace::Instrumentation::DataCapture.capturable_body(response.body))
|
41
|
+
end
|
42
|
+
annotate_span_with_response!(span, response)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware
|
4
|
+
def call(env)
|
5
|
+
if untraced_request?(env)
|
6
|
+
OpenTelemetry::Common::Utilities.untraced do
|
7
|
+
return @app.call(env)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
original_env = env.dup
|
12
|
+
extracted_context = OpenTelemetry.propagation.extract(
|
13
|
+
env,
|
14
|
+
getter: OpenTelemetry::Common::Propagation.rack_env_getter
|
15
|
+
)
|
16
|
+
frontend_context = create_frontend_span(env, extracted_context)
|
17
|
+
|
18
|
+
# restore extracted context in this process:
|
19
|
+
OpenTelemetry::Context.with_current(frontend_context || extracted_context) do
|
20
|
+
request_span_name = create_request_span_name(env['REQUEST_URI'] || original_env['PATH_INFO'], env)
|
21
|
+
request_span_kind = frontend_context.nil? ? :server : :internal
|
22
|
+
|
23
|
+
ht_attributes = Hypertrace::Instrumentation::RackCompatible.extract_req_headers_as_attributes(env, 'http.request.header')
|
24
|
+
content_type = env['CONTENT_TYPE']
|
25
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type,
|
26
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
27
|
+
|
28
|
+
body_cap = Hypertrace::Instrumentation::DataCapture.capturable_body(env['rack.input'])
|
29
|
+
ht_attributes['http.request.body'] = body_cap if body_cap
|
30
|
+
end
|
31
|
+
|
32
|
+
tracer.in_span(request_span_name,
|
33
|
+
attributes: request_span_attributes(env: env).merge(ht_attributes),
|
34
|
+
kind: request_span_kind) do |request_span|
|
35
|
+
OpenTelemetry::Instrumentation::Rack.with_span(request_span) do
|
36
|
+
@app.call(env).tap do |status, headers, response|
|
37
|
+
|
38
|
+
response_headers = headers.to_hash
|
39
|
+
Hypertrace::Instrumentation::DataCapture.headers_to_attribute_keys(response_headers, Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
40
|
+
response_headers.each do |h_k, h_v|
|
41
|
+
request_span.set_attribute("http.response.header.#{h_k.downcase}", h_v)
|
42
|
+
end
|
43
|
+
content_type_arr = response_headers.find{|x|x[0].downcase == "content-type"}
|
44
|
+
if Hypertrace::Instrumentation::DataCapture.can_capture?(content_type_arr&.join(''),
|
45
|
+
Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
46
|
+
cap_body = Hypertrace::Instrumentation::RackCompatible.extract_response_body(response)
|
47
|
+
request_span.set_attribute('http.response.body', cap_body) if cap_body
|
48
|
+
end
|
49
|
+
|
50
|
+
set_attributes_after_request(request_span, status, headers, response)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
finish_span(frontend_context)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hypertrace
|
4
|
+
module Instrumentation
|
5
|
+
class RackCompatible
|
6
|
+
|
7
|
+
CONTENT_TYPE_SUBSTRINGS = %w[json x-www-form-urlencoded]
|
8
|
+
|
9
|
+
def self.extract_req_headers_as_attributes env, attr_prefix
|
10
|
+
return unless Hypertrace::RubyAgent.instance.config.data_capture.http_headers.request
|
11
|
+
|
12
|
+
headers = extract_headers_from_env(env)
|
13
|
+
attr_hash = headers.map do |key, value|
|
14
|
+
# https://github.com/rails/rails/blob/a6bf6d55804850b390d840b87a679b784c0a3db4/actionpack/lib/action_dispatch/http/headers.rb#L5-L23
|
15
|
+
# in rack underscore and dashes resolve to the same header(using - instead of _)
|
16
|
+
key = key.gsub('_', '-')
|
17
|
+
["#{attr_prefix}.#{key}".downcase, value]
|
18
|
+
end.to_h
|
19
|
+
|
20
|
+
attr_hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.should_record_env?(env)
|
24
|
+
return false unless env.has_key?("CONTENT_TYPE")
|
25
|
+
Hypertrace::Instrumentation::DataCapture.can_capture?(env['CONTENT_TYPE'], Hypertrace::Instrumentation::DataCapture::TYPE_REQUEST)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.should_record_rack_array?(response)
|
29
|
+
content_type_arr = response[1].find{|x|x && x[0].downcase == "content-type"}
|
30
|
+
return false unless content_type_arr
|
31
|
+
content_type = content_type_arr[1]
|
32
|
+
Hypertrace::Instrumentation::DataCapture.can_capture?(content_type, Hypertrace::Instrumentation::DataCapture::TYPE_RESPONSE)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.extract_response_body rack_response
|
36
|
+
if rack_response.is_a?(Rack::BodyProxy)
|
37
|
+
Hypertrace::Instrumentation::DataCapture.capturable_body(rack_response.join(''))
|
38
|
+
elsif rack_response.is_a?(Array) && rack_response.length == 3
|
39
|
+
Hypertrace::Instrumentation::DataCapture.capturable_body(rack_response[2].join(''))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.extract_response_headers_as_attributes rack_response, attr_prefix
|
44
|
+
return unless Hypertrace::RubyAgent.instance.config.data_capture.http_headers.response
|
45
|
+
return if rack_response.is_a?(Rack::BodyProxy)
|
46
|
+
|
47
|
+
rack_response[1].map do |header, value|
|
48
|
+
["#{attr_prefix}.#{header}".downcase, value]
|
49
|
+
end.to_h
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.extract_headers_from_env env
|
53
|
+
result = env.select{|x|x.start_with?("HTTP_")}
|
54
|
+
result.transform_keys!{|k|k[5..-1]}
|
55
|
+
if env.has_key?("CONTENT_TYPE")
|
56
|
+
result['content-type'] = env['CONTENT_TYPE']
|
57
|
+
end
|
58
|
+
if env.has_key?("CONTENT_LENGTH")
|
59
|
+
result['content-length'] = env["CONTENT_LENGTH"]
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|