hoss-agent 1.0.9

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.
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