stackify-ruby-apm 1.8.0 → 1.10.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a671e23d695c474d3aafa85df6756d98fa51610411f40e2c750a8e2e5ad2bb1
4
- data.tar.gz: c34ada9683508fbf2345434260c616100a5394fc58c22c4ea5eeceb8e7be78fe
3
+ metadata.gz: 4b2d9a08bd90459363bada0dcc980ab8ee610d5f7bbb41100c53f4d6dc8be6ac
4
+ data.tar.gz: 624b3bd6b64a48270927b77e94663e11416e8f0be84fd86b32b6f5c215251510
5
5
  SHA512:
6
- metadata.gz: 135db2d570f4d24b94ebd39f70909eb1ca21beb52052eaf25f1788d339039803f229ccdfaea00618386f6656378ae934e79f4a85d4c8eee059ff3941794b9c3f
7
- data.tar.gz: b6d344829e9d14e8736bf3e0807fe2c9995d859a733fd1b79920196bc30df3afeac1c2374ad469e1a4bffe9428f43b258243af5138c61981e627ba14f37928f7
6
+ metadata.gz: 8e749581d00b6d425476dfd7683a86b4ccd67491a5713c50be3602668e666519d94ca3abc74f561454d8cb2283113426caee59e226cea4e2dd83ba1bd0fbc856
7
+ data.tar.gz: 9f6784afe5998f4d5c3b5c2501df118d19384b0bd00e9e57c20d58ccc1987021eed0ce114b642624b5ca11339d51c2a91d5527a6a10eab123f57fc8b61fd479b
data/Gemfile CHANGED
@@ -4,27 +4,3 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in stackify-ruby-apm.gemspec
6
6
  gemspec
7
-
8
- group :test do
9
- gem 'activerecord'
10
- gem 'curb', '0.9.8'
11
- gem 'fakeredis'
12
- gem 'google-protobuf', '3.5.0'
13
- gem 'http'
14
- gem 'httpclient'
15
- gem 'mongo'
16
- gem 'mysql2'
17
- gem 'net_http_unix', '~> 0.2'
18
- gem 'pg', '~> 0.20'
19
- gem 'rack-test'
20
- gem 'rails', '~> 4.0'
21
- gem 'rubocop', require: false
22
- gem 'sequel'
23
- gem 'sinatra'
24
- gem 'sinatra-activerecord'
25
- gem 'sqlite3', '1.3.13'
26
- gem 'stackify-api-ruby'
27
- gem 'timecop'
28
- gem 'to_bool'
29
- gem 'webmock'
30
- end
@@ -0,0 +1 @@
1
+ require 'stackify_ruby_apm_lambda'
@@ -118,6 +118,10 @@ module StackifyRubyAPM
118
118
  # Stores transaction in queue
119
119
  #
120
120
  def enqueue_transaction(transaction)
121
+ if !@config.queue
122
+ return @trace_logger.post([transaction])
123
+ end
124
+
121
125
  boot_worker unless worker_running?
122
126
  pending_transactions.push(transaction)
123
127
  return unless should_flush_transactions?
@@ -8,7 +8,12 @@ require 'logger'
8
8
  require 'yaml'
9
9
  require 'socket'
10
10
  module StackifyRubyAPM
11
- TRANSPORT = [TRACE_LOG = 'default'.freeze, UNIX_SOCKET = 'agent_socket'.freeze, AGENT_HTTP = 'agent_http'.freeze].freeze
11
+ TRANSPORT = [
12
+ TRACE_LOG = 'default'.freeze,
13
+ UNIX_SOCKET = 'agent_socket'.freeze,
14
+ AGENT_HTTP = 'agent_http'.freeze,
15
+ LOGGING = 'logging'.freeze
16
+ ].freeze
12
17
 
13
18
  # @api private
14
19
  class Config
@@ -62,7 +67,10 @@ module StackifyRubyAPM
62
67
  stackify_properties_file: '/usr/local/stackify/stackify-ruby-apm/stackify.properties',
63
68
  agent_traces_url: '/traces',
64
69
  unix_socket_path: '/usr/local/stackify/stackify.sock',
65
- transport_http_endpoint: 'https://localhost:10601'
70
+ transport_http_endpoint: 'https://localhost:10601',
71
+
72
+ queue: true,
73
+ lambda_handler: ''
66
74
  }.freeze
