application_insights 0.3.0 → 0.5.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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWFiNjNiNjljMTc2ODI1NzQ4M2VhNTlmODU2MTcxZTFlZjliMTA2Yw==
4
+ ODhmYmI4Y2ExYTdmNTAyOGY3MDAyNGM3NDI0N2ViMmM0NmMzYTkzNg==
5
5
  data.tar.gz: !binary |-
6
- NGM3NzEwNDZkYzI1MWEwNjRiNTMwNWMyOGJlYTkwODA5MjM2MmI2Yw==
6
+ NjY2MmVkYzcwYzFmZjYzYzMyZjYzZmIwMTBjOTliZjBlNzc2NGUyZA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MTU5OTgzY2QzMWMwOTJmYTM3YTZiZWNlYTg4NmQ0NWM4ODkzYjk2ZTI4MTQ4
10
- NzJmMDJiNTQxNTA0YzkwNjBmYTEwYTc3ZjBhNmU5YjQxZTRmODA2ZTU4Zjgz
11
- ZWJhMzAzZWI2NmYyNzRjMzg0ZDNmZmQwNjZkNmY3MGU2NjYzN2U=
9
+ YTc3NGQyNDM1N2I4MjNkYmUxOTRlMTNhNDdkNWU0MGY1ZjZlMDNhZDQ5NmFi
10
+ MGQ4MmViMGE0YzU1MTRmMjI5M2Q2NmE0YTcyNmNmMjRjNTQzNWY2ZWFkNGE4
11
+ MTlhNGMwMDBkYTc1NjczN2I0YWViOTQyNWRjZjRiM2QwM2I1ZDc=
12
12
  data.tar.gz: !binary |-
13
- NjA1MDU4ZWY2Mjc0MDZiZTYxM2Y0ZTk3MzQ2MjI2NzdjZDU1Y2FkZTY0MGY5
14
- ZDBkNDQ1ZGQ4Zjk5M2MwNThmM2VjNjg4ZWNlNjE3ZjMxMjdkMWI3NDRiYmMy
15
- YTc5ZDcxYmUwZWM1MzNjMDVhYTU1ZmEwYTFjNDUxOTUxMDYyY2M=
13
+ YTYyMGU4MzkxYjUwZTljM2Y3ZGYxZGI1MTUxZGI0ZDc3OGI4YTMyNDQyNTBl
14
+ NTIxMDM4YTc0ZWM0ODU5ZmE3MWQ0ZDI4ODEzZDAzMmVlYTlhOGFmMGNmYmQy
15
+ YzRkNjg0OTM0MGYyYzYzNGM1MDMyYjMwNzE5ODZmYjgyMmM4M2I=
data/README.md CHANGED
@@ -30,8 +30,7 @@ Once installed, you can send telemetry to Application Insights. Here are a few s
30
30
  ###Sending a simple event telemetry item###
31
31
  ```ruby
32
32
  require 'application_insights'
33
- tc = ApplicationInsights::TelemetryClient.new
34
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
33
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
35
34
  tc.track_event 'My event'
36
35
  tc.flush
37
36
  ```
@@ -39,8 +38,7 @@ tc.flush
39
38
  ###Sending an event telemetry item with custom properties and measurements###
40
39
  ```ruby
41
40
  require 'application_insights'
42
- tc = ApplicationInsights::TelemetryClient.new
43
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
41
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
44
42
  tc.track_event 'My event', :properties => { 'custom property' => 'some value' }, :measurements => { 'custom metric' => 13 }
45
43
  tc.flush
46
44
  ```
@@ -48,8 +46,7 @@ tc.flush
48
46
  ###Sending a trace telemetry item with custom properties###
49
47
  ```ruby
50
48
  require 'application_insights'
51
- tc = ApplicationInsights::TelemetryClient.new
52
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
49
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
53
50
  tc.track_trace 'My trace statement', :properties => { 'custom property' => 'some value' }
54
51
  tc.flush
55
52
  ```
@@ -57,8 +54,7 @@ tc.flush
57
54
  ###Sending a metric telemetry item (without and with optional values)###
58
55
  ```ruby
59
56
  require 'application_insights'
60
- tc = ApplicationInsights::TelemetryClient.new
61
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
57
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
62
58
  tc.track_metric 'My metric', 42
63
59
  # with all optional values set
64
60
  tc.track_metric 'My metric', 42, :kind => ApplicationInsights::Channel::Contracts::DataPointType::AGGREGATION, :count => 3, :min => 1, :max => 100, :std_dev => 10, :properties => { 'custom property' => 'some value' }
@@ -68,8 +64,7 @@ tc.flush
68
64
  ###Sending an exception telemetry item with custom properties and measurements###
69
65
  ```ruby
70
66
  require 'application_insights'
71
- tc = ApplicationInsights::TelemetryClient.new
72
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
67
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
73
68
  begin
