librato-logreporter 0.1.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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ### Version 0.1.0
2
+ * Initial version
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2011. Librato, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of Librato, Inc. nor the names of project contributors
12
+ may be used to endorse or promote products derived from this software
13
+ without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL LIBRATO, INC. BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ librato-logreporter
2
+ =======
3
+
4
+ [![Build Status](https://secure.travis-ci.org/librato/librato-logreporter.png?branch=master)](http://travis-ci.org/librato/librato-logreporter) [![Code Climate](https://codeclimate.com/github/librato/librato-logreporter.png)](https://codeclimate.com/github/librato/librato-logreporter)
5
+
6
+ NOTE: This library is in active development and is suggested for early-adopter use only.
7
+
8
+ `librato-logreporter` provides an easy interface to write metrics ultimately bound for [Librato Metrics](https://metrics.librato.com/) to your logs or another IO stream. It is fully format-compliant with [l2met](https://github.com/ryandotsmith/l2met). If you are running on Heroku it will allow you to easily insert metrics which can be retrieved via a [log drain](https://devcenter.heroku.com/articles/logging#syslog-drains).
9
+
10
+ This library is ideally suited for custom or short-lived processes where the overhead of in-process collection will be costly and external metric collectors are unavailable.
11
+
12
+ If you are considering using `librato-logreporter` for a rails or rack-based web app, first explore [librato-rails](https://github.com/librato/librato-rails) and/or [librato-rack](https://github.com/librato/librato-rack). In most cases one of these libraries will be a better solution for your web applications.
13
+
14
+ Currently Ruby 1.9.2+ is required.
15
+
16
+ ## Quick Start
17
+
18
+ Install `librato-logreporter` in your application:
19
+
20
+ require 'librato/logreporter'
21
+
22
+ You can now track custom metrics by adding simple one-liners to your code:
23
+
24
+ # keep counts of key events
25
+ Librato.increment 'jobs.worked'
26
+
27
+ # benchmark sections of code to verify performance
28
+ Librato.timing 'my.complicated.work' do
29
+ # do work
30
+ end
31
+
32
+ # track averages across processes/jobs/requests
33
+ Librato.measure 'payload.size', payload.length_in_bytes
34
+
35
+ ## Installation & Configuration
36
+
37
+ Install the gem:
38
+
39
+ $ gem install librato-logreporter
40
+
41
+ Or add to your Gemfile if using bundler:
42
+
43
+ gem "librato-logreporter"
44
+
45
+ Then require it:
46
+
47
+ require 'librato-reporter'
48
+
49
+ If you don't have a [Librato Metrics](https://metrics.librato.com/) account already, [sign up](https://metrics.librato.com/). In order to send measurements to Librato you will need to provide your account credentials to the processor for output from `librato-reporter`.
50
+
51
+ ##### Environment variables
52
+
53
+ There are a few optional environment variables you may find useful:
54
+
55
+ * `LIBRATO_SOURCE` - the default source to use for submitted metrics. If not set your metrics will be submitted without a source.
56
+ * `LIBRATO_PREFIX` - a prefix which will be appended to all metric names
57
+
58
+ ##### Running on Heroku
59
+
60
+ You should specify a custom source for your app to track properly. You can set the source in your environment:
61
+
62
+ heroku config:add LIBRATO_SOURCE=myappname
63
+
64
+ NOTE: if Heroku idles your process no measurements will be sent until it receives a request and is restarted. If you see intermittent gaps in your measurements during periods of low traffic this is the most likely cause.
65
+
66
+ ##### Harvesting your metrics from your logs
67
+
68
+ There are few options for this which we will document further going forward. For the moment, [come ask us about it](http://chat.librato.com/).
69
+
70
+ ## Custom Measurements
71
+
72
+ Tracking anything that interests you is easy with Librato. There are four primary helpers available:
73
+
74
+ #### increment
75
+
76
+ Use for tracking a running total of something _across_ jobs or requests, examples:
77
+
78
+ # increment the 'jobs.completed' metric by one
79
+ Librato.increment 'jobs.completed'
80
+
81
+ # increment by five
82
+ Librato.increment 'items.purchased', :by => 5
83
+
84
+ # increment with a custom source
85
+ Librato.increment 'user.purchases', :source => user.id
86
+
87
+ Other things you might track this way: user activity, requests of a certain type or to a certain route, total jobs queued or processed, emails sent or received
88
+
89
+ #### measure
90
+
91
+ Use when you want to track an average value _per_-measurement period. Examples:
92
+
93
+ Librato.measure 'payload.size', 212
94
+
95
+ # report from a custom source
96
+ Librato.measure 'jobs.by.user', 3, :source => job.requestor.id
97
+
98
+ #### timing
99
+
100
+ Like `Librato.measure` this is per-period, but specialized for timing information:
101
+
102
+ Librato.timing 'twitter.lookup.time', 21.2
103
+
104
+ The block form auto-submits the time it took for its contents to execute as the measurement value:
105
+
106
+ Librato.timing 'twitter.lookup.time' do
107
+ @twitter = Twitter.lookup(user)
108
+ end
109
+
110
+ #### group
111
+
112
+ There is also a grouping helper, to make managing nested metrics easier. So this:
113
+
114
+ Librato.measure 'memcached.gets', 20
115
+ Librato.measure 'memcached.sets', 2
116
+ Librato.measure 'memcached.hits', 18
117
+
118
+ Can also be written as:
119
+
120
+ Librato.group 'memcached' do |g|
121
+ g.measure 'gets', 20
122
+ g.measure 'sets', 2
123
+ g.measure 'hits', 18
124
+ end
125
+
126
+ Symbols can be used interchangeably with strings for metric names.
127
+
128
+ ## Contribution
129
+
130
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
131
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
132
+ * Fork the project and submit a pull request from a feature or bugfix branch.
133
+ * Please include tests. This is important so we don't break your changes unintentionally in a future version.
134
+ * Please don't modify the gemspec, Rakefile, version, or changelog. If you do change these files, please isolate a separate commit so we can cherry-pick around it.
135
+
136
+ ## Copyright
137
+
138
+ Copyright (c) 2013 [Librato Inc.](http://librato.com) See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # console
9
+ desc "Open an console session preloaded with this library"
10
+ task :console do
11
+ sh "pry -r ./lib/librato-logreporter.rb"
12
+ end
13
+
14
+ Bundler::GemHelper.install_tasks
15
+
16
+ require 'rake/testtask'
17
+
18
+ Rake::TestTask.new(:test) do |t|
19
+ t.libs << 'lib'
20
+ t.libs << 'test'
21
+ t.pattern = 'test/**/*_test.rb'
22
+ t.verbose = false
23
+ end
24
+
25
+ task :default => :test
@@ -0,0 +1 @@
1
+ require 'librato/logreporter'
@@ -0,0 +1,123 @@
1
+ require_relative 'logreporter/configuration'
2
+ require_relative 'logreporter/group'
3
+ require_relative 'logreporter/version'
4
+
5
+ module Librato
6
+ extend SingleForwardable
7
+ def_delegators :log_reporter, :increment, :measure, :timing, :group
8
+
9
+ def self.log_reporter
10
+ @log_reporter ||= LogReporter.new
11
+ end
12
+
13
+ # Provides a common interface to reporting metrics with methods
14
+ # like #increment, #measure, #timing, #group - all written
15
+ # to your preferred IO stream.
16
+ #
17
+ class LogReporter
18
+ include Configuration
19
+
20
+ # Group a set of metrics by common prefix
21
+ #
22
+ # @example
23
+ # # write a 'performance.hits' increment and
24
+ # # a 'performance.response.time' measure
25
+ # group(:performance) do |perf|
26
+ # perf.increment :hits
27
+ # perf.measure 'response.time', response_time
28
+ # end
29
+ #
30
+ def group(prefix)
31
+ yield Group.new(collector: self, prefix: prefix)
32
+ end
33
+
34
+ # Increment a given metric
35
+ #
36
+ # @example Increment metric 'foo' by 1
37
+ # increment :foo
38
+ #
39
+ # @example Increment metric 'bar' by 2
40
+ # increment :bar, :by => 2
41
+ #
42
+ # @example Increment metric 'foo' by 1 with a custom source
43
+ # increment :foo, :source => user.id
44
+ #
45
+ def increment(counter, options={})
46
+ by = options[:by] || 1
47
+ log_write(counter => by, :source => options[:source])
48
+ end
49
+
50
+ # @example Simple measurement
51
+ # measure 'sources_returned', sources.length
52
+ #
53
+ # @example Simple timing in milliseconds
54
+ # timing 'myservice.lookup', 2.31
55
+ #
56
+ # @example Block-based timing
57
+ # timing 'db.query' do
58
+ # do_my_query
59
+ # end
60
+ #
61
+ # @example Custom source
62
+ # measure 'user.all_orders', user.order_count, :source => user.id
63
+ #
64
+ def measure(*args, &block)
65
+ options = {}
66
+ event = args[0].to_s
67
+ returned = nil
68
+
69
+ # handle block or specified argument
70
+ if block_given?
71
+ start = Time.now
72
+ returned = yield
73
+ value = ((Time.now - start) * 1000.0).to_i
74
+ elsif args[1]
75
+ value = args[1]
76
+ else
77
+ raise "no value provided"
78
+ end
79
+
80
+ # detect options hash if present
81
+ if args.length > 1 and args[-1].respond_to?(:each)
82
+ options = args[-1]
83
+ end
84
+
85
+ log_write(event => value, :source => options[:source])
86
+ returned
87
+ end
88
+ alias :timing :measure
89
+
90
+ private
91
+
92
+ # take key/value pairs and return an array of measure strings
93
+ def add_prefixes(measures)
94
+ measure_prefix = 'measure.'
95
+ measure_prefix << "#{prefix}." if prefix
96
+ measures.map { |keyval|
97
+ joined = keyval.join('=')
98
+ if keyval[0].to_sym == :source
99
+ joined
100
+ else
101
+ measure_prefix + joined
102
+ end
103
+ }
104
+ end
105
+
106
+ # add default source as appropriate
107
+ def manage_source(measures)
108
+ if source && !measures[:source]
109
+ measures[:source] = source # set default source
110
+ end
111
+ if !source && measures.has_key?(:source) && !measures[:source]
112
+ measures.delete(:source) # remove empty source
113
+ end
114
+ measures
115
+ end
116
+
117
+ def log_write(measures)
118
+ measure_chunks = add_prefixes(manage_source(measures))
119
+ log.puts measure_chunks.join(' ')
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,41 @@
1
+ module Librato
2
+ class LogReporter
3
+
4
+ # Handles configuration options and intelligent defaults
5
+ # for the LogReporter class.
6
+ #
7
+ module Configuration
8
+
9
+ # current IO to log to
10
+ def log
11
+ @log ||= $stdout
12
+ end
13
+
14
+ # set IO to log to
15
+ def log=(io)
16
+ @log = io
17
+ end
18
+
19
+ # current prefix
20
+ def prefix
21
+ @prefix ||= ENV['LIBRATO_PREFIX']
22
+ end
23
+
24
+ # set prefix
25
+ def prefix=(prefix)
26
+ @prefix = prefix
27
+ end
28
+
29
+ # current default source
30
+ def source
31
+ @source ||= ENV['LIBRATO_SOURCE']
32
+ end
33
+
34
+ # set default source
35
+ def source=(source)
36
+ @source = source
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ module Librato
2
+ class LogReporter
3
+
4
+ # Encapsulates state for grouping operations
5
+ #
6
+ class Group
7
+
8
+ def initialize(options={})
9
+ @collector = options[:collector]
10
+ @prefix = "#{options[:prefix]}."
11
+ end
12
+
13
+ def group(prefix)
14
+ prefix = apply_prefix(prefix)
15
+ yield self.class.new(collector: @collector, prefix: prefix)
16
+ end
17
+
18
+ def increment(counter, options={})
19
+ counter = apply_prefix(counter)
20
+ @collector.increment counter, options
21
+ end
22
+
23
+ def measure(*args, &block)
24
+ args[0] = apply_prefix(args[0])
25
+ @collector.measure(*args, &block)
26
+ end
27
+ alias :timing :measure
28
+
29
+ private
30
+
31
+ def apply_prefix(str)
32
+ "#{@prefix}#{str}"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Librato
2
+ class LogReporter
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'pry'
5
+ require 'minitest/autorun'
6
+
7
+ require 'librato/logreporter'
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+ require 'stringio'
3
+
4
+ module Librato
5
+ class LogReporter
6
+ class ConfigurationTest < MiniTest::Test
7
+
8
+ def setup
9
+ @reporter = LogReporter.new
10
+ end
11
+
12
+ def test_default_log
13
+ assert_equal $stdout, @reporter.log
14
+ end
15
+
16
+ def test_setting_log
17
+ @reporter.log = $stderr
18
+ assert_equal $stderr, @reporter.log
19
+ end
20
+
21
+ def test_default_prefix
22
+ assert_equal nil, @reporter.prefix
23
+ end
24
+
25
+ def test_setting_prefix
26
+ @reporter.prefix = 'mypref'
27
+ assert_equal 'mypref', @reporter.prefix
28
+ end
29
+
30
+ def test_default_source
31
+ assert_equal nil, @reporter.source
32
+ end
33
+
34
+ def test_setting_source
35
+ @reporter.source = 'librato'
36
+ assert_equal 'librato', @reporter.source
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ module Librato
4
+ class LogReporter
5
+ class GroupTest < MiniTest::Test
6
+
7
+ def setup
8
+ @reporter = MiniTest::Mock.new
9
+ @group = Group.new(collector: @reporter, prefix: 'mypref')
10
+ end
11
+
12
+ def test_increment
13
+ @reporter.expect :increment, true, ['mypref.foo', {}]
14
+ @group.increment 'foo'
15
+ @reporter.verify
16
+
17
+ @reporter.expect :increment, true, ['mypref.bar', {:source => 'baz'}]
18
+ @group.increment 'bar', :source => 'baz'
19
+ @reporter.verify
20
+ end
21
+
22
+ def test_measure
23
+ @reporter.expect :measure, true, ['mypref.mpg', 52]
24
+ @group.measure 'mpg', 52
25
+ @reporter.verify
26
+
27
+ @reporter.expect :measure, true, ['mypref.mpg', 8, {:source => 'vette'}]
28
+ @group.measure 'mpg', 8, :source => 'vette'
29
+ @reporter.verify
30
+ end
31
+
32
+ def test_timing
33
+ @reporter.expect :measure, true, ['mypref.completion']
34
+ @group.timing('completion') { sleep 0.01 }
35
+ @reporter.verify
36
+ end
37
+
38
+ def test_nesting_group
39
+ @reporter.expect :increment, true, ['mypref.more.foo', {}]
40
+ @group.group :more do |m|
41
+ m.increment :foo
42
+ end
43
+ @reporter.verify
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+ require 'stringio'
3
+
4
+ module Librato
5
+ class LogReporterTest < MiniTest::Test
6
+
7
+ def setup
8
+ @buffer = StringIO.new
9
+ @reporter = LogReporter.new
10
+ @reporter.log = @buffer
11
+ end
12
+
13
+ def test_increment
14
+ @reporter.increment :foo
15
+ assert_last_logged 'measure.foo=1'
16
+
17
+ @reporter.increment 'foo.bar', :by => 2
18
+ assert_last_logged 'measure.foo.bar=2'
19
+ end
20
+
21
+ def test_increment_supports_source
22
+ @reporter.source = 'sf'
23
+
24
+ # default source
25
+ @reporter.increment 'days.foggy'
26
+ assert_last_logged 'measure.days.foggy=1 source=sf'
27
+
28
+ # custom source
29
+ @reporter.increment 'days.foggy', :source => 'seattle'
30
+ assert_last_logged 'measure.days.foggy=1 source=seattle'
31
+ end
32
+
33
+ def test_measure
34
+ @reporter.measure 'documents.rendered', 12
35
+ assert_last_logged 'measure.documents.rendered=12'
36
+
37
+ # custom source
38
+ @reporter.measure 'cycles.wasted', 23, :source => 'cpu_1'
39
+ assert_last_logged 'measure.cycles.wasted=23 source=cpu_1'
40
+ end
41
+
42
+ def test_timing
43
+ @reporter.timing('do.stuff') { sleep 0.1 }
44
+ last = last_logged
45
+ assert last =~ /\=/, 'should have a measure pair'
46
+ key, value = last.split('=')
47
+ assert_equal 'measure.do.stuff', key, 'should have timing key'
48
+ assert_in_delta 100, value.to_i, 10
49
+
50
+ # custom source
51
+ @reporter.timing('do.more', :source => 'worker') do
52
+ sleep 0.05
53
+ end
54
+ assert last_logged.index('source=worker'), 'should use custom source'
55
+ end
56
+
57
+ def test_basic_grouping
58
+ @reporter.group :pages do |p|
59
+ p.increment :total
60
+ p.timing :render_time, 63
61
+ # nested
62
+ p.group('public') { |pub| pub.increment 'views', :by => 2 }
63
+ end
64
+
65
+ @buffer.rewind
66
+ lines = @buffer.readlines
67
+ assert_equal 'measure.pages.total=1', lines[0].chomp
68
+ assert_equal 'measure.pages.render_time=63', lines[1].chomp
69
+ assert_equal 'measure.pages.public.views=2', lines[2].chomp
70
+ end
71
+
72
+ def test_custom_prefix
73
+ @reporter.prefix = 'librato'
74
+
75
+ # increment
76
+ @reporter.increment 'views'
77
+ assert_last_logged 'measure.librato.views=1'
78
+
79
+ # measure/timing
80
+ @reporter.measure 'sql.queries', 6
81
+ assert_last_logged 'measure.librato.sql.queries=6'
82
+
83
+ # group
84
+ @reporter.group :private do |priv|
85
+ priv.increment 'secret'
86
+ end
87
+ assert_last_logged 'measure.librato.private.secret=1'
88
+ end
89
+
90
+ private
91
+
92
+ def assert_last_logged(string)
93
+ assert_equal string, last_logged, "Last logged should be '#{string}'."
94
+ end
95
+
96
+ def last_logged
97
+ @buffer.rewind
98
+ @buffer.readlines[-1].to_s.chomp
99
+ end
100
+
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: librato-logreporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Sanders
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Provides a simple interface to log Librato metrics to your log files
31
+ in l2met format.
32
+ email:
33
+ - matt@librato.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - lib/librato/logreporter/configuration.rb
39
+ - lib/librato/logreporter/group.rb
40
+ - lib/librato/logreporter/version.rb
41
+ - lib/librato/logreporter.rb
42
+ - lib/librato-logreporter.rb
43
+ - LICENSE
44
+ - Rakefile
45
+ - README.md
46
+ - CHANGELOG.md
47
+ - test/test_helper.rb
48
+ - test/unit/logreporter/configuration_test.rb
49
+ - test/unit/logreporter/group_test.rb
50
+ - test/unit/logreporter_test.rb
51
+ homepage: https://github.com/librato/librato-logreporter
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ segments:
64
+ - 0
65
+ hash: -2188963699629568403
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ segments:
73
+ - 0
74
+ hash: -2188963699629568403
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.25
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Write Librato metrics to your logs with a convenient interface
81
+ test_files:
82
+ - test/test_helper.rb
83
+ - test/unit/logreporter/configuration_test.rb
84
+ - test/unit/logreporter/group_test.rb
85
+ - test/unit/logreporter_test.rb