67
75
 
68
76
  ENV_TO_KEY = {
@@ -87,7 +95,9 @@ module StackifyRubyAPM
87
95
  'STACKIFY_SPAN_FRAMES_MIN_DURATION' => [:int, 'span_frames_min_duration'],
88
96
  'STACKIFY_MAX_QUEUE_SIZE' => [:int, 'max_queue_size'],
89
97
  'STACKIFY_FLUSH_INTERVAL' => 'flush_interval_seconds',
90
- 'STACKIFY_DISABLED_SPIES' => [:list, 'disabled_spies']
98
+ 'STACKIFY_DISABLED_SPIES' => [:list, 'disabled_spies'],
99
+ 'STACKIFY_QUEUE' => [:bool, 'queue'],
100
+ 'STACKIFY_LAMBDA_HANDLER' => 'lambda_handler'
91
101
  }.freeze
92
102
 
93
103
  def initialize(options = {})
@@ -158,6 +168,9 @@ module StackifyRubyAPM
158
168
  attr_accessor :root_path
159
169
  attr_accessor :http_status
160
170
 
171
+ attr_accessor :queue
172
+ attr_accessor :lambda_handler
173
+
161
174
  attr_reader :client_id
162
175
  attr_reader :device_id
163
176
  attr_reader :apm_disabled_in_rake
@@ -196,6 +209,8 @@ module StackifyRubyAPM
196
209
  curb/multi
197
210
  httparty
198
211
  stackify_logger
212
+ sidekiq
213
+ delayed_job
199
214
  ]
200
215
  end
201
216
 
@@ -221,11 +236,18 @@ module StackifyRubyAPM
221
236
  new_available_spies - disabled_spies
222
237
  end
223
238
 
239
+ # Default Transport
240
+ #
241
+ # initialize default logger transport
224
242
  def debug_logger
225
- debugger_logpath = log_path == '-' ? $stdout : log_path
226
- logger = StackifyLogger.new(debugger_logpath, debugger_filenum_rotate, debugger_byte_size)
227
- logger.level = log_level
228
- self.logger = logger
243
+ case @transport.downcase
244
+ # For unix socket we don't create a trace log file
245
+ when StackifyRubyAPM::TRACE_LOG
246
+ debugger_logpath = log_path == '-' ? $stdout : log_path
247
+ logger = StackifyLogger.new(debugger_logpath, debugger_filenum_rotate, debugger_byte_size)
248
+ logger.level = log_level
249
+ self.logger = logger
250
+ end
229
251
  end
230
252
 
231
253
  private
@@ -12,12 +12,19 @@ module StackifyRubyAPM
12
12
  class Context
13
13
  include NaivelyHashable
14
14
 
15
- attr_accessor :request, :response
15
+ attr_accessor :request, :response, :aws, :category
16
16
  attr_reader :custom, :tags
17
17
 
18
18
  def initialize
19
19
  @custom = {}
20
20
  @tags = {}
21
21
  end
22
+
23
+ # add aws context to context instance
24
+ #
25
+ # values {arn}
26
+ def add_aws_context ctx
27
+ @aws = ctx
28
+ end
22
29
  end
23
30
  end
@@ -18,7 +18,7 @@ module DatabaseHelper
18
18
  elsif driver.include? 'db2'
19
19
  'db2'
20
20
  elsif driver.include? 'sqlite'
21
- 'generic'
21
+ 'sqlite'
22
22
  end
23
23
  end
24
24
  # rubocop:enable Metrics/CyclomaticComplexity
@@ -90,9 +90,8 @@ module StackifyRubyAPM
90
90
  )
91
91
  end
92
92
 
93
- req = #{current_method_without}(*args, &block)
94
93
  StackifyRubyAPM.span name, type, context: ctx do
95
- req
94
+ #{current_method_without}(*args, &block)
96
95
  end
97
96
  else
98
97
  return #{current_method_without}(*args, &block)
@@ -169,9 +168,9 @@ module StackifyRubyAPM
169
168
  CATEGORY: 'Ruby'
170
169
  )
171
170
  end
172
- req = _self_without_apm_#{current_method}(*args, &block)
171
+
173
172
  StackifyRubyAPM.span name, type, context: ctx do
174
- req
173
+ _self_without_apm_#{current_method}(*args, &block)
175
174
  end
176
175
  else
177
176
  return _self_without_apm_#{current_method}(*args, &block)
@@ -14,9 +14,6 @@ module StackifyRubyAPM
14
14
  if FileTest.exist?(@filename)
15
15
  else
16
16
  @dev = create_logfile(@filename)
17
- File.chmod(0o777, @filename)
18
-
19
- return @dev
20
17
  end
21
18
  end
22
19
  write_without_apm(message)
@@ -58,7 +55,6 @@ module StackifyRubyAPM
58
55
  temp_newfilename = "#{temp_filename}-#{filename_counter}.log"
59
56
  @filename = temp_newfilename
60
57
  @dev = create_logfile(@filename)
61
- File.chmod(0o777, @filename)
62
58
 
63
59
  true
64
60
  end
@@ -80,10 +76,16 @@ module StackifyRubyAPM
80
76
  @dev.close rescue nil
81
77
  File.rename(@filename.to_s, age_file)
82
78
  @dev = create_logfile(@filename)
83
- File.chmod(0o777, @filename)
84
79
 
85
80
  true
86
81
  end
82
+
83
+ # Override create_logfile of core LogDevice class where we set File.chmod to 0o777
84
+ def create_logfile(filename)
85
+ logdev = super
86
+ File.chmod(0o777, filename)
87
+ logdev
88
+ end
87
89
  # rubocop:enable Style/RescueModifier
88
90
  # rubocop:enable Lint/RescueWithoutErrorClass
89
91
  end
@@ -15,8 +15,18 @@ module StackifyRubyAPM
15
15
  def build
16
16
  # get process id
17
17
  pid = $PID || Process.pid
18
+ # get hostname and remove extra unwanted characters
19
+ begin
20
+ hostname = (@config.hostname || `hostname`).strip
21
+ rescue StandardError
22
+ # use socket to get hostname if `hostname` throws exception
23
+ require 'socket'
24
+ hostname = Socket.gethostname
25
+ end
26
+
18
27
  hash = {
19
- CATEGORY: 'Ruby',
28
+ PROFILER_VERSION: StackifyRubyAPM::VERSION,
29
+ CATEGORY: @transaction.context.category || 'Ruby',
20
30
  APPLICATION_PATH: '/',
21
31
  APPLICATION_FILESYSTEM_PATH: @config.root_path,
22
32
  APPLICATION_NAME: @config.application_name.strip,
@@ -26,7 +36,7 @@ module StackifyRubyAPM
26
36
  THREAD_ID: Thread.current.object_id,
27
37
  TRACE_SOURCE: 'RUBY',
28
38
  TRACE_TARGET: 'RETRACE',
29
- HOST_NAME: (@config.hostname || `hostname`).strip,
39
+ HOST_NAME: hostname,
30
40
  OS_TYPE: StackifyRubyAPM::Util.host_os,
31
41
  PROCESS_ID: pid,
32
42
  TRACE_VERSION: '2.0',
@@ -36,6 +46,7 @@ module StackifyRubyAPM
36
46
  hash[:STATUS] = @transaction.context.response.status_code if @transaction.context && @transaction.context.response && @transaction.context.response.status_code
37
47
  hash[:URL] = @transaction.context.request.url[:full] if @transaction.context && @transaction.context.request && @transaction.context.request.url[:full]
38
48
  hash[:RUM] = true if @config.rum_enabled.is_a?(TrueClass)
49
+ hash[:AWS_LAMBDA_ARN] = @transaction.context.aws[:arn] if @transaction.context && @transaction.context.aws && @transaction.context.aws[:arn]
39
50
  hash
40
51
  end
41
52
  # rubocop:enable Metrics/CyclomaticComplexity
@@ -3,6 +3,7 @@
3
3
  # Monkey patch for the custom instrumentation any values from config/stackify.json will be loop and will
4
4
  # register as a spy.
5
5
  #
6
+ require 'logger'
6
7
  require 'stackify_apm/instrumenter_helper.rb'
7
8
  require 'stackify_apm/config'
8
9
  require 'json'
@@ -10,7 +11,8 @@ require 'json'
10
11
  # rubocop:disable Metrics/PerceivedComplexity
11
12
  module StackifyRubyAPM
12
13
  # @api private
13
- module Spies
14
+ module Spies extend Log
15
+
14
16
  def self.run_custom_instrumentation
15
17
  config = Config.new
16
18
  to_instrument = parse_json_config(config.json_config_file)
@@ -27,11 +29,21 @@ module StackifyRubyAPM
27
29
  tracked_func = custom_spy['trackedFunction']
28
30
  tracked_func_name = defined?(custom_spy['trackedFunctionName']) ? custom_spy['trackedFunctionName'] : ''
29
31
  transaction = defined?(custom_spy['transaction']) ? custom_spy['transaction'] : nil
32
+ file_path = defined?(custom_spy['file_path']) ? custom_spy['file_path'] : nil
30
33
 
31
34
  tracked_function_tpl = tracked_func_name.nil? ? '{{ClassName}}.{{MethodName}}' : tracked_func_name
32
35
  tracked_function_name = tracked_function_tpl.sub '{{ClassName}}', current_class
33
36
  tracked_function_name = tracked_function_name.sub '{{MethodName}}', current_method
34
37
 
38
+ if !class_exists?(current_class) && !file_path.nil?
39
+ begin
40
+ require file_path
41
+ rescue LoadError => e
42
+ debug "[StackifyRubyAPM] File path doesn't exist."
43
+ debug e.inspect
44
+ end
45
+ end
46
+
35
47
  # rubocop:disable Style/Next
36
48
  if class_exists?(current_class)
37
49
  mod_constant = Module.const_get(current_class.to_s)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the delayed_job class for running async tasks.
4
+ #
5
+
6
+ module StackifyRubyAPM
7
+ # @api private
8
+ module Spies
9
+ # @api private
10
+ class DelayedJobSpy
11
+ def install
12
+ Delayed::Backend::Base.class_eval do
13
+ alias_method 'invoke_job_without_apm', 'invoke_job'
14
+
15
+ def invoke_job(*args, &block)
16
+ ret = nil
17
+ begin
18
+ name = nil
19
+ if payload_object.is_a?(::Delayed::PerformableMethod)
20
+ object = payload_object.object
21
+ klass = object.is_a?(Class) ? object : object.class
22
+ class_name = klass.name
23
+ separator = payload_object.object.is_a?(Class) ? '.' : '#'
24
+ method_name = payload_object.method_name
25
+ name = "#{class_name}#{separator}#{method_name}"
26
+ else
27
+ name = payload_object.class.name
28
+ end
29
+ ctx = StackifyRubyAPM::Context.new
30
+ ctx.category = 'Delayed::Job'
31
+ transaction = StackifyRubyAPM.transaction name, 'TASK', context: ctx
32
+ ret = invoke_job_without_apm(*args, &block)
33
+ rescue StackifyRubyAPM::InternalError
34
+ raise # Don't report StackifyRubyAPM errors
35
+ rescue StandardError => e
36
+ StackifyRubyAPM.report e
37
+ raise e
38
+ ensure
39
+ transaction.submit()
40
+ end
41
+ ret
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ register 'Delayed::Backend::Base', 'delayed/backend/base', DelayedJobSpy.new
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the sidekiq class for running async tasks.
4
+ #
5
+
6
+ module StackifyRubyAPM
7
+ # @api private
8
+ module Spies
9
+ # @api private
10
+ class SidekiqClientSpy
11
+ def install
12
+ Sidekiq::Processor.class_eval do
13
+ alias_method 'process_without_apm', 'process'
14
+
15
+ def process(work, *args, &block)
16
+ ret = nil
17
+ begin
18
+ job = nil
19
+ if defined? work.message
20
+ job = work.message
21
+ else
22
+ job = work.job
23
+ end
24
+ job_hash = JSON.parse job
25
+ name = job_hash["class"]
26
+ transaction = StackifyRubyAPM.transaction name, 'TASK'
27
+ ret = process_without_apm(work, *args, &block)
28
+ rescue StackifyRubyAPM::InternalError
29
+ raise # Don't report StackifyRubyAPM errors
30
+ rescue StandardError => e
31
+ StackifyRubyAPM.report e
32
+ raise e
33
+ ensure
34
+ transaction.submit()
35
+ end
36
+ ret
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ register 'Sidekiq::Processor', 'sidekiq/processor', SidekiqClientSpy.new
43
+ end
44
+ end
@@ -1,3 +1,4 @@
1
+ require 'json'
1
2
  require 'stackify_apm/root_info'
2
3
  require 'stackify_apm/serializers'
3
4
 
@@ -12,6 +13,15 @@ module StackifyRubyAPM
12
13
  @transaction_serializers = Serializers::Transactions.new(config)
13
14
  end
14
15
 
16
+ def get_protobuf_message(transactions)
17
+ protobuf_obj = build_message(transactions)
18
+ StackifyProtoBuf::Traces.encode(protobuf_obj)
19
+ end
20
+
21
+ def get_json_message(transactions)
22
+ JSON.generate(build_json_message(transactions))
23
+ end
24
+
15
25
  # This method will build a group of Stackify::Traces from the protobuf objects.
16
26
  # It accept Array of transactions.
17
27
  def build_message(transactions = [])
@@ -28,6 +38,19 @@ module StackifyRubyAPM
28
38
  debug "[AgentBaseTransport] build_message() exception: #{e.inspect}"
29
39
  end
30
40
 
41
+ def build_json_message(transactions = [])
42
+ traces = []
43
+ transactions.each do |transaction|
44
+ # convert transaction to json
45
+ json_transaction = @transaction_serializers.build_json(@config, transaction)
46
+ # add to traces array
47
+ traces << json_transaction
48
+ end
49
+ return traces
50
+ rescue StandardError => e
51
+ debug "[AgentBaseTransport] build_json_message() exception: #{e.inspect}"
52
+ end
53
+
31
54
  def post(_transactions = [])
32
55
  raise NotImplementedError
33
56
  end
@@ -5,24 +5,32 @@ require 'faraday'
5
5
  require 'ostruct'
6
6
 
7
7
  module StackifyRubyAPM
8
- # This class will handle the sending of protobuf messages through HTTP.
8
+ # This class will handle the sending of transaction messages through HTTP.
9
9
  # @api private
10
10
  class AgentHTTPClient < AgentBaseTransport
11
11
  include Log
12
12
 
13
- HEADERS = {
14
- 'Content-Type' => 'application/x-protobuf'
15
- }.freeze
16
-
17
13
  def initialize(config)
18
14
  @config = config
19
15
  super(config)
20
16
  end
21
17
 
18
+ def get_protobuf_headers
19
+ {
20
+ 'Content-Type' => 'application/x-protobuf'
21
+ }.freeze
22
+ end
23
+
24
+ def get_json_headers
25
+ {
26
+ 'Content-Type' => 'application/json'
27
+ }.freeze
28
+ end
29
+
22
30
  # rubocop:disable Metrics/CyclomaticComplexity
23
31
  # rubocop:disable Metrics/PerceivedComplexity
24
32
  #
25
- # This method will send a protobuf message to HTTP request.
33
+ # This method will send a transction message to HTTP request.
26
34
  # It will accept Array of transactions.
27
35
  def post(transactions = [])
28
36
  debug '[AgentHTTPClient] post()' if ENV['STACKIFY_TRANSPORT_LOG_LEVEL'] == '0'
@@ -31,16 +39,14 @@ module StackifyRubyAPM
31
39
  retry_count = 0
32
40
  delay = @config.delay_seconds
33
41
  begin
34
- protobuf_obj = build_message(transactions)
35
- message = StackifyProtoBuf::Traces.encode(protobuf_obj)
36
- # Convert message into binary and send it to http request
42
+ message = get_json_message(transactions)
37
43
  conn = Faraday.new(ssl: { verify: false })
38
44
  response = conn.post do |req|
39
45
  req.url URI(@config.transport_http_endpoint + @config.agent_traces_url)
40
- req.headers = HEADERS
46
+ req.headers = get_json_headers
41
47
  req.body = message
42
48
  end
43
- if response.try(:status) == 200
49
+ if defined?(response.status) && response.status == 200
44
50
  debug '[AgentHTTPClient] Successfully send message via http request.' if ENV['STACKIFY_TRANSPORT_LOG_LEVEL'] == '0'
45
51
  elsif ENV['STACKIFY_TRANSPORT_LOG_LEVEL'] == '0'
46
52
  debug "[AgentHTTPClient] Failure sending via http request: #{response.inspect}"