application_insights 0.5.3 → 0.5.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.
Files changed (82) hide show
  1. checksums.yaml +6 -14
  2. data/.travis.yml +21 -9
  3. data/CONTRIBUTING.md +30 -0
  4. data/Gemfile +4 -4
  5. data/LICENSE.txt +11 -11
  6. data/README.md +13 -14
  7. data/Rakefile +14 -14
  8. data/application_insights.gemspec +28 -29
  9. data/lib/application_insights.rb +9 -9
  10. data/lib/application_insights/channel/asynchronous_queue.rb +58 -51
  11. data/lib/application_insights/channel/asynchronous_sender.rb +132 -123
  12. data/lib/application_insights/channel/contracts/application.rb +14 -53
  13. data/lib/application_insights/channel/contracts/cloud.rb +14 -0
  14. data/lib/application_insights/channel/contracts/data.rb +14 -48
  15. data/lib/application_insights/channel/contracts/data_point.rb +24 -130
  16. data/lib/application_insights/channel/contracts/data_point_type.rb +7 -16
  17. data/lib/application_insights/channel/contracts/dependency_kind.rb +9 -19
  18. data/lib/application_insights/channel/contracts/dependency_source_type.rb +9 -19
  19. data/lib/application_insights/channel/contracts/device.rb +28 -257
  20. data/lib/application_insights/channel/contracts/envelope.rb +40 -266
  21. data/lib/application_insights/channel/contracts/event_data.rb +28 -77
  22. data/lib/application_insights/channel/contracts/exception_data.rb +37 -140
  23. data/lib/application_insights/channel/contracts/exception_details.rb +28 -129
  24. data/lib/application_insights/channel/contracts/internal.rb +14 -53
  25. data/lib/application_insights/channel/contracts/json_serializable.rb +59 -59
  26. data/lib/application_insights/channel/contracts/location.rb +16 -36
  27. data/lib/application_insights/channel/contracts/message_data.rb +24 -77
  28. data/lib/application_insights/channel/contracts/metric_data.rb +27 -60
  29. data/lib/application_insights/channel/contracts/operation.rb +19 -121
  30. data/lib/application_insights/channel/contracts/page_view_data.rb +30 -111
  31. data/lib/application_insights/channel/contracts/remote_dependency_data.rb +56 -260
  32. data/lib/application_insights/channel/contracts/request_data.rb +36 -176
  33. data/lib/application_insights/channel/contracts/session.rb +15 -70
  34. data/lib/application_insights/channel/contracts/severity_level.rb +13 -25
  35. data/lib/application_insights/channel/contracts/stack_frame.rb +17 -94
  36. data/lib/application_insights/channel/contracts/user.rb +19 -104
  37. data/lib/application_insights/channel/event.rb +68 -64
  38. data/lib/application_insights/channel/queue_base.rb +65 -62
  39. data/lib/application_insights/channel/sender_base.rb +79 -72
  40. data/lib/application_insights/channel/synchronous_queue.rb +45 -39
  41. data/lib/application_insights/channel/synchronous_sender.rb +17 -15
  42. data/lib/application_insights/channel/telemetry_channel.rb +120 -102
  43. data/lib/application_insights/channel/telemetry_context.rb +85 -68
  44. data/lib/application_insights/rack/track_request.rb +87 -84
  45. data/lib/application_insights/telemetry_client.rb +229 -217
  46. data/lib/application_insights/unhandled_exception.rb +49 -47
  47. data/lib/application_insights/version.rb +3 -3
  48. data/test/application_insights.rb +8 -9
  49. data/test/application_insights/channel/contracts/test_application.rb +44 -44
  50. data/test/application_insights/channel/contracts/test_cloud.rb +44 -0
  51. data/test/application_insights/channel/contracts/test_data.rb +44 -44
  52. data/test/application_insights/channel/contracts/test_data_point.rb +109 -109
  53. data/test/application_insights/channel/contracts/test_device.rb +200 -200
  54. data/test/application_insights/channel/contracts/test_envelope.rb +209 -209
  55. data/test/application_insights/channel/contracts/test_event_data.rb +62 -62
  56. data/test/application_insights/channel/contracts/test_exception_data.rb +111 -111
  57. data/test/application_insights/channel/contracts/test_exception_details.rb +106 -106
  58. data/test/application_insights/channel/contracts/test_internal.rb +44 -44
  59. data/test/application_insights/channel/contracts/test_location.rb +70 -31
  60. data/test/application_insights/channel/contracts/test_message_data.rb +66 -66
  61. data/test/application_insights/channel/contracts/test_metric_data.rb +50 -50
  62. data/test/application_insights/channel/contracts/test_operation.rb +109 -96
  63. data/test/application_insights/channel/contracts/test_page_view_data.rb +88 -88
  64. data/test/application_insights/channel/contracts/test_remote_dependency_data.rb +209 -209
  65. data/test/application_insights/channel/contracts/test_request_data.rb +153 -153
  66. data/test/application_insights/channel/contracts/test_session.rb +57 -57
  67. data/test/application_insights/channel/contracts/test_stack_frame.rb +83 -83
  68. data/test/application_insights/channel/contracts/test_user.rb +96 -83
  69. data/test/application_insights/channel/test_asynchronous_queue.rb +47 -47
  70. data/test/application_insights/channel/test_asynchronous_sender.rb +80 -80
  71. data/test/application_insights/channel/test_event.rb +52 -52
  72. data/test/application_insights/channel/test_queue_base.rb +88 -88
  73. data/test/application_insights/channel/test_sender_base.rb +87 -87
  74. data/test/application_insights/channel/test_synchronous_queue.rb +27 -27
  75. data/test/application_insights/channel/test_synchronous_sender.rb +10 -10
  76. data/test/application_insights/channel/test_telemetry_channel.rb +126 -102
  77. data/test/application_insights/channel/test_telemetry_context.rb +82 -74
  78. data/test/application_insights/mock_sender.rb +37 -37
  79. data/test/application_insights/rack/test_track_request.rb +142 -139
  80. data/test/application_insights/test_telemetry_client.rb +133 -123
  81. data/test/application_insights/test_unhandled_exception.rb +23 -24
  82. metadata +23 -33