74
69
  raise ArgumentError, 'Something has gone wrong!'
75
70
  rescue => e
@@ -81,8 +76,7 @@ tc.flush
81
76
  ###Configuring context for a telemetry client instance###
82
77
  ```ruby
83
78
  require 'application_insights'
84
- tc = ApplicationInsights::TelemetryClient.new
85
- tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
79
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>'
86
80
  tc.context.application.id = 'My application'
87
81
  tc.context.application.ver = '1.2.3'
88
82
  tc.context.device.id = 'My current device'
@@ -110,7 +104,7 @@ require 'application_insights'
110
104
  sender = ApplicationInsights::Channel::AsynchronousSender.new
111
105
  queue = ApplicationInsights::Channel::AsynchronousQueue.new sender
112
106
  channel = ApplicationInsights::Channel::TelemetryChannel.new nil, queue
113
- tc = ApplicationInsights::TelemetryClient.new channel
107
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>', channel
114
108
  # Note: the event will be sent on a separate thread; if the app finishes before
115
109
  # the thread finishes, the data is lost
116
110
  tc.track_event 'My event'
@@ -122,7 +116,7 @@ require 'application_insights'
122
116
  sender = ApplicationInsights::Channel::AsynchronousSender.new
123
117
  queue = ApplicationInsights::Channel::AsynchronousQueue.new sender
124
118
  channel = ApplicationInsights::Channel::TelemetryChannel.new nil, queue
125
- tc = ApplicationInsights::TelemetryClient.new channel
119
+ tc = ApplicationInsights::TelemetryClient.new '<YOUR INSTRUMENTATION KEY GOES HERE>', channel
126
120
  # flush telemetry if we have 10 or more telemetry items in our queue
127
121
  tc.channel.queue.max_queue_length = 10
128
122
  # send telemetry to the service in batches of 5
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
25
25
  spec.add_development_dependency 'yard', '~> 0.8.7.6'
26
26
  spec.add_development_dependency 'redcarpet', '~> 3.2.2'
27
+ spec.add_development_dependency 'rack', '>= 1.0.0'
27
28
  end
@@ -1,3 +1,9 @@
1
1
  require_relative 'application_insights/telemetry_client'
2
2
  require_relative 'application_insights/unhandled_exception'
3
- require_relative 'application_insights/version'
3
+ require_relative 'application_insights/version'
4
+
5
+ module ApplicationInsights
6
+ module Rack
7
+ autoload :TrackRequest, "application_insights/rack/track_request"
8
+ end
9
+ end
@@ -20,8 +20,9 @@ module ApplicationInsights
20
20
  @send_interval = 1.0
21
21
  @send_remaining_time = 0
22
22
  @send_time = 3.0
23
- @lock_send_remaining_time = Mutex.new
23
+ @lock_work_thread = Mutex.new
24
24
  @work_thread = nil
25
+ @start_notification_processed = true
25
26
  super service_endpoint_uri
26
27
  end
27
28
 
@@ -41,40 +42,37 @@ module ApplicationInsights
41
42
  # a total duration of {#send_time} seconds for new items. If a worker thread has already been created, calling
42
43
  # this method does nothing.
43
44
  def start
44
- @lock_send_remaining_time.synchronize do
45
- # only maintain one working thread at one time
46
- if !@work_thread
47
- local_send_interval = (@send_interval < 0.1) ? 0.1 : @send_interval
48
- @send_remaining_time = (@send_time < local_send_interval) ? local_send_interval : @send_time
49
- @work_thread = Thread.new do
50
- run
45
+ @start_notification_processed = false
46
+ # Maintain one working thread at one time
47
+ if !@work_thread
48
+ @lock_work_thread.synchronize do
49
+ if !@work_thread
50
+ local_send_interval = (@send_interval < 0.1) ? 0.1 : @send_interval
51
+ @send_remaining_time = (@send_time < local_send_interval) ? local_send_interval : @send_time
52
+ @work_thread = Thread.new do
53
+ run
54
+ end
55
+ @work_thread.abort_on_exception = false
51
56
  end
52
- @work_thread.abort_on_exception = false
53
57
  end
54
58
  end
55
59
  end
56
60
 
57
- # Calling this method will shut down the worker thread that checks the {#queue} for new items. The thread will be
58
- # allowed to shut down gracefully.
59
- def stop
60
- @lock_send_remaining_time.synchronize do
61
- @send_remaining_time = 0
62
- end
63
- end
64
-
65
61
  private
66
62
 
67
63
  def run
68
64
  # save the queue locally
69
65
  local_queue = @queue
70
66
  if local_queue == nil
71
- stop
67
+ @work_thread = nil
72
68
  return
73
69
  end
74
70
 
71
+ begin
75
72
  # fix up the send interval (can't be lower than 100ms)
76
73
  local_send_interval = (@send_interval < 0.1) ? 0.1 : @send_interval
77
74
  while TRUE
75
+ @start_notification_processed = true
78
76
  while TRUE
79
77
  # get at most @send_buffer_size items from the queue
80
78
  counter = @send_buffer_size
@@ -90,9 +88,7 @@ module ApplicationInsights
90
88
  break if data.length == 0
91
89
 
92
90
  # reset the send time
93
- @lock_send_remaining_time.synchronize do
94
- @send_remaining_time = @send_time
95
- end
91
+ @send_remaining_time = @send_time
96
92
 
97
93
  # finally send the data
98
94
  send data
@@ -106,15 +102,21 @@ module ApplicationInsights
106
102
  end
107
103
 
108
104
  # decrement the remaining time
109
- @lock_send_remaining_time.synchronize do
110
- @send_remaining_time -= local_send_interval
111
- # Check queue as well to avoid missing any 'start' notification occurred during waiting
112
- if @send_remaining_time <= 0 && local_queue.empty?
113
- @work_thread = nil
114
- break
115
- end
105
+ @send_remaining_time -= local_send_interval
106
+ # If remaining time <=0 and there is no start notification unprocessed, then stop the working thread
107
+ if @send_remaining_time <= 0 && @start_notification_processed
108
+ # Note: there is still a chance some start notification could be missed, e.g., the start method
109
+ # got triggered between the above and following line. However the data is not lost as it would be processed
110
+ # later when next start notification comes after the worker thread stops. The cost to ensure no
111
+ # notification miss is high where a lock is required each time the start method calls.
112
+ @work_thread = nil
113
+ break
116
114
  end
117
115
  end
116
+ rescue
117
+ # Make sure work_thread sets to nil when it terminates abnormally
118
+ @work_thread = nil
119
+ end
118
120
  end
119
121
  end
120
122
  end
@@ -53,7 +53,7 @@ module ApplicationInsights
53
53
  end
54
54
 
55
55
  # Indicates whether the queue is empty.
56
- # @return [true, false] true if the queue is empty
56
+ # @return [Boolean] true if the queue is empty
57
57
  def empty?
58
58
  @queue.empty?
59
59
  end
@@ -43,7 +43,9 @@ module ApplicationInsights
43
43
  'Content-Encoding' => 'gzip'
44
44
  }
45
45
  request = Net::HTTP::Post.new(uri.path, headers)
46
- compressed_data = compress(data_to_send.to_json)
46
+ # Use JSON.generate instead of to_json, otherwise it will default to ActiveSupport::JSON.encode for Rails app
47
+ json = JSON.generate(data_to_send)
48
+ compressed_data = compress(json)
47
49
  request.body = compressed_data
48
50
 
49
51
  http = Net::HTTP.new uri.hostname, uri.port
@@ -1,3 +1,4 @@
1
+ require 'time'
1
2
  require_relative 'asynchronous_queue'
2
3
  require_relative 'asynchronous_sender'
3
4
  require_relative 'telemetry_context'
@@ -62,7 +63,7 @@ module ApplicationInsights
62
63
  }
63
64
  envelope_attributes = {
64
65
  :name => 'Microsoft.ApplicationInsights.' + data_type[0..-5],
65
- :time => Time.now.getutc.strftime('%FT%T.%6NZ'),
66
+ :time => Time.now.iso8601(7),
66
67
  :i_key => local_context.instrumentation_key,
67
68
  :tags => get_tags(local_context),
68
69
  :data => Contracts::Data.new(data_attributes)
@@ -62,7 +62,7 @@ module ApplicationInsights
62
62
 
63
63
  # The property context. This contains free-form properties that you can add to your telemetry.
64
64
  # @return [Hash<String, String>] the context object.
65
- attr_reader :properties
65
+ attr_accessor :properties
66
66
  end
67
67
  end
68
68
  end
@@ -0,0 +1,84 @@
1
+ require 'rack'
2
+ require_relative '../channel/contracts/request_data'
3
+ require_relative '../telemetry_client'
4
+
5
+ module ApplicationInsights
6
+ module Rack
7
+ # Track every request and sends the request data to Application Insights.
8
+ class TrackRequest
9
+ # Initializes a new instance of the class.
10
+ # @param [Object] app the inner rack application.
11
+ # @param [String] instrumentation_key to identify which Application Insights application this data is for.
12
+ # @param [Fixnum] buffer_size the buffer size and the buffered requests would send to Application Insights
13
+ # when buffer is full.
14
+ # @param [Fixnum] send_interval the frequency (in seconds) to check buffer and send buffered requests to Application
15
+ # Insights if any.
16
+ def initialize(app, instrumentation_key, buffer_size=500, send_interval=60)
17
+ @app = app
18
+ @instrumentation_key = instrumentation_key
19
+ @buffer_size = buffer_size
20
+ @send_interval = send_interval
21
+ end
22
+
23
+ # Track requests and send data to Application Insights asynchronously.
24
+ # @param [Hash] env the rack environment.
25
+ def call(env)
26
+ start = Time.now
27
+ begin
28
+ status, headers, response = @app.call(env)
29
+ rescue Exception => ex
30
+ status = 500
31
+ exception = ex
32
+ end
33
+ stop = Time.now
34
+
35
+ if !@client
36
+ sender = @sender || Channel::AsynchronousSender.new
37
+ sender.send_interval = @send_interval
38
+ queue = Channel::AsynchronousQueue.new sender
39
+ queue.max_queue_length = @buffer_size
40
+ channel = Channel::TelemetryChannel.new nil, queue
41
+ @client = TelemetryClient.new @instrumentation_key, channel
42
+ end
43
+
44
+ request = ::Rack::Request.new env
45
+ id = rand(16**32).to_s(16)
46
+ start_time = start.iso8601(7)
47
+ duration = format_request_duration(stop - start)
48
+ success = status.to_i < 400
49
+ options = {
50
+ :name => "#{request.request_method} #{request.path}",
51
+ :http_method => request.request_method,
52
+ :url => request.url
53
+ }
54
+ @client.track_request id, start_time, duration, status, success, options
55
+
56
+ if exception != nil
57
+ @client.track_exception exception, handled_at: 'Unhandled'
58
+ raise exception
59
+ end
60
+
61
+ [status, headers, response]
62
+ end
63
+
64
+ private
65
+
66
+ def sender=(sender)
67
+ @sender = sender if sender.is_a? Channel::AsynchronousSender
68
+ end
69
+
70
+ def client
71
+ @client
72
+ end
73
+
74
+ def format_request_duration(duration_seconds)
75
+ if duration_seconds >= 86400
76
+ # just return 1 day when it takes more than 1 day which should not happen for requests.
77
+ return "%d:%02d:%02d:%02d.%07d" % [1, 0, 0, 0, 0]
78
+ end
79
+
80
+ Time.at(duration_seconds).gmtime.strftime("0:%H:%M:%S.%7N")
81
+ end
82
+ end
83
+ end
84
+ end
@@ -9,16 +9,20 @@ require_relative 'channel/contracts/data_point_type'
9
9
  require_relative 'channel/contracts/metric_data'
10
10
  require_relative 'channel/contracts/message_data'
11
11
  require_relative 'channel/contracts/stack_frame'
12
+ require_relative 'channel/contracts/request_data'
13
+ require_relative 'channel/contracts/severity_level'
12
14
 
13
15
  module ApplicationInsights
14
16
  # The telemetry client used for sending all types of telemetry. It serves as the main entry point for
15
17
  # interacting with the Application Insights service.
16
18
  class TelemetryClient
17
19
  # Initializes a new instance of the class.
20
+ # @param [String] instrumentation_key to identify which Application Insights application this data is for.
18
21
  # @param [Channel::TelemetryChannel] telemetry_channel the optional telemetry channel to be used instead of
19
22
  # constructing a default one.
20
- def initialize(telemetry_channel = nil)
23
+ def initialize(instrumentation_key = nil, telemetry_channel = nil)
21
24
  @context = Channel::TelemetryContext.new
25
+ @context.instrumentation_key = instrumentation_key
22
26
  @channel = telemetry_channel || Channel::TelemetryChannel.new
23
27
  end
24
28
 
@@ -83,7 +87,7 @@ module ApplicationInsights
83
87
  details_attributes = {
84
88
  :id => 1,
85
89
  :outer_id => 0,
86
- :type_name => exception.class,
90
+ :type_name => exception.class.name,
87
91
  :message => exception.message,
88
92
  :has_full_stack => exception.backtrace != nil,
89
93
  :stack => (exception.backtrace.join("\n") if exception.backtrace),
@@ -159,18 +163,51 @@ module ApplicationInsights
159
163
 
160
164
  # Sends a single trace statement.
161
165
  # @param [String] name the trace statement.
166
+ # @param [Channel::Contracts::SeverityLevel] severity_level the severity level.
162
167
  # @param [Hash] options the options to create the {Channel::Contracts::EventData} object.
163
168
  # @option options [Hash] :properties the set of custom properties the client wants attached to this
164
169
  # data item. (defaults to: {})
165
- def track_trace(name, options={})
170
+ def track_trace(name, severity_level = Channel::Contracts::SeverityLevel::INFORMATION, options={})
166
171
  data_attributes = {
167
- :message => name || 'Null',
168
- :properties => options.fetch(:properties) { {} }
172
+ :message => name || 'Null',
173
+ :severity_level => severity_level || Channel::Contracts::SeverityLevel::INFORMATION,
174
+ :properties => options.fetch(:properties) { {} }
169
175
  }
170
176
  data = Channel::Contracts::MessageData.new data_attributes
171
177
  self.channel.write(data, self.context)
172
178
  end
173
179
 
180
+ # Sends a single request.
181
+ # @param [String] id the unique identifier of the request.
182
+ # @param (String) start_time the start time of the request.
183
+ # @param [String] duration the duration to process the request.
184
+ # @param [String] response_code the response code of the request.
185
+ # @param [Boolean] success indicates whether the request succeeds or not.
186
+ # @param [Hash] options the options to create the {Channel::Contracts::RequestData} object.
187
+ # @option options [String] :name the name of the request.
188
+ # @option options [String] :http_method the http method used for the request.
189
+ # @option options [String] :url the url of the request.
190
+ # @option options [Hash] :properties the set of custom properties the client wants attached to this
191
+ # data item. (defaults to: {})
192
+ # @option options [Hash] :measurements the set of custom measurements the client wants to attach to
193
+ # this data item (defaults to: {})
194
+ def track_request(id, start_time, duration, response_code, success, options={})
195
+ data_attributes = {
196
+ :id => id || 'Null',
197
+ :start_time => start_time || Time.now.iso8601(7),
198
+ :duration => duration || '0:00:00:00.0000000',
199
+ :response_code => response_code || 200,
200
+ :success => success = nil ? true : success,
201
+ :name => options[:name],
202
+ :http_method => options[:http_method],
203
+ :url => options[:url],
204
+ :properties => options.fetch(:properties) { {} },
205
+ :measurements => options.fetch(:measurements) { {} }
206
+ }
207
+ data = Channel::Contracts::RequestData.new data_attributes
208
+ self.channel.write(data, self.context)
209
+ end
210
+
174
211
  # Flushes data in the queue. Data in the queue will be sent either immediately irrespective of what sender is
175
212
  # being used.
176
213
  def flush
@@ -37,9 +37,8 @@ module ApplicationInsights
37
37
 
38
38
  queue = Channel::SynchronousQueue.new @sender
39
39
  channel = Channel::TelemetryChannel.new nil, queue
40
- client = TelemetryClient.new channel
41
- client.context.instrumentation_key = instrumentation_key
42
- client.track_exception($!, {:handled_at => 'Unhandled'})
40
+ client = TelemetryClient.new instrumentation_key, channel
41
+ client.track_exception($!, handled_at: 'Unhandled')
43
42
  client.flush
44
43
  end
45
44
  end
@@ -1,3 +1,3 @@
1
1
  module ApplicationInsights
2
- VERSION = '0.3.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require_relative '../../../lib/application_insights/channel/sender_base'
2
2
  require_relative '../../../lib/application_insights/channel/asynchronous_queue'
3
+ require_relative '../mock_sender'
3
4
  require 'test/unit'
4
5
 
5
6
  include ApplicationInsights::Channel
@@ -43,26 +44,4 @@ class TestAsynchronousQueue < Test::Unit::TestCase
43
44
  result = queue.flush_notification.wait
44
45
  assert_equal true, result
45
46
  end
46
- end
47
-
48
- class MockAsynchronousSender
49
- def initialize
50
- @send_buffer_size = 2
51
- @data = []
52
- @queue = nil
53
- @start_call_count = 0
54
- end
55
-
56
- attr_accessor :start_call_count
57
- attr_accessor :send_buffer_size
58
- attr_accessor :data
59
- attr_accessor :queue
60
-
61
- def start
62
- @start_call_count += 1
63
- end
64
-
65
- def send(data_to_send)
66
- @data.push data_to_send
67
- end
68
47
  end
@@ -36,7 +36,7 @@ class TestAsynchronousSender < Test::Unit::TestCase
36
36
  sender.invoke_base_start = true
37
37
  queue.push 1
38
38
  assert_not_nil sender.work_thread
39
- sleep 3.0
39
+ sleep 5.0
40
40
  assert_nil sender.work_thread
41
41
  end
42
42
 
@@ -1,5 +1,6 @@
1
1
  require_relative '../../../lib/application_insights/channel/sender_base'
2
2
  require_relative '../../../lib/application_insights/channel/synchronous_queue'
3
+ require_relative '../mock_sender'
3
4
  require 'test/unit'
4
5
 
5
6
  include ApplicationInsights::Channel
@@ -11,32 +12,17 @@ class TestSynchronousQueue < Test::Unit::TestCase
11
12
 
12
13
  def test_flush_works_as_expected
13
14
  sender = MockSynchronousSender.new
15
+ sender.send_buffer_size = 2
14
16
  queue = SynchronousQueue.new sender
15
17
  queue.max_queue_length = 3
16
18
  (1..7).to_a.each do |i|
17
19
  queue.push i
18
20
  end
19
- assert_equal [[1, 2], [3], [4, 5], [6]], sender.data
21
+ assert_equal [[1, 2], [3], [4, 5], [6]], sender.buffer
20
22
  temp = []
21
23
  queue.instance_variable_get('@queue').length.times do |i|
22
24
  temp.push queue.instance_variable_get('@queue').pop
23
25
  end
24
26
  assert_equal [7], temp
25
27
  end
26
- end
27
-
28
- class MockSynchronousSender
29
- def initialize
30
- @send_buffer_size = 2
31
- @data = []
32
- @queue = nil
33
- end
34
-
35
- attr_accessor :send_buffer_size
36
- attr_accessor :data
37
- attr_accessor :queue
38
-
39
- def send(data_to_send)
40
- @data.push data_to_send
41
- end
42
28
  end
@@ -68,5 +68,8 @@ class TestTelemetryContext < Test::Unit::TestCase
68
68
  context = TelemetryContext.new
69
69
  assert_not_nil context.properties
70
70
  assert_equal 0, context.properties.count
71
+ expected = {:a => 'a', :b => 'b'}
72
+ context.properties = expected
73
+ assert_same expected, context.properties
71
74
  end
72
75
  end
@@ -0,0 +1,37 @@
1
+ require_relative '../../lib/application_insights/channel/synchronous_sender'
2
+ require_relative '../../lib/application_insights/channel/asynchronous_sender'
3
+
4
+ include ApplicationInsights::Channel
5
+
6
+ class MockSynchronousSender < SynchronousSender
7
+ def initialize
8
+ @buffer = []
9
+ super
10
+ end
11
+
12
+ attr_accessor :buffer
13
+
14
+ def send(data)
15
+ @buffer << data
16
+ end
17
+ end
18
+
19
+ class MockAsynchronousSender < AsynchronousSender
20
+ def initialize
21
+ @buffer = []
22
+ @start_call_count = 0
23
+ super
24
+ end
25
+
26
+ attr_accessor :start_call_count
27
+ attr_accessor :buffer
28
+
29
+ def start
30
+ @start_call_count += 1
31
+ super
32
+ end
33
+
34
+ def send(data)
35
+ @buffer << data
36
+ end
37
+ end
@@ -0,0 +1,139 @@
1
+ require 'test/unit'
2
+ require 'rack/mock'
3
+ require_relative '../mock_sender'
4
+ require_relative '../../../lib/application_insights/rack/track_request'
5
+
6
+ include ApplicationInsights::Rack
7
+
8
+ class TestTrackRequest < Test::Unit::TestCase
9
+ def test_call_works_as_expected
10
+ response_code = rand(200..204)
11
+ app = Proc.new {|env| sleep(2); [response_code, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
12
+ url = "http://localhost:8080/foo?a=b"
13
+ http_method = 'PUT'
14
+ env = Rack::MockRequest.env_for(url, :method => http_method)
15
+ instrumentation_key = 'key'
16
+ sender = MockAsynchronousSender.new
17
+ track_request = TrackRequest.new app, instrumentation_key, 500, 1
18
+ track_request.send(:sender=, sender)
19
+ start_time = Time.now
20
+ result = track_request.call(env)
21
+ assert_equal app.call(env), result
22
+ sleep(sender.send_interval)
23
+
24
+ assert_equal 1, sender.buffer.count
25
+ payload = sender.buffer[0]
26
+ assert_equal instrumentation_key, payload[0].i_key
27
+
28
+ request_data = payload[0].data.base_data
29
+ assert_equal "#{http_method} /foo", request_data.name
30
+ assert_equal response_code, request_data.response_code
31
+ assert_equal true, request_data.success
32
+ assert_equal http_method, request_data.http_method
33
+ assert_equal url, request_data.url
34
+ assert_equal true, request_data.duration.start_with?("0:00:00:02")
35
+ assert_in_epsilon Time.parse(request_data.start_time) - start_time, 0, 0.01
36
+ end
37
+
38
+ def test_call_with_failed_request
39
+ response_code = rand(400..600)
40
+ app = Proc.new {|env| [response_code, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
41
+ url = "http://localhost:8080/foo"
42
+ http_method = 'PUT'
43
+ env = Rack::MockRequest.env_for(url, :method => http_method)
44
+ instrumentation_key = 'key'
45
+ sender = MockAsynchronousSender.new
46
+ track_request = TrackRequest.new app, instrumentation_key, 500, 1
47
+ track_request.send(:sender=, sender)
48
+ track_request.call(env)
49
+ sleep(sender.send_interval)
50
+
51
+ payload = sender.buffer[0]
52
+ request_data = payload[0].data.base_data
53
+ assert_equal false, request_data.success
54
+ end
55
+
56
+ def test_call_with_unhandled_exception
57
+ app = Proc.new {|env| raise StandardError, 'Boom!'}
58
+ env = Rack::MockRequest.env_for( "http://localhost:8080/foo", :method => "GET")
59
+ instrumentation_key = 'key'
60
+ sender = MockAsynchronousSender.new
61
+ track_request = TrackRequest.new app, instrumentation_key, 500, 1
62
+ track_request.send(:sender=, sender)
63
+
64
+ begin
65
+ track_request.call(env)
66
+ rescue => ex
67
+ assert_equal 'Boom!', ex.message
68
+ end
69
+
70
+ sleep(sender.send_interval)
71
+ payload = sender.buffer[0]
72
+ assert_equal 2, payload.count
73
+ request_data = payload[0].data.base_data
74
+ assert_equal false, request_data.success
75
+ assert_equal 500, request_data.response_code
76
+
77
+ exception_data = payload[1].data.base_data
78
+ assert_equal instrumentation_key, payload[1].i_key
79
+ assert_equal 'Unhandled', exception_data.handled_at
80
+ end
81
+
82
+ def test_internal_client
83
+ app = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
84
+ env = Rack::MockRequest.env_for('http://localhost:8080/foo', :method => "GET")
85
+ buffer_size = 30
86
+ send_interval = 5
87
+ track_request = TrackRequest.new app, 'key', buffer_size, send_interval
88
+ client = track_request.send(:client)
89
+ # test lazy initialization
90
+ assert_nil client
91
+
92
+ track_request.call(env)
93
+ client = track_request.send(:client)
94
+ channel = client.channel
95
+ assert_equal buffer_size, channel.queue.max_queue_length
96
+ assert_equal send_interval, channel.sender.send_interval
97
+
98
+ track_request.call(env)
99
+ client2 = track_request.send(:client)
100
+ channel2 = client2.channel
101
+ assert_same client, client2
102
+ assert_same channel, channel2
103
+ end
104
+
105
+ def test_format_request_duration_less_than_a_day
106
+ app = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
107
+ track_request = TrackRequest.new app, 'one'
108
+ duration_seconds = rand(86400) + rand
109
+ time_span = track_request.send(:format_request_duration, duration_seconds)
110
+ time_span_format = /^(?<day>\d{1}):(?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2}).(?<fraction>\d{7})$/
111
+ match = time_span_format.match time_span
112
+ assert_not_nil match
113
+ days = duration_seconds.to_i/86400
114
+ assert_equal days, match['day'].to_i
115
+ hours = (duration_seconds - days * 86400).to_i/3600
116
+ assert_equal hours, match['hour'].to_i
117
+ minutes = (duration_seconds - days * 86400 - hours * 3600).to_i/60
118
+ assert_equal minutes, match['minute'].to_i
119
+ seconds = (duration_seconds - days * 86400 - hours * 3600 - minutes * 60).to_i
120
+ assert_equal seconds, match['second'].to_i
121
+ fraction = ((duration_seconds - duration_seconds.to_i) * 10 ** 7).to_i
122
+ assert_equal fraction, match['fraction'].to_i
123
+ end
124
+
125
+ def test_format_request_duration_more_than_a_day
126
+ app = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
127
+ track_request = TrackRequest.new app, 'one'
128
+ duration_seconds = rand(86400..240000) + rand
129
+ time_span = track_request.send(:format_request_duration, duration_seconds)
130
+ time_span_format = /^(?<day>\d{1}):(?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2}).(?<fraction>\d{7})$/
131
+ match = time_span_format.match time_span
132
+ assert_not_nil match
133
+ assert_equal 1, match['day'].to_i
134
+ assert_equal 0, match['hour'].to_i
135
+ assert_equal 0, match['minute'].to_i
136
+ assert_equal 0, match['second'].to_i
137
+ assert_equal 0, match['fraction'].to_i
138
+ end
139
+ end
@@ -14,9 +14,10 @@ class TestTelemetryClient < Test::Unit::TestCase
14
14
  assert_not_nil client.channel
15
15
 
16
16
  channel = Object.new
17
- client = TelemetryClient.new channel
17
+ client = TelemetryClient.new 'a', channel
18
18
  assert_not_nil client.context
19
19
  assert_same channel, client.channel
20
+ assert_equal 'a', client.context.instrumentation_key
20
21
  end
21
22
 
22
23
  def test_context_property_works_as_expected
@@ -81,9 +82,19 @@ class TestTelemetryClient < Test::Unit::TestCase
81
82
 
82
83
  def test_track_trace_view_works_as_expected
83
84
  client, sender = self.create_client
84
- client.track_trace 'test'
85
+ client.track_trace 'test', Channel::Contracts::SeverityLevel::WARNING
85
86
  client.flush
86
- expected = '[{"ver":1,"name":"Microsoft.ApplicationInsights.Message","time":"TIME_PLACEHOLDER","sampleRate":100.0,"tags":{"ai.internal.sdkVersion":"rb:0.1.0"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"test"}}}]'
87
+ expected = '[{"ver":1,"name":"Microsoft.ApplicationInsights.Message","time":"TIME_PLACEHOLDER","sampleRate":100.0,"tags":{"ai.internal.sdkVersion":"rb:0.1.0"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"test","severityLevel":2}}}]'
88
+ sender.data_to_send[0].time = 'TIME_PLACEHOLDER'
89
+ actual = sender.data_to_send.to_json
90
+ assert_equal expected, actual
91
+ end
92
+
93
+ def test_track_request_view_works_as_expected
94
+ client, sender = self.create_client
95
+ client.track_request 'test', '2015-01-24T23:10:22.7411910-08:00', '0:00:00:02.0000000','200', true
96
+ client.flush
97
+ expected = '[{"ver":1,"name":"Microsoft.ApplicationInsights.Request","time":"TIME_PLACEHOLDER","sampleRate":100.0,"tags":{"ai.internal.sdkVersion":"rb:0.1.0"},"data":{"baseType":"RequestData","baseData":{"ver":2,"id":"test","startTime":"2015-01-24T23:10:22.7411910-08:00","duration":"0:00:00:02.0000000","responseCode":"200","success":true}}}]'
87
98
  sender.data_to_send[0].time = 'TIME_PLACEHOLDER'
88
99
  actual = sender.data_to_send.to_json
89
100
  assert_equal expected, actual
@@ -93,7 +104,7 @@ class TestTelemetryClient < Test::Unit::TestCase
93
104
  sender = MockTelemetryClientSender.new
94
105
  queue = Channel::SynchronousQueue.new sender
95
106
  channel = Channel::TelemetryChannel.new nil, queue
96
- client = TelemetryClient.new channel
107
+ client = TelemetryClient.new nil, channel
97
108
  return client, sender
98
109
  end
99
110
  end
@@ -1,4 +1,5 @@
1
1
  require 'test/unit'
2
+ require_relative 'mock_sender'
2
3
  require_relative '../../lib/application_insights/unhandled_exception'
3
4
 
4
5
  include ApplicationInsights
@@ -6,7 +7,7 @@ include ApplicationInsights
6
7
  class TestUnhandledException < Test::Unit::TestCase
7
8
 
8
9
  def test_send
9
- sender = MockSender.new
10
+ sender = MockSynchronousSender.new
10
11
  error = 'Boom!'
11
12
  instrumentation_key = 'one'
12
13
  begin
@@ -20,23 +21,4 @@ class TestUnhandledException < Test::Unit::TestCase
20
21
  assert_equal instrumentation_key, payload[0].i_key
21
22
  assert_equal 'Unhandled', payload[0].data.base_data.handled_at
22
23
  end
23
- end
24
-
25
- class MockSender
26
- def initialize
27
- @buffer = []
28
- @queue = nil
29
- @send_buffer_size = 100
30
- end
31
-
32
- attr_accessor :buffer
33
- attr_accessor :queue
34
- attr_accessor :send_buffer_size
35
-
36
- def send(data)
37
- @buffer << data
38
- end
39
-
40
- def flush
41
- end
42
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: application_insights
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Microsoft
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-15 00:00:00.000000000 Z
11
+ date: 2015-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
68
  version: 3.2.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
69
83
  description: This project extends the Application Insights API surface to support
70
84
  Ruby.
71
85
  email:
@@ -114,6 +128,7 @@ files:
114
128
  - lib/application_insights/channel/synchronous_sender.rb
115
129
  - lib/application_insights/channel/telemetry_channel.rb
116
130
  - lib/application_insights/channel/telemetry_context.rb
131
+ - lib/application_insights/rack/track_request.rb
117
132
  - lib/application_insights/telemetry_client.rb
118
133
  - lib/application_insights/unhandled_exception.rb
119
134
  - lib/application_insights/version.rb
@@ -146,6 +161,8 @@ files:
146
161
  - test/application_insights/channel/test_synchronous_sender.rb
147
162
  - test/application_insights/channel/test_telemetry_channel.rb
148
163
  - test/application_insights/channel/test_telemetry_context.rb
164
+ - test/application_insights/mock_sender.rb
165
+ - test/application_insights/rack/test_track_request.rb
149
166
  - test/application_insights/test_telemetry_client.rb
150
167
  - test/application_insights/test_unhandled_exception.rb
151
168
  homepage: https://github.com/Microsoft/AppInsights-Ruby
@@ -202,6 +219,8 @@ test_files:
202
219
  - test/application_insights/channel/test_synchronous_sender.rb
203
220
  - test/application_insights/channel/test_telemetry_channel.rb
204
221
  - test/application_insights/channel/test_telemetry_context.rb
222
+ - test/application_insights/mock_sender.rb
223
+ - test/application_insights/rack/test_track_request.rb
205
224
  - test/application_insights/test_telemetry_client.rb
206
225
  - test/application_insights/test_unhandled_exception.rb
207
226
  has_rdoc: