Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #55 from Microsoft/develop
Browse files Browse the repository at this point in the history
Release 0.5.6
  • Loading branch information
Sergey Kanzhelev authored May 2, 2018
2 parents e62c433 + ec96ca3 commit 8d3cf66
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 33 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ rvm:
- 2.2.0
- 2.3.3
- 2.4.0
- 2.5.0
- ruby-head

cache: bundler
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

This file needs to be updated with every significant pull request. It is used to write down release notes.

## Version 0.5.6
* Expose request id to parent Rack application when using `ApplicationInsights::Rack::TrackRequest` middleware through `env['ApplicationInsights.request.id']`.
* Implement operation context functionality for `ApplicationInsights::Rack::TrackRequest`.
* Add functionality to accept a Request-Id header for telemetry correlation.
* Add operation context to request tracking middleware.

## Version 0.5.5
* Add some basic logging when failed to send telemetry to the server
* Add timestamp as an optional parameter to the TelemetryChannel::write() method

## Version 0.5.4

Changelog started after this release.
Changelog started after this release.
38 changes: 38 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,44 @@ Make sure you have bundler installed, you can install it by ```sudo gem install

Run ```rake test```.

## Releasing new version

This is for repository maintainers only:

1. Create and merge develop->master PR https://github.com/Microsoft/ApplicationInsights-Ruby/compare/master...develop?expand=1
2. Checkout latest `master`
```
git checkout master
git pull
```
3. Remove old gem: `rm *.gem`
4. [Build gem](https://github.com/Microsoft/ApplicationInsights-Ruby/blob/develop/CONTRIBUTING.md#build-gem)
5. Push gem: `gem push application_insights-0.5.5.gem`
6. Check gem on [rubygems](https://rubygems.org/gems/application_insights)
7. Tag code:
```
git tag -a v0.5.5
git push origin v0.5.5
```
8. Update description of [release](https://github.com/Microsoft/ApplicationInsights-Ruby/releases/edit/v0.5.5) from [CHANGELOG.md](https://github.com/Microsoft/ApplicationInsights-Ruby/blob/master/CHANGELOG.md)
9. Create a branch off `develop` branch
```
git checkout develop
git pull
git checkout -b releaseUpdates
git push --set-upstream origin releaseUpdates
```
10. Update version in `/lib/application_insights/version.rb`
11. Create new entry for the next release in `/CHANGELOG.md`
12. Push changes
```
git add -A
git commit -m "post release updates"
git push
```
13. Submit releaseUpdates->develop PR: https://github.com/Microsoft/ApplicationInsights-Ruby/compare/develop...releaseUpdates?expand=1
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,17 @@ use ApplicationInsights::Rack::TrackRequest, '<YOUR INSTRUMENTATION KEY GOES HER
# For rails, suggest to set up this middleware in application.rb so that unhandled exceptions from controllers are also collected
config.middleware.use 'ApplicationInsights::Rack::TrackRequest', '<YOUR INSTRUMENTATION KEY GOES HERE>', <buffer size>
```

#### Rerieving the Request-Id value from ApplicationInsights ####
```ruby
# from time to time you may need to access a request's id from within your app
application_insights_request_id = env['ApplicationInsights.request.id']

# this can be used for a number of different purposes, including telemetry correlation
uri = URI('http://api.example.com/search/?q=test')

req = Net::HTTP::Get.new(uri)
req['Request-Id'] = "#{application_insights_request_id}1" if application_insights_request_id

Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
```
2 changes: 2 additions & 0 deletions application_insights.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'redcarpet', '~> 3.2.2'
spec.add_development_dependency 'rack', '>= 1.0.0'
spec.add_development_dependency 'test-unit', '~> 3.0.8'
spec.add_development_dependency 'mocha', '~> 1.5.0'

end
2 changes: 1 addition & 1 deletion lib/application_insights/channel/contracts/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Operation
root_id: 'ai.operation.rootId',
synthetic_source: 'ai.operation.syntheticSource',
is_synthetic: 'ai.operation.isSynthetic',
correlation_vector: "ai.operation.correlationVector"
correlation_vector: 'ai.operation.correlationVector'
)
end
end
12 changes: 10 additions & 2 deletions lib/application_insights/channel/queue_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class QueueBase
def initialize(sender)
@queue = Queue.new
@max_queue_length = 500
@sender = sender
@sender.queue = self if sender
self.sender = sender
end

# The maximum number of items that will be held by the queue before the
Expand All @@ -27,6 +26,15 @@ def initialize(sender)
# @return [SenderBase] the sender object.
attr_reader :sender

# Change the sender that is associated with this queue.
# @param [SenderBase] sender the sender object.
# @return [SenderBase] the sender object.
def sender=(sender)
@sender = sender
@sender.queue = self if sender
@sender
end

# Adds the passed in item object to the queue and calls {#flush} if the
# size of the queue is larger than {#max_queue_length}. This method does
# nothing if the passed in item is nil.
Expand Down
105 changes: 86 additions & 19 deletions lib/application_insights/rack/track_request.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'rack'
require 'securerandom'
require_relative '../channel/contracts/request_data'
require_relative '../telemetry_client'

Expand All @@ -19,11 +20,23 @@ def initialize(app, instrumentation_key, buffer_size = 500, send_interval = 60)
@instrumentation_key = instrumentation_key
@buffer_size = buffer_size
@send_interval = send_interval

@sender = Channel::AsynchronousSender.new
@sender.send_interval = @send_interval
queue = Channel::AsynchronousQueue.new @sender
queue.max_queue_length = @buffer_size
@channel = Channel::TelemetryChannel.new nil, queue

@client = TelemetryClient.new @instrumentation_key, @channel
end

# Track requests and send data to Application Insights asynchronously.
# @param [Hash] env the rack environment.
def call(env)
# Build a request ID, incorporating one from our request if one exists.
request_id = request_id_header(env['HTTP_REQUEST_ID'])
env['ApplicationInsights.request.id'] = request_id

start = Time.now
begin
status, headers, response = @app.call(env)
Expand All @@ -33,28 +46,17 @@ def call(env)
end
stop = Time.now

unless @client
sender = @sender || Channel::AsynchronousSender.new
sender.send_interval = @send_interval
queue = Channel::AsynchronousQueue.new sender
queue.max_queue_length = @buffer_size
channel = Channel::TelemetryChannel.new nil, queue

@client = TelemetryClient.new @instrumentation_key, channel
end

request = ::Rack::Request.new env
id = rand(16**32).to_s(16)
start_time = start.iso8601(7)
duration = format_request_duration(stop - start)
success = status.to_i < 400
options = {
:name => "#{request.request_method} #{request.path}",
:http_method => request.request_method,
:url => request.url
}

@client.track_request id, start_time, duration, status, success, options
request = ::Rack::Request.new env
options = options_hash(request)

data = request_data(request_id, start_time, duration, status, success, options)
context = telemetry_context(request_id, env['HTTP_REQUEST_ID'])

@client.channel.write data, context, start_time

if exception
@client.track_exception exception, handled_at: 'Unhandled'
Expand All @@ -67,7 +69,10 @@ def call(env)
private

def sender=(sender)
@sender = sender if sender.is_a? Channel::AsynchronousSender
if sender.is_a? Channel::AsynchronousSender
@sender = sender
@client.channel.queue.sender = @sender
end
end

def client
Expand All @@ -82,6 +87,68 @@ def format_request_duration(duration_seconds)

Time.at(duration_seconds).gmtime.strftime("00.%H:%M:%S.%7N")
end

def request_id_header(request_id)
valid_request_id_header = valid_request_id(request_id)

length = valid_request_id_header ? 5 : 10
id = SecureRandom.base64(length)

if valid_request_id_header
request_id_has_end = %w[. _].include?(request_id[-1])
request_id << '.' unless request_id_has_end

return "#{request_id}#{id}_"
end

"|#{id}."
end

def valid_request_id(request_id)
request_id && request_id[0] == '|'
end

def operation_id(id)
# Returns the root ID from the '|' to the first '.' if any.
root_start = id[0] == '|' ? 1 : 0

root_end = id.index('.')
root_end = root_end ? root_end - 1 : id.length - root_start

id[root_start..root_end]
end

def options_hash(request)
{
name: "#{request.request_method} #{request.path}",
http_method: request.request_method,
url: request.url
}
end

def request_data(request_id, start_time, duration, status, success, options)
Channel::Contracts::RequestData.new(
:id => request_id || 'Null',
:start_time => start_time || Time.now.iso8601(7),
:duration => duration || '0:00:00:00.0000000',
:response_code => status || 200,
:success => success == nil ? true : success,
:name => options[:name],
:http_method => options[:http_method],
:url => options[:url],
:properties => options[:properties] || {},
:measurements => options[:measurements] || {}
)
end

