nexaas-auditor 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +25 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +10 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +172 -0
- data/Rakefile +6 -0
- data/TODO.md +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nexaas/auditor/adapters/nunes.rb +31 -0
- data/lib/nexaas/auditor/audit_logger.rb +51 -0
- data/lib/nexaas/auditor/configuration.rb +41 -0
- data/lib/nexaas/auditor/logs_subscriber.rb +17 -0
- data/lib/nexaas/auditor/rails_subscriber.rb +15 -0
- data/lib/nexaas/auditor/statistics_tracker.rb +22 -0
- data/lib/nexaas/auditor/statistics_trackers/base.rb +52 -0
- data/lib/nexaas/auditor/statistics_trackers/log.rb +20 -0
- data/lib/nexaas/auditor/statistics_trackers/stathat.rb +40 -0
- data/lib/nexaas/auditor/stats_subscriber.rb +17 -0
- data/lib/nexaas/auditor/subscriber.rb +40 -0
- data/lib/nexaas/auditor/version.rb +5 -0
- data/lib/nexaas/auditor.rb +57 -0
- data/nexaas-auditor.gemspec +35 -0
- metadata +145 -0
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
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,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,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
|