librato-logreporter 0.1.0

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