def telemetry_context(request_id, request_id_header)
context = Channel::TelemetryContext.new
context.instrumentation_key = @instrumentation_key
context.operation.id = operation_id(request_id)
context.operation.parent_id = request_id_header

context
end
end
end
end
2 changes: 1 addition & 1 deletion lib/application_insights/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ApplicationInsights
VERSION = '0.5.5'
VERSION = '0.5.6'.freeze
end
58 changes: 49 additions & 9 deletions test/application_insights/rack/test_track_request.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'test/unit'
require 'mocha/test_unit'
require 'rack/mock'
require_relative '../mock_sender'
require_relative '../../../lib/application_insights/rack/track_request'
Expand All @@ -20,8 +21,12 @@ def test_call_works_as_expected
track_request = TrackRequest.new app, instrumentation_key, 500, 1
track_request.send(:sender=, sender)
start_time = Time.now

SecureRandom.expects(:base64).with(10).returns('y0NM2eOY/fnQPw==')
result = track_request.call(env)
assert_equal app.call(env), result

app_result = app.call(env)
assert_equal app_result, result
sleep(sender.send_interval)

assert_equal 1, sender.buffer.count
Expand Down Expand Up @@ -89,20 +94,14 @@ def test_internal_client
send_interval = 5
track_request = TrackRequest.new app, 'key', buffer_size, send_interval
client = track_request.send(:client)
# test lazy initialization
assert_nil client
# test client initialization
assert_equal ApplicationInsights::TelemetryClient, client.class

track_request.call(env)
client = track_request.send(:client)
channel = client.channel
assert_equal buffer_size, channel.queue.max_queue_length
assert_equal send_interval, channel.sender.send_interval

track_request.call(env)
client2 = track_request.send(:client)
channel2 = client2.channel
assert_same client, client2
assert_same channel, channel2
end

def test_format_request_duration_less_than_a_day
Expand Down Expand Up @@ -139,4 +138,45 @@ def test_format_request_duration_more_than_a_day
assert_equal 0, match['second'].to_i
assert_equal 0, match['fraction'].to_i
end

def test_request_id_is_generated_correctly
app = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}
url = "http://localhost:8080/foo?a=b"
http_method = 'PUT'
env = Rack::MockRequest.env_for(url, :method => http_method)
instrumentation_key = 'key'
sender = MockAsynchronousSender.new
track_request = TrackRequest.new app, instrumentation_key, 500, 0
track_request.send(:sender=, sender)

# ignores ids that don't begin with | (16 chars)
env['HTTP_REQUEST_ID'] = 'ab456_1.ea6741a'
SecureRandom.expects(:base64).with(10).returns('y0NM2eOY/fnQPw==')
track_request.call(env)
assert_equal '|y0NM2eOY/fnQPw==.', env['ApplicationInsights.request.id']

# appends to ids with a dot (8 chars)
env['HTTP_REQUEST_ID'] = '|1234.'
SecureRandom.expects(:base64).with(5).returns('eXsMFHs=')
track_request.call(env)
assert_equal '|1234.eXsMFHs=_', env['ApplicationInsights.request.id']

# appends to ids with an underscore (8 chars)
env['HTTP_REQUEST_ID'] = '|1234_'
SecureRandom.expects(:base64).with(5).returns('eXsMFHs=')
track_request.call(env)
assert_equal '|1234_eXsMFHs=_', env['ApplicationInsights.request.id']

# appends a dot if neither a dot or underscore are present (8 chars)
env['HTTP_REQUEST_ID'] = '|ab456_1.ea6741a'
SecureRandom.expects(:base64).with(5).returns('eXsMFHs=')
track_request.call(env)
assert_equal '|ab456_1.ea6741a.eXsMFHs=_', env['ApplicationInsights.request.id']

# generates a stand-alone id if one is not provided (16 chars)
env.delete('HTTP_REQUEST_ID')
SecureRandom.expects(:base64).with(10).returns('y0NM2eOY/fnQPw==')
track_request.call(env)
assert_equal '|y0NM2eOY/fnQPw==.', env['ApplicationInsights.request.id']
end
end

0 comments on commit 8d3cf66

Please sign in to comment.