nexaas-auditor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.5
5
+ - 2.3.1
6
+ before_install: gem install bundler -v 1.12.5
7
+ script: bundle exec rake spec
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at rodrigo@pittlandia.net. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nexaas-auditor.gemspec
4
+ gemspec
5
+
6
+ gem 'activesupport', '~> 4.2', require: false
7
+ gem 'stathat', '~> 0.1', require: false
8
+ gem 'nunes', '~> 0.4', require: false
9
+
10
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "RAILS_ENV=test RACK_ENV=test bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.("routing/#{m[1]}_routing"),
51
+ rspec.spec.("controllers/#{m[1]}_controller"),
52
+ rspec.spec.("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rodrigo Tassinari de Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # Nexaas::Auditor
2
+
3
+ [![Build Status](https://travis-ci.org/myfreecomm/nexaas-auditor.svg?branch=master)](https://travis-ci.org/myfreecomm/nexaas-auditor)
4
+ [![Test Coverage](https://codeclimate.com/github/myfreecomm/nexaas-auditor/badges/coverage.svg)](https://codeclimate.com/github/myfreecomm/nexaas-auditor/coverage)
5
+ [![Code Climate](https://codeclimate.com/github/myfreecomm/nexaas-auditor/badges/gpa.svg)](https://codeclimate.com/github/myfreecomm/nexaas-auditor)
6
+
7
+ Common **opnionated** code for audit logs and statistcs tracking for Rails apps, via [ActiveSupport instrumentation](http://edgeguides.rubyonrails.org/active_support_instrumentation.html). Used in production in a few [Nexaas](http://www.nexaas.com) systems.
8
+
9
+ This has been tested with Rails 4.2.x only so far. It probably works fine as well in Rails 4.1.x, but I'm not sure about Rails 3.x yet. It requires Ruby v2.2.3 at least (but we recommend using v2.3.x).
10
+
11
+ The audit log is created in a [logfmt](https://www.brandur.org/logfmt) format only for now. Support for more log formats is planned in the future.
12
+
13
+ Both the audit log and statistics tracking assume all instrumented events are named in a dot notation format, for example you could use `'app.users.login.sucess'` to instrument a successful user login event. The `'app.'` prefix is a suggestion to separate your bussiness-logic events from framework-specific (Rails) events, which will always have a `'rails.'` prefix, for example `'rails.action_controller.runtime.total'` for example.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'nexaas-auditor'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```
26
+ $ bundle
27
+ ```
28
+
29
+ Or install it yourself as:
30
+
31
+ ```
32
+ $ gem install nexaas-auditor
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ In a Rails initializer file such as `config/initializers/nexaas_auditor.rb`, put something like this:
38
+
39
+ ```ruby
40
+ require 'nexaas/auditor'
41
+
42
+ Nexaas::Auditor.configure do |config|
43
+ config.enabled = true
44
+ config.logger = Rails.logger
45
+
46
+ # use audit logging for bussiness-logic instrumented events
47
+ config.log_app_events = true
48
+
49
+ # use statistics tracking for bussiness-logic instrumented events
50
+ config.track_app_events = true
51
+
52
+ # use statistics tracking for default Rails instrumented events
53
+ # don't forget to add the 'nunes' gem to your Gemfile (we use Nunes to do the
54
+ # heavy lifting on the instrumented Rails events)
55
+ config.track_rails_events = true
56
+
57
+ # optionally, prepend statistics metric names with your app name. use this if
58
+ # you use the same statistics service (ie StatHat) for multiple apps.
59
+ config.statistics_namespace = 'myappname'
60
+
61
+ if Rails.env.production?
62
+ # use StatHat service in production only
63
+ # don't forget to add the 'stathat' gem to your Gemfile
64
+ config.statistics_service = 'stathat'
65
+ config.stathat_settings = {key: 'stathat-ez-key'}
66
+ else
67
+ # the 'log' service only writes the stats to the audit log instead of
68
+ # sending them to an external service.
69
+ config.statistics_service = 'log'
70
+ end
71
+ end
72
+
73
+ # setup all subscribers
74
+ Nexaas::Auditor.subscribe_all
75
+ ```
76
+
77
+ Then, create your loggers and statistic trackers in `app/loggers/` and `app/statistics/` for example, inheriting from `Nexaas::Auditor::LogSubscriber` and `Nexaas::Auditor::StatsSubscriber` respectively.
78
+
79
+ For example:
80
+
81
+ ```ruby
82
+ class UsersAppLogger < ::Nexaas::Auditor::LogSubscriber
83
+ # The namespace for events to subscribe to. In this example, subscribe to all
84
+ # events beggining with "app.users.".
85
+ def self.pattern
86
+ /\Aapp\.users\..+\Z/
87
+ end
88
+
89
+ # Called when an event with name == 'app.users.login.success' is received.
90
+ #
91
+ # name = the event name, 'app.users.login.success' in this case
92
+ # start = the time the event started
93
+ # finish = the time the event finished
94
+ # (tip: finish - start gives you the duration in seconds as a float)
95
+ # event_id = an unique id for the event
96
+ # payload = a hash of extra data the event may have supplied when instrumented
97
+ #
98
+ def log_event_app_users_login_success(name, start, finish, event_id, payload)
99
+ user_id = payload[:user_id]
100
+ # Do the actual loggging. The `:level` and `:measure` keys are required,
101
+ # anything else will be transformed in a key=value pair in the log string.
102
+ logger.log(level: :info, measure: name, user_id: user_id)
103
+ end
104
+ end
105
+
106
+ class UsersAppStatsTracker < ::Nexaas::Auditor::StatsSubscriber
107
+ # The namespace for events to subscribe to. In this example, subscribe to all
108
+ # events beggining with "app.users.".
109
+ def self.pattern
110
+ /\Aapp\.users\..+\Z/
111
+ end
112
+
113
+ # Called when an event with name == 'app.users.login.success' is received.
114
+ #
115
+ # name = the event name, 'app.users.login.success' in this case
116
+ # start = the time the event started
117
+ # finish = the time the event finished
118
+ # (tip: finish - start gives you the duration in seconds as a float)
119
+ # event_id = an unique id for the event
120
+ # payload = a hash of extra data the event may have supplied when instrumented
121
+ #
122
+ def track_event_app_users_login_success(name, start, finish, event_id, payload)
123
+ user_id = payload[:user_id]
124
+
125
+ # Do the actual statistic tracking.
126
+
127
+ # Use the `count` type to track event occurences or quantities. The `:metric`
128
+ # key is required (generally use the name or append something to the name).
129
+ # The `:value` should be an Integer. If `:value` is ommited it will be
130
+ # assumed a value of 1.
131
+ tracker.track_count(metric: name, value: 1)
132
+
133
+ # Use the `value` type to track event durations or amounts. The `:metric`
134
+ # key is required (generally use the name or append something to the name).
135
+ # The `:value` key is also required and should be an Integer, Float or Decimal.
136
+ duration = ((finish - start) * 1_000.0).round # to get the value in milliseconds
137
+ tracker.track_value(metric: "#{name}.duration", value: duration)
138
+ end
139
+ end
140
+ ```
141
+
142
+ Now all that is left is for you to instrument your code to fire the events above. Following the example of logging and tracking user logins, we might have this in a hipothetical `SessionsController` in your app:
143
+
144
+ ```ruby
145
+ class SessionsController < ApplicationController
146
+ def create
147
+ @user = User.authenticate(session_params)
148
+ if @user
149
+ Nexaas::Auditor.instrument('app.users.login.success', user_id: @user.id)
150
+ redirect_to root_path, success: "You are logged in!"
151
+ else
152
+ # do something else, show some error, etc
153
+ end
154
+ end
155
+ end
156
+ ```
157
+
158
+ ## Development
159
+
160
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
161
+
162
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
163
+
164
+ ## Contributing
165
+
166
+ Bug reports and pull requests are welcome on GitHub at https://github.com/myfreecomm/nexaas-auditor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
167
+
168
+ There is a list of planned features and improvements on the [TODO.md](https://github.com/myfreecomm/nexaas-auditor/blob/master/TODO.md) file, please read it before anything else if you want to help with nexaas-auditor development.
169
+
170
+ ## License
171
+
172
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,12 @@
1
+ ## TODO
2
+
3
+ Tasks yet to be done, in order of importance. Feel free to help out ;-)
4
+
5
+ - Only require [Nunes](https://github.com/jnunemaker/nunes) gem if the configuration says `track_rails_events = true`, and "fail fast" if `Nunes` is not loaded
6
+ - Only require [StatHat](https://github.com/patrickxb/stathat) gem if the configuration says `statistics_service = 'stathat'`, and "fail fast" if `StatHat` is not loaded
7
+ - Extract log formating logic out of Nexaas::Auditor::AuditLogger into a proper log formating class
8
+ - Add possibility of supporting more log formats (currently only logfmt format is supported)
9
+ - Add options to limit the metrics subscribed by default when `track_rails_events = true`; by default [Nunes](https://github.com/jnunemaker/nunes) sends **a lot** of metrics, some of them may not be so useful and end up "polluting" the statistics service (or, more importantly, cost unnecessary money if the service restricts the number of metrics created)
10
+ - Add more statistic services support ([Instrumental](https://instrumentalapp.com/), [self-hosted StatsD](https://github.com/etsy/statsd), [Librato](https://www.librato.com/), [DataDoc](https://www.datadoghq.com/), etc)
11
+ - Separate the audit logging part from the statistics tracking part, maybe in separate gems as well, but in a way that they could just as easily be all used togheter and share common code.
12
+ - Separate the bussiness-logic statistics tracking part from the Rails (Nunes) statistics tracking, maybe in separate gems as well, but in a way that they could just as easily be all used togheter and share common code.
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nexaas/auditor"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ require 'nunes'
2
+
3
+ module Nexaas
4
+ module Auditor
5
+ module Adapters
6
+
7
+ class Nunes < ::Nunes::Adapter
8
+
9
+ attr_reader :client
10
+
11
+ def initialize(client)
12
+ @client = client
13
+ end
14
+
15
+ def increment(metric, value=1)
16
+ client.track_count(prepare(metric), value)
17
+ end
18
+
19
+ def timing(metric, value)
20
+ client.track_value(prepare(metric), value)
21
+ end
22
+
23
+ def prepare(metric, replacement = Separator)
24
+ metric = "rails.#{metric}"
25
+ super
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module Nexaas
2
+ module Auditor
3
+ class AuditLogger
4
+
5
+ VALID_LEVELS = %w(degug info warn error fatal)
6
+
7
+ attr_accessor :logger
8
+
9
+ def initialize(logger=nil)
10
+ @logger = logger
11
+ end
12
+
13
+ def logger
14
+ @logger ||= Nexaas::Auditor.configuration.logger
15
+ end
16
+
17
+ def log(options={})
18
+ level = options.delete(:level)
19
+ raise ArgumentError, "required key `:level` not found" if level.nil?
20
+ raise ArgumentError, "key `:level` is invalid: '#{level}'" unless VALID_LEVELS.include?(level.to_s)
21
+ check_message!(options)
22
+ safe_call { logger.send(level, to_message(options)) }
23
+ end
24
+
25
+ private
26
+
27
+ def to_message(options)
28
+ # TODO move this logic to a dedicated class and add more tests
29
+ options.inject(['audit_log=true']) do |array, (key,value)|
30
+ value = value.respond_to?(:iso8601) ? value.iso8601 : value
31
+ array << "#{key}=#{value.to_s.strip.gsub(/\s/, '-')}"
32
+ array
33
+ end.join(' ')
34
+ end
35
+
36
+ def safe_call(&block)
37
+ begin
38
+ yield(block)
39
+ rescue => exception
40
+ logger.fatal("role=audit_logger class=#{self.class} measure=errors.unable_to_log exception=#{exception.class}")
41
+ end
42
+ end
43
+
44
+ def check_message!(options)
45
+ measure = options[:measure] || options['measure']
46
+ raise ArgumentError, "required key 'measure' not found or empty" if measure.to_s == ''
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,41 @@
1
+ require 'logger'
2
+
3
+ module Nexaas
4
+ module Auditor
5
+ class Configuration
6
+
7
+ attr_accessor :enabled
8
+ attr_accessor :logger
9
+
10
+ attr_accessor :log_app_events
11
+ attr_accessor :track_app_events
12
+ attr_accessor :track_rails_events
13
+
14
+ attr_accessor :statistics_namespace
15
+ attr_accessor :statistics_service
16
+
17
+ attr_accessor :stathat_settings
18
+
19
+ def initialize
20
+ @enabled = nil # set to true when configure is called
21
+ @logger = nil
22
+ @log_app_events = false
23
+ @track_app_events = false
24
+ @track_rails_events = false
25
+ @statistics_namespace = nil
26
+ @statistics_service = 'log' # or 'stathat'
27
+ @stathat_settings = {key: nil}
28
+ end
29
+
30
+ # allow params to be read like a hash
31
+ def [](option)
32
+ send(option)
33
+ end
34
+
35
+ def logger
36
+ @logger ||= ::Logger.new(STDERR)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module Nexaas
2
+ module Auditor
3
+ class LogsSubscriber < Subscriber
4
+
5
+ def logger
6
+ Nexaas::Auditor.logger
7
+ end
8
+
9
+ private
10
+
11
+ def event_method_name(name)
12
+ "log_event_#{name.downcase.gsub('.', '_')}"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Nexaas
2
+ module Auditor
3
+ class RailsSubscriber
4
+
5
+ def self.subscribe_all
6
+ ::Nunes.subscribe(nunes_statistics_wrapper)
7
+ end
8
+
9
+ def self.nunes_statistics_wrapper
10
+ Nexaas::Auditor::Adapters::Nunes.new(Nexaas::Auditor.tracker)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Nexaas
2
+ module Auditor
3
+ class StatisticsTracker
4
+
5
+ VALID_SERVICES = %w(log stathat)
6
+
7
+ def self.setup(service, namespace=nil)
8
+ raise ArgumentError,
9
+ "unknown statistics service '#{service}'" unless VALID_SERVICES.include?(service)
10
+ tracker = if service == 'stathat'
11
+ key = Nexaas::Auditor.configuration.stathat_settings[:key]
12
+ StatisticsTrackers::Stathat.new(key, namespace)
13
+ else
14
+ logger = Nexaas::Auditor.configuration.logger
15
+ StatisticsTrackers::Log.new(logger, namespace)
16
+ end
17
+ tracker
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,52 @@
1
+ module Nexaas
2
+ module Auditor
3
+ module StatisticsTrackers
4
+ class Base
5
+
6
+ def track_count(name, value=nil)
7
+ track(:count, name, value)
8
+ end
9
+
10
+ def track_value(name, value)
11
+ track(:value, name, value)
12
+ end
13
+
14
+ private
15
+
16
+ def track(type, name, value)
17
+ value ||= 1 if type == :count
18
+ validate_value!(value, type)
19
+ full_name = full_metric_name(name)
20
+ validate_name!(name, full_name)
21
+
22
+ send_track(type, full_name, value)
23
+ end
24
+
25
+ def send_track(type, full_name, value)
26
+ raise "Not Implemented, override in subclass."
27
+ end
28
+
29
+ def full_metric_name(name)
30
+ if @namespace.to_s == ''
31
+ name
32
+ else
33
+ "#{@namespace}.#{name}"
34
+ end
35
+ end
36
+
37
+ # allowed chars: a-z, A-Z, `.`, `-` and `_`
38
+ def validate_name!(name, full_name)
39
+ if (name.to_s == '') || !(full_name =~ /\A[a-zA-Z0-9\.\-_]+\Z/)
40
+ raise ArgumentError, "unsuported metric name: '#{name}'"
41
+ end
42
+ end
43
+
44
+ # allowed values: Numeric (Integer, Float, Decimal, etc)
45
+ def validate_value!(value, type)
46
+ raise ArgumentError, "unsuported value: #{value} (#{value.class})" unless value.is_a?(Numeric)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ module Nexaas
2
+ module Auditor
3
+ module StatisticsTrackers
4
+ class Log < Base
5
+
6
+ def initialize(logger, namespace=nil)
7
+ @logger = logger || Nexaas::Auditor.configuration.logger
8
+ @namespace = namespace.to_s
9
+ end
10
+
11
+ private
12
+
13
+ def send_track(type, full_name, value)
14
+ @logger.info("[#{self.class}] type=#{type} metric=#{full_name} value=#{value}")
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'stathat'
2
+
3
+ module Nexaas
4
+ module Auditor
5
+ module StatisticsTrackers
6
+ class Stathat < Base
7
+
8
+ attr_reader :logger
9
+
10
+ def initialize(key, namespace=nil)
11
+ @key = key.to_s
12
+ @namespace = namespace.to_s
13
+ @logger = Nexaas::Auditor.configuration.logger
14
+ raise ArgumentError, "required Stathat EZ Key not found" if @key == ''
15
+ end
16
+
17
+ private
18
+
19
+ # Regex to determine if the current process is a short lived kind of
20
+ # script.
21
+ ShortRunningProcessNamesRegex = /sidekiq|resque|delayed|rspec|rake/i
22
+
23
+ def short_running_process?
24
+ $0 =~ ShortRunningProcessNamesRegex
25
+ end
26
+
27
+ def send_track(type, full_name, value)
28
+ # the default StatHat::API Ruby methods are asynchronous. If you are
29
+ # using this gem in a script that is short-lived, you can use
30
+ # StatHat::SyncAPI to make synchronous calls to StatHat.
31
+ klass = (short_running_process? ? ::StatHat::SyncAPI : ::StatHat::API)
32
+
33
+ logger.debug("[#{self.class}] calling #{klass}.ez_post_#{type}('#{full_name}', '#{@key}', #{value})")
34
+ klass.send("ez_post_#{type}", full_name, @key, value)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end