@@ -1,15 +1,17 @@
1
- require_relative 'sender_base'
2
-
3
- module ApplicationInsights
4
- module Channel
5
- # A synchronous sender that works in conjunction with the {SynchronousQueue}. The queue will call {#send} on the
6
- # current instance with the data to send.
7
- class SynchronousSender < SenderBase
8
- # Initializes a new instance of the class.
9
- # @param [String] service_endpoint_uri the address of the service to send telemetry data to.
10
- def initialize(service_endpoint_uri='https://dc.services.visualstudio.com/v2/track')
11
- super service_endpoint_uri
12
- end
13
- end
14
- end
15
- end
1
+ require_relative 'sender_base'
2
+
3
+ module ApplicationInsights
4
+ module Channel
5
+ # A synchronous sender that works in conjunction with the {SynchronousQueue}.
6
+ # The queue will call {#send} on the current instance with the data to send.
7
+ class SynchronousSender < SenderBase
8
+ SERVICE_ENDPOINT_URI = 'https://dc.services.visualstudio.com/v2/track'
9
+ # Initializes a new instance of the class.
10
+ # @param [String] service_endpoint_uri the address of the service to send
11
+ # telemetry data to.
12
+ def initialize(service_endpoint_uri = SERVICE_ENDPOINT_URI)
13
+ super service_endpoint_uri
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,102 +1,120 @@
1
- require 'time'
2
- require_relative 'asynchronous_queue'
3
- require_relative 'asynchronous_sender'
4
- require_relative 'telemetry_context'
5
- require_relative 'synchronous_queue'
6
- require_relative 'synchronous_sender'
7
- require_relative 'contracts/envelope'
8
- require_relative 'contracts/data'
9
- require_relative 'contracts/internal'
10
- require_relative '../../application_insights/version'
11
-
12
- module ApplicationInsights
13
- module Channel
14
- # The telemetry channel is responsible for constructing a {Contracts::Envelope} object from the passed in
15
- # data and specified telemetry context.
16
- # @example
17
- # require 'application_insights'
18
- # channel = ApplicationInsights::Channel::TelemetryChannel.new
19
- # event = ApplicationInsights::Channel::Contracts::EventData.new :name => 'My event'
20
- # channel.write event
21
- class TelemetryChannel
22
- # Initializes a new instance of the class.
23
- # @param [TelemetryContext] context the telemetry context to use when sending telemetry data.
24
- # @param [QueueBase] queue the queue to enqueue the resulting {Contracts::Envelope} to.
25
- def initialize(context=nil, queue=nil)
26
- @context = context || TelemetryContext.new
27
- @queue = queue || SynchronousQueue.new(SynchronousSender.new)
28
- end
29
-
30
- # The context associated with this channel. All {Contracts::Envelope} objects created by this channel will use
31
- # this value if it's present or if none is specified as part of the {#write} call.
32
- # @return [TelemetryContext] the context instance (defaults to: TelemetryContext.new)
33
- attr_reader :context
34
-
35
- # The queue associated with this channel. All {Contracts::Envelope} objects created by this channel will be
36
- # pushed to this queue.
37
- # @return [QueueBase] the queue instance (defaults to: SynchronousQueue.new)
38
- attr_reader :queue
39
-
40
- # The sender associated with this channel. This instance will be used to transmit telemetry to the service.
41
- # @return [SenderBase] the sender instance (defaults to: SynchronousSender.new)
42
- def sender
43
- @queue.sender
44
- end
45
-
46
- # Flushes the enqueued data by calling {QueueBase#flush}.
47
- def flush
48
- @queue.flush
49
- end
50
-
51
- # Enqueues the passed in data to the {#queue}. If the caller specifies a context as well, it will take precedence
52
- # over the instance in {#context}.
53
- # @param [Object] data the telemetry data to send. This will be wrapped in an {Contracts::Envelope} before being
54
- # enqueued to the {#queue}.
55
- # @param [TelemetryContext] context the override context to use when constructing the {Contracts::Envelope}.
56
- def write(data, context=nil)
57
- local_context = context || @context
58
- raise ArgumentError, 'Context was required but not provided' unless local_context
59
- data_type = data.class.name.gsub(/^.*::/, '')
60
- set_properties data, local_context
61
- data_attributes = {
62
- :base_type => data_type,
63
- :base_data => data
64
- }
65
- envelope_attributes = {
66
- :name => 'Microsoft.ApplicationInsights.' + data_type[0..-5],
67
- :time => Time.now.iso8601(7),
68
- :i_key => local_context.instrumentation_key,
69
- :tags => get_tags(local_context),
70
- :data => Contracts::Data.new(data_attributes)
71
- }
72
- envelope = Contracts::Envelope.new envelope_attributes
73
- @queue.push(envelope)
74
- end
75
-
76
- private
77
-
78
- def get_tags(context)
79
- hash = {}
80
- internal_context_attributes = {
81
- :sdk_version => 'rb:' + ApplicationInsights::VERSION
82
- }
83
- internal_context = Contracts::Internal.new internal_context_attributes
84
- contexts = [ internal_context, context.application, context.device, context.user, context.session, context.location, context.operation ]
85
- contexts.each { |c| hash.merge!(c.to_h) if c }
86
- hash
87
- end
88
-
89
- def set_properties(data, context)
90
- if context.properties
91
- properties = data.properties || {}
92
- context.properties.each do |key, value|
93
- unless properties.key?(key)
94
- properties[key] = value
95
- end
96
- end
97
- data.properties = properties
98
- end
99
- end
100
- end
101
- end
102
- end
1
+ require 'time'
2
+ require_relative 'asynchronous_queue'
3
+ require_relative 'asynchronous_sender'
4
+ require_relative 'telemetry_context'
5
+ require_relative 'synchronous_queue'
6
+ require_relative 'synchronous_sender'
7
+ require_relative 'contracts/envelope'
8
+ require_relative 'contracts/data'
9
+ require_relative 'contracts/internal'
10
+ require_relative '../../application_insights/version'
11
+
12
+ module ApplicationInsights
13
+ module Channel
14
+ # The telemetry channel is responsible for constructing a
15
+ # {Contracts::Envelope} object from the passed in data and specified
16
+ # telemetry context.
17
+ #
18
+ # @example
19
+ # require 'application_insights'
20
+ # channel = ApplicationInsights::Channel::TelemetryChannel.new
21
+ # event = ApplicationInsights::Channel::Contracts::EventData.new name: 'My event'
22
+ # channel.write event
23
+ class TelemetryChannel
24
+ # Initializes a new instance of the class.
25
+ # @param [TelemetryContext] context the telemetry context to use when
26
+ # sending telemetry data.
27
+ # @param [QueueBase] queue the queue to enqueue the resulting
28
+ # {Contracts::Envelope} to.
29
+ def initialize(context=nil, queue=nil)
30
+ @context = context || TelemetryContext.new
31
+ @queue = queue || SynchronousQueue.new(SynchronousSender.new)
32
+ end
33
+
34
+ # The context associated with this channel. All {Contracts::Envelope}
35
+ # objects created by this channel will use this value if it's present or if
36
+ # none is specified as part of the {#write} call.
37
+ # @return [TelemetryContext] the context instance
38
+ # (defaults to: TelemetryContext.new)
39
+ attr_reader :context
40
+
41
+ # The queue associated with this channel. All {Contracts::Envelope} objects
42
+ # created by this channel will be pushed to this queue.
43
+ # @return [QueueBase] the queue instance (defaults to: SynchronousQueue.new)
44
+ attr_reader :queue
45
+
46
+ # The sender associated with this channel. This instance will be used to
47
+ # transmit telemetry to the service.
48
+ # @return [SenderBase] the sender instance (defaults to: SynchronousSender.new)
49
+ def sender
50
+ @queue.sender
51
+ end
52
+
53
+ # Flushes the enqueued data by calling {QueueBase#flush}.
54
+ def flush
55
+ @queue.flush
56
+ end
57
+
58
+ # Enqueues the passed in data to the {#queue}. If the caller specifies a
59
+ # context as well, it will take precedence over the instance in {#context}.
60
+ # @param [Object] data the telemetry data to send. This will be wrapped in
61
+ # an {Contracts::Envelope} before being enqueued to the {#queue}.
62
+ # @param [TelemetryContext] context the override context to use when
63
+ # constructing the {Contracts::Envelope}.
64
+ def write(data, context=nil)
65
+ local_context = context || @context
66
+ raise ArgumentError, 'Context was required but not provided' unless local_context
67
+ data_type = data.class.name.gsub(/^.*::/, '')
68
+ set_properties data, local_context
69
+ data_attributes = {
70
+ :base_type => data_type,
71
+ :base_data => data
72
+ }
73
+ envelope_attributes = {
74
+ :name => 'Microsoft.ApplicationInsights.' + data_type[0..-5],
75
+ :time => Time.now.iso8601(7),
76
+ :i_key => local_context.instrumentation_key,
77
+ :tags => get_tags(local_context),
78
+ :data => Contracts::Data.new(data_attributes)
79
+ }
80
+ envelope = Contracts::Envelope.new envelope_attributes
81
+ @queue.push(envelope)
82
+ end
83
+
84
+ private
85
+
86
+ def get_tags(context)
87
+ hash = {}
88
+ internal_context_attributes = {
89
+ :sdk_version => 'rb:' + ApplicationInsights::VERSION
90
+ }
91
+ internal_context = Contracts::Internal.new internal_context_attributes
92
+
93
+ [internal_context,
94
+ context.application,
95
+ context.cloud,
96
+ context.device,
97
+ context.user,
98
+ context.session,
99
+ context.location,
100
+ context.operation].each { |c| hash.merge!(c.to_h) if c }
101
+
102
+ hash.delete_if { |k, v| v.nil? }
103
+
104
+ hash
105
+ end
106
+
107
+ def set_properties(data, context)
108
+ if context.properties
109
+ properties = data.properties || {}
110
+ context.properties.each do |key, value|
111
+ unless properties.key?(key)
112
+ properties[key] = value
113
+ end
114
+ end
115
+ data.properties = properties
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,68 +1,85 @@
1
- require_relative 'contracts/application'
2
- require_relative 'contracts/device'
3
- require_relative 'contracts/user'
4
- require_relative 'contracts/session'
5
- require_relative 'contracts/operation'
6
- require_relative 'contracts/location'
7
-
8
- module ApplicationInsights
9
- module Channel
10
- # Represents the context for sending telemetry to the Application Insights service.
11
- # @example
12
- # require 'application_insights'
13
- # context = ApplicationInsights::Channel::TelemetryContext.new
14
- # context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
15
- # context.application.id = 'My application'
16
- # context.application.ver = '1.2.3'
17
- # context.device.id = 'My current device'
18
- # context.device.oem_name = 'Asus'
19
- # context.device.model = 'X31A'
20
- # context.device.type = "Other"
21
- # context.user.id = 'santa@northpole.net'
22
- class TelemetryContext
23
- # Initializes a new instance of the class.
24
- def initialize
25
- @instrumentation_key = nil
26
- @application = Contracts::Application.new
27
- @device = Contracts::Device.new
28
- @user = Contracts::User.new
29
- @session = Contracts::Session.new
30
- @operation = Contracts::Operation.new
31
- @location = Contracts::Location.new
32
- @properties = {}
33
- end
34
-
35
- # The instrumentation key that is used to identify which Application Insights application this data is for.
36
- # @return [String] the instrumentation key.
37
- attr_accessor :instrumentation_key
38
-
39
- # The application context. This contains properties of the application you are running.
40
- # @return [Contracts::Application] the context object.
41
- attr_accessor :application
42
-
43
- # The device context. This contains properties of the device you are running on.
44
- # @return [Contracts::Device] the context object.
45
- attr_accessor :device
46
-
47
- # The user context. This contains properties of the user you are generating telemetry for.
48
- # @return [Contracts::User] the context object.
49
- attr_accessor :user
50
-
51
- # The session context. This contains properties of the session you are generating telemetry for.
52
- # @return [Contracts::Session] the context object.
53
- attr_accessor :session
54
-
55
- # The operation context. This contains properties of the operation you are generating telemetry for.
56
- # @return [Contracts::Operation] the context object.
57
- attr_accessor :operation
58
-
59
- # The location context. This contains properties of the location you are generating telemetry from.
60
- # @return [Contracts::Location] the context object.
61
- attr_accessor :location
62
-
63
- # The property context. This contains free-form properties that you can add to your telemetry.
64
- # @return [Hash<String, String>] the context object.
65
- attr_accessor :properties
66
- end
67
- end
68
- end
1
+ require_relative 'contracts/application'
2
+ require_relative 'contracts/cloud'
3
+ require_relative 'contracts/device'
4
+ require_relative 'contracts/user'
5
+ require_relative 'contracts/session'
6
+ require_relative 'contracts/operation'
7
+ require_relative 'contracts/location'
8
+
9
+ module ApplicationInsights
10
+ module Channel
11
+ # Represents the context for sending telemetry to the
12
+ # Application Insights service.
13
+ #
14
+ # @example
15
+ # require 'application_insights'
16
+ # context = ApplicationInsights::Channel::TelemetryContext.new
17
+ # context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
18
+ # context.application.id = 'My application'
19
+ # context.application.ver = '1.2.3'
20
+ # context.device.id = 'My current device'
21
+ # context.device.oem_name = 'Asus'
22
+ # context.device.model = 'X31A'
23
+ # context.device.type = "Other"
24
+ # context.user.id = 'santa@northpole.net'
25
+ class TelemetryContext
26
+ # Initializes a new instance of the class.
27
+ def initialize
28
+ @instrumentation_key = nil
29
+ @application = Contracts::Application.new
30
+ @cloud = Contracts::Cloud.new
31
+ @device = Contracts::Device.new
32
+ @user = Contracts::User.new
33
+ @session = Contracts::Session.new
34
+ @operation = Contracts::Operation.new
35
+ @location = Contracts::Location.new
36
+ @properties = {}
37
+ end
38
+
39
+ # The instrumentation key that is used to identify which
40
+ # Application Insights application this data is for.
41
+ # @return [String] the instrumentation key.
42
+ attr_accessor :instrumentation_key
43
+
44
+ # The application context. This contains properties of the
45
+ # application you are running.
46
+ # @return [Contracts::Application] the context object.
47
+ attr_accessor :application
48
+
49
+ # The cloud context. This contains properties of the
50
+ # cloud role you are generating telemetry for.
51
+ # @return [Contracts::Cloud] the context object.
52
+ attr_accessor :cloud
53
+
54
+ # The device context. This contains properties of the
55
+ # device you are running on.
56
+ # @return [Contracts::Device] the context object.
57
+ attr_accessor :device
58
+
59
+ # The user context. This contains properties of the
60
+ # user you are generating telemetry for.
61
+ # @return [Contracts::User] the context object.
62
+ attr_accessor :user
63
+
64
+ # The session context. This contains properties of the
65
+ # session you are generating telemetry for.
66
+ # @return [Contracts::Session] the context object.
67
+ attr_accessor :session
68
+
69
+ # The operation context. This contains properties of the
70
+ # operation you are generating telemetry for.
71
+ # @return [Contracts::Operation] the context object.
72
+ attr_accessor :operation
73
+
74
+ # The location context. This contains properties of the
75
+ # location you are generating telemetry from.
76
+ # @return [Contracts::Location] the context object.
77
+ attr_accessor :location
78
+
79
+ # The property context. This contains free-form properties
80
+ # that you can add to your telemetry.
81
+ # @return [Hash<String, String>] the context object.
82
+ attr_accessor :properties
83
+ end
84
+ end
85
+ end
@@ -1,84 +1,87 @@
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
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
12
+ # application this data is for.
13
+ # @param [Fixnum] buffer_size the buffer size and the buffered requests would
14
+ # send to Application Insights when buffer is full.
15
+ # @param [Fixnum] send_interval the frequency (in seconds) to check buffer
16
+ # and send buffered requests to Application Insights if any.
17
+ def initialize(app, instrumentation_key, buffer_size = 500, send_interval = 60)
18
+ @app = app
19
+ @instrumentation_key = instrumentation_key
20
+ @buffer_size = buffer_size
21
+ @send_interval = send_interval
22
+ end
23
+
24
+ # Track requests and send data to Application Insights asynchronously.
25
+ # @param [Hash] env the rack environment.
26
+ def call(env)
27
+ start = Time.now
28
+ begin
29
+ status, headers, response = @app.call(env)
30
+ rescue Exception => ex
31
+ status = 500
32
+ exception = ex
33
+ end
34
+ stop = Time.now
35
+
36
+ unless @client
37
+ sender = @sender || Channel::AsynchronousSender.new
38
+ sender.send_interval = @send_interval
39
+ queue = Channel::AsynchronousQueue.new sender
40
+ queue.max_queue_length = @buffer_size
41
+ channel = Channel::TelemetryChannel.new nil, queue
42
+
43
+ @client = TelemetryClient.new @instrumentation_key, channel
44
+ end
45
+
46
+ request = ::Rack::Request.new env
47
+ id = rand(16**32).to_s(16)
48
+ start_time = start.iso8601(7)
49
+ duration = format_request_duration(stop - start)
50
+ success = status.to_i < 400
51
+ options = {
52
+ :name => "#{request.request_method} #{request.path}",
53
+ :http_method => request.request_method,
54
+ :url => request.url
55
+ }
56
+
57
+ @client.track_request id, start_time, duration, status, success, options
58
+
59
+ if exception
60
+ @client.track_exception exception, handled_at: 'Unhandled'
61
+ raise exception
62
+ end
63
+
64
+ [status, headers, response]
65
+ end
66
+
67
+ private
68
+
69
+ def sender=(sender)
70
+ @sender = sender if sender.is_a? Channel::AsynchronousSender
71
+ end
72
+
73
+ def client
74
+ @client
75
+ end
76
+
77
+ def format_request_duration(duration_seconds)
78
+ if duration_seconds >= 86400
79
+ # just return 1 day when it takes more than 1 day which should not happen for requests.
80
+ return "%02d.%02d:%02d:%02d.%07d" % [1, 0, 0, 0, 0]
81
+ end
82
+
83
+ Time.at(duration_seconds).gmtime.strftime("00.%H:%M:%S.%7N")
84
+ end
85
+ end
86
+ end
87
+ end