application_insights 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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