hoss-agent 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/Bug_report.md +40 -0
  3. data/.github/ISSUE_TEMPLATE/Feature_request.md +17 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +60 -0
  5. data/.gitignore +27 -0
  6. data/.rspec +2 -0
  7. data/Dockerfile +43 -0
  8. data/Gemfile +105 -0
  9. data/LICENSE +201 -0
  10. data/hoss-agent.gemspec +42 -0
  11. data/lib/hoss-agent.rb +210 -0
  12. data/lib/hoss.rb +21 -0
  13. data/lib/hoss/agent.rb +235 -0
  14. data/lib/hoss/central_config.rb +184 -0
  15. data/lib/hoss/central_config/cache_control.rb +51 -0
  16. data/lib/hoss/child_durations.rb +64 -0
  17. data/lib/hoss/config.rb +315 -0
  18. data/lib/hoss/config/bytes.rb +42 -0
  19. data/lib/hoss/config/duration.rb +40 -0
  20. data/lib/hoss/config/options.rb +154 -0
  21. data/lib/hoss/config/regexp_list.rb +30 -0
  22. data/lib/hoss/config/wildcard_pattern_list.rb +54 -0
  23. data/lib/hoss/context.rb +64 -0
  24. data/lib/hoss/context/request.rb +28 -0
  25. data/lib/hoss/context/request/socket.rb +36 -0
  26. data/lib/hoss/context/request/url.rb +59 -0
  27. data/lib/hoss/context/response.rb +47 -0
  28. data/lib/hoss/context/user.rb +59 -0
  29. data/lib/hoss/context_builder.rb +112 -0
  30. data/lib/hoss/deprecations.rb +39 -0
  31. data/lib/hoss/error.rb +49 -0
  32. data/lib/hoss/error/exception.rb +70 -0
  33. data/lib/hoss/error/log.rb +41 -0
  34. data/lib/hoss/error_builder.rb +90 -0
  35. data/lib/hoss/event.rb +131 -0
  36. data/lib/hoss/instrumenter.rb +107 -0
  37. data/lib/hoss/internal_error.rb +23 -0
  38. data/lib/hoss/logging.rb +70 -0
  39. data/lib/hoss/metadata.rb +36 -0
  40. data/lib/hoss/metadata/process_info.rb +35 -0
  41. data/lib/hoss/metadata/service_info.rb +76 -0
  42. data/lib/hoss/metadata/system_info.rb +47 -0
  43. data/lib/hoss/metadata/system_info/container_info.rb +136 -0
  44. data/lib/hoss/naively_hashable.rb +38 -0
  45. data/lib/hoss/rails.rb +68 -0
  46. data/lib/hoss/railtie.rb +42 -0
  47. data/lib/hoss/report.rb +9 -0
  48. data/lib/hoss/sinatra.rb +53 -0
  49. data/lib/hoss/spies.rb +104 -0
  50. data/lib/hoss/spies/faraday.rb +106 -0
  51. data/lib/hoss/spies/http.rb +86 -0
  52. data/lib/hoss/spies/net_http.rb +101 -0
  53. data/lib/hoss/stacktrace.rb +33 -0
  54. data/lib/hoss/stacktrace/frame.rb +66 -0
  55. data/lib/hoss/stacktrace_builder.rb +124 -0
  56. data/lib/hoss/transport/base.rb +191 -0
  57. data/lib/hoss/transport/connection.rb +55 -0
  58. data/lib/hoss/transport/connection/http.rb +139 -0
  59. data/lib/hoss/transport/connection/proxy_pipe.rb +94 -0
  60. data/lib/hoss/transport/filters.rb +60 -0
  61. data/lib/hoss/transport/filters/hash_sanitizer.rb +77 -0
  62. data/lib/hoss/transport/filters/secrets_filter.rb +48 -0
  63. data/lib/hoss/transport/headers.rb +74 -0
  64. data/lib/hoss/transport/serializers.rb +113 -0
  65. data/lib/hoss/transport/serializers/context_serializer.rb +112 -0
  66. data/lib/hoss/transport/serializers/error_serializer.rb +92 -0
  67. data/lib/hoss/transport/serializers/event_serializer.rb +73 -0
  68. data/lib/hoss/transport/serializers/metadata_serializer.rb +92 -0
  69. data/lib/hoss/transport/serializers/report_serializer.rb +33 -0
  70. data/lib/hoss/transport/user_agent.rb +48 -0
  71. data/lib/hoss/transport/worker.rb +330 -0
  72. data/lib/hoss/util.rb +54 -0
  73. data/lib/hoss/util/inflector.rb +110 -0
  74. data/lib/hoss/util/lru_cache.rb +65 -0
  75. data/lib/hoss/util/throttle.rb +52 -0
  76. data/lib/hoss/version.rb +22 -0
  77. metadata +147 -0
@@ -0,0 +1,42 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ lib = File.expand_path('../lib', __FILE__)
19
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
20
+ require 'hoss/version'
21
+
22
+ Gem::Specification.new do |spec|
23
+ spec.name = 'hoss-agent'
24
+ spec.version = Hoss::VERSION
25
+ spec.authors = ['Cameron Cooper']
26
+ spec.email = ['cameron@hoss.com']
27
+
28
+ spec.summary = 'The official Hoss SDK for Ruby'
29
+ spec.homepage = 'https://hoss.com/'
30
+ spec.metadata = { 'source_code_uri' => 'https://github.com/hossapp/ruby-agent' }
31
+ spec.license = 'Apache-2.0'
32
+ spec.required_ruby_version = ">= 2.3.0"
33
+
34
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
35
+ f.match(%r{^(test|spec|features)/})
36
+ end
37
+
38
+ spec.add_dependency('concurrent-ruby', '~> 1.0')
39
+ spec.add_dependency('http', '>= 3.0')
40
+
41
+ spec.require_paths = ['lib']
42
+ end
@@ -0,0 +1,210 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'erb'
21
+ require 'http'
22
+ require 'json'
23
+ require 'yaml'
24
+ require 'zlib'
25
+ require 'logger'
26
+ require 'concurrent'
27
+ require 'forwardable'
28
+ require 'securerandom'
29
+
30
+ require 'hoss/version'
31
+ require 'hoss/internal_error'
32
+ require 'hoss/logging'
33
+
34
+ # Core
35
+ require 'hoss/agent'
36
+ require 'hoss/config'
37
+ require 'hoss/context'
38
+ require 'hoss/instrumenter'
39
+ require 'hoss/util'
40
+
41
+ require 'hoss/rails' if defined?(::Rails::Railtie)
42
+ require 'hoss/sinatra' if defined?(::Sinatra)
43
+
44
+ # Hoss
45
+ module Hoss
46
+ class << self
47
+ ### Life cycle
48
+
49
+ # Starts the Hoss Agent
50
+ #
51
+ # @param config [Config] An instance of Config
52
+ # @return [Agent] The resulting [Agent]
53
+ def start(config = {})
54
+ Agent.start config
55
+ end
56
+
57
+ # Stops the Hoss Agent
58
+ def stop
59
+ Agent.stop
60
+ end
61
+
62
+ # Restarts the Hoss Agent using the same config or a new
63
+ # one, if it is provided.
64
+ # Starts the agent if it is not running.
65
+ # Stops and starts the agent if it is running.
66
+ def restart(config = nil)
67
+ config ||= agent&.config
68
+ stop if running?
69
+ start(config)
70
+ end
71
+
72
+ # @return [Boolean] Whether there's an [Agent] running
73
+ def running?
74
+ Agent.running?
75
+ end
76
+
77
+ # @return [Agent] Currently running [Agent] if any
78
+ def agent
79
+ Agent.instance
80
+ end
81
+
82
+ # Get a formatted string containing transaction, span, and trace ids.
83
+ # If a block is provided, the ids are yielded.
84
+ #
85
+ # @yield [String|nil, String|nil, String|nil] The transaction, span,
86
+ # and trace ids.
87
+ # @return [String] Unless block given
88
+ def log_ids
89
+ ids = []
90
+ ids.join(' ')
91
+ end
92
+
93
+ def start_event
94
+ agent&.start_event.tap do |event|
95
+ end
96
+ end
97
+
98
+ def end_event
99
+ agent&.end_event
100
+ end
101
+
102
+ def with_event
103
+ unless block_given?
104
+ raise ArgumentError,
105
+ 'expected a block. Do you want `start_event\' instead?'
106
+ end
107
+
108
+ begin
109
+ event = start_event
110
+ yield event
111
+ ensure
112
+ end_event
113
+ end
114
+ end
115
+
116
+ def current_event
117
+ agent&.current_event
118
+ end
119
+
120
+ # rubocop:enable Metrics/ParameterLists
121
+
122
+ # Build a [Context] from a Rack `env`. The context may include information
123
+ # about the request, response, current user and more
124
+ #
125
+ # @param rack_env [Rack::Env] A Rack env
126
+ # @return [Context] The built context
127
+ def build_context(
128
+ rack_env: nil,
129
+ for_type: :transaction
130
+ )
131
+ agent&.build_context(rack_env: rack_env, for_type: for_type)
132
+ end
133
+
134
+ ### Errors
135
+
136
+ # Report and exception to APM
137
+ #
138
+ # @param exception [Exception] The exception
139
+ # @param context [Context] An optional [Context]
140
+ # @param handled [Boolean] Whether the exception was rescued
141
+ # @return [String] ID of the generated [Error]
142
+ def report(exception, context: nil, handled: true)
143
+ agent&.report(exception, context: context, handled: handled)
144
+ end
145
+
146
+ # Report a custom string error message to APM
147
+ #
148
+ # @param message [String] The message
149
+ # @param context [Context] An optional [Context]
150
+ # @return [String] ID of the generated [Error]
151
+ def report_message(message, context: nil, **attrs)
152
+ agent&.report_message(
153
+ message,
154
+ context: context,
155
+ backtrace: caller,
156
+ **attrs
157
+ )
158
+ end
159
+
160
+ ### Context
161
+
162
+ # Set a _label_ value for the current transaction
163
+ #
164
+ # @param key [String,Symbol] A key
165
+ # @param value [Object] A value
166
+ # @return [Object] The given value
167
+ def set_label(key, value)
168
+ case value
169
+ when TrueClass,
170
+ FalseClass,
171
+ Numeric,
172
+ NilClass,
173
+ String
174
+ agent&.set_label(key, value)
175
+ else
176
+ agent&.set_label(key, value.to_s)
177
+ end
178
+ end
179
+
180
+ # Provide further context for the current transaction
181
+ #
182
+ # @param custom [Hash] A hash with custom information. Can be nested.
183
+ # @return [Hash] The current custom context
184
+ def set_custom_context(custom)
185
+ agent&.set_custom_context(custom)
186
+ end
187
+
188
+ # Provide a user to the current transaction
189
+ #
190
+ # @param user [Object] An object representing a user
191
+ # @return [Object] Given user
192
+ def set_user(user)
193
+ agent&.set_user(user)
194
+ end
195
+
196
+ # Provide a filter to transform payloads before sending them off
197
+ #
198
+ # @param key [Symbol] Unique filter key
199
+ # @param callback [Object, Proc] A filter that responds to #call(payload)
200
+ # @yield [Hash] A filter. Used if provided. Otherwise using `callback`
201
+ # @return [Bool] true
202
+ def add_filter(key, callback = nil, &block)
203
+ if callback.nil? && !block_given?
204
+ raise ArgumentError, '#add_filter needs either `callback\' or a block'
205
+ end
206
+
207
+ agent&.add_filter(key, block || callback)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,21 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ # Make bundler auto-require work
21
+ require 'hoss-agent'
@@ -0,0 +1,235 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'hoss/error'
21
+
22
+ require 'hoss/context_builder'
23
+ require 'hoss/error_builder'
24
+ require 'hoss/stacktrace_builder'
25
+
26
+ require 'hoss/central_config'
27
+ require 'hoss/transport/base'
28
+
29
+ require 'hoss/spies'
30
+
31
+ module Hoss
32
+ # @api private
33
+ class Agent
34
+ include Logging
35
+ extend Forwardable
36
+
37
+ LOCK = Mutex.new
38
+
39
+ # life cycle
40
+
41
+ def self.instance # rubocop:disable Style/TrivialAccessors
42
+ @instance
43
+ end
44
+
45
+ def self.start(config)
46
+ return @instance if @instance
47
+
48
+ config = Config.new(config) unless config.is_a?(Config)
49
+
50
+ LOCK.synchronize do
51
+ return @instance if @instance
52
+
53
+ unless config.enabled?
54
+ config.logger.debug format(
55
+ "%sAgent disabled with `enabled: false'",
56
+ Logging::PREFIX
57
+ )
58
+ return
59
+ end
60
+
61
+ @instance = new(config).start
62
+ end
63
+ end
64
+
65
+ def self.stop
66
+ LOCK.synchronize do
67
+ return unless @instance
68
+
69
+ @instance.stop
70
+ @instance = nil
71
+ end
72
+ end
73
+
74
+ def self.running?
75
+ !!@instance
76
+ end
77
+
78
+ def initialize(config)
79
+ @stacktrace_builder = StacktraceBuilder.new(config)
80
+ @context_builder = ContextBuilder.new(config)
81
+ @error_builder = ErrorBuilder.new(self)
82
+
83
+ @central_config = CentralConfig.new(config)
84
+ @transport = Transport::Base.new(config)
85
+ @instrumenter = Instrumenter.new(
86
+ config,
87
+ metrics: nil,
88
+ stacktrace_builder: stacktrace_builder
89
+ ) { |event| enqueue event }
90
+ @pid = Process.pid
91
+ end
92
+
93
+ attr_reader(
94
+ :central_config,
95
+ :config,
96
+ :context_builder,
97
+ :error_builder,
98
+ :instrumenter,
99
+ :stacktrace_builder,
100
+ :transport
101
+ )
102
+
103
+ def_delegator :@central_config, :config
104
+
105
+ def start
106
+ unless config.api_key
107
+ config.logger.warn "Hoss API Key missing, not starting."
108
+ return
109
+ end
110
+ unless config.disable_start_message?
111
+ config.logger.info format(
112
+ '[%s] Starting agent, reporting to %s',
113
+ VERSION, config.api_host
114
+ )
115
+ end
116
+
117
+ central_config.start
118
+ transport.start
119
+ instrumenter.start
120
+ # metrics.start
121
+
122
+ config.enabled_instrumentations.each do |lib|
123
+ debug "Requiring spy: #{lib}"
124
+ require "hoss/spies/#{lib}"
125
+ end
126
+
127
+ self
128
+ end
129
+
130
+ def stop
131
+ debug 'Stopping agent'
132
+
133
+ central_config.stop
134
+ instrumenter.stop
135
+ transport.stop
136
+
137
+ self
138
+ end
139
+
140
+ at_exit do
141
+ stop
142
+ end
143
+
144
+ # transport
145
+
146
+ def enqueue(obj)
147
+ transport.submit obj
148
+ end
149
+
150
+ def current_event
151
+ instrumenter.current_event
152
+ end
153
+
154
+ def start_event
155
+ detect_forking!
156
+ instrumenter.start_event
157
+ end
158
+
159
+ def end_event
160
+ instrumenter.end_event
161
+ end
162
+
163
+ def set_label(key, value)
164
+ instrumenter.set_label(key, value)
165
+ end
166
+
167
+ def set_custom_context(context)
168
+ instrumenter.set_custom_context(context)
169
+ end
170
+
171
+ def set_user(user)
172
+ instrumenter.set_user(user)
173
+ end
174
+
175
+ def build_context(rack_env:, for_type:)
176
+ @context_builder.build(rack_env: rack_env, for_type: for_type)
177
+ end
178
+
179
+ # errors
180
+
181
+ def report(exception, context: nil, handled: true)
182
+ return unless config.recording?
183
+ detect_forking!
184
+ return if config.filter_exception_types.include?(exception.class.to_s)
185
+
186
+ error = @error_builder.build_exception(
187
+ exception,
188
+ context: context,
189
+ handled: handled
190
+ )
191
+ enqueue error
192
+ error.id
193
+ end
194
+
195
+ def report_message(message, context: nil, backtrace: nil, **attrs)
196
+ return unless config.recording?
197
+ detect_forking!
198
+
199
+ error = @error_builder.build_log(
200
+ message,
201
+ context: context,
202
+ backtrace: backtrace,
203
+ **attrs
204
+ )
205
+ enqueue error
206
+ error.id
207
+ end
208
+
209
+ # filters
210
+
211
+ def add_filter(key, callback)
212
+ transport.add_filter(key, callback)
213
+ end
214
+
215
+ # misc
216
+
217
+ def inspect
218
+ super.split.first + '>'
219
+ end
220
+
221
+ def detect_forking!
222
+ return if @pid == Process.pid
223
+
224
+ config.logger.debug "Detected forking,
225
+ restarting threads in process [PID:#{Process.pid}]"
226
+
227
+ central_config.handle_forking!
228
+ transport.handle_forking!
229
+ instrumenter.handle_forking!
230
+ metrics.handle_forking!
231
+
232
+ @pid = Process.pid
233
+ end
234
+ end
235
+ end