yodeler 0.0.10 → 0.1.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +33 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +16 -0
  8. data/README.md +158 -67
  9. data/Rakefile +4 -21
  10. data/bin/console +14 -0
  11. data/bin/setup +7 -0
  12. data/lib/yodeler/adapter_not_registered_error.rb +14 -0
  13. data/lib/yodeler/adapters/http_adapter.rb +51 -0
  14. data/lib/yodeler/adapters/memory_adapter.rb +23 -0
  15. data/lib/yodeler/adapters/void_adapter.rb +10 -0
  16. data/lib/yodeler/client.rb +166 -0
  17. data/lib/yodeler/duplicate_endpoint_name_error.rb +9 -0
  18. data/lib/yodeler/endpoint.rb +35 -0
  19. data/lib/yodeler/metric.rb +36 -0
  20. data/lib/yodeler/version.rb +1 -1
  21. data/lib/yodeler.rb +41 -106
  22. data/yodeler.gemspec +29 -0
  23. metadata +44 -102
  24. data/MIT-LICENSE +0 -20
  25. data/app/assets/javascripts/yodeler/application.js +0 -13
  26. data/app/assets/stylesheets/yodeler/application.css +0 -13
  27. data/app/controllers/yodeler/application_controller.rb +0 -4
  28. data/app/helpers/yodeler/application_helper.rb +0 -4
  29. data/app/views/layouts/yodeler/application.html.erb +0 -14
  30. data/config/routes.rb +0 -2
  31. data/lib/generators/yodeler/USAGE +0 -2
  32. data/lib/generators/yodeler/install_generator.rb +0 -23
  33. data/lib/generators/yodeler/templates/initializer.rb +0 -7
  34. data/lib/generators/yodeler/templates/migration.rb +0 -50
  35. data/lib/tasks/yodeler_tasks.rake +0 -4
  36. data/lib/yodeler/configuration.rb +0 -12
  37. data/lib/yodeler/engine.rb +0 -10
  38. data/lib/yodeler/listens_to_yodeler.rb +0 -32
  39. data/lib/yodeler/models/event.rb +0 -17
  40. data/lib/yodeler/models/event_type.rb +0 -6
  41. data/lib/yodeler/models/event_types/base.rb +0 -59
  42. data/lib/yodeler/models/event_types/noop_event_type.rb +0 -12
  43. data/lib/yodeler/models/notification.rb +0 -30
  44. data/lib/yodeler/models/subscription.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e61910148974128f8852d2b671187a588150737
4
- data.tar.gz: 7b22137cdea54c279888833b91729b8eb85c7ae4
3
+ metadata.gz: fa2389240d853b1e917ca4741cd9d8ca0c9bde03
4
+ data.tar.gz: 43aae361bf5632fea1641a3914f2b5390759cffe
5
5
  SHA512:
6
- metadata.gz: 7dda2288ceb0a4e29d9f9927faefbb113f124c754a6e77318c29f980aa178ede37f3362b7e8b153b8f91f502fa0d7eb32193aee92665f484f831dc6184094a7a
7
- data.tar.gz: 88e3857b1b847f493d2fe8609d0e143d4f29f8cff66885ac68144e078dbbe8fbee8f95d6ae545e0b9eca63b173a1d6f29a2b6948ed8954037bef0b45bb2b2738
6
+ metadata.gz: a081b0689cd5e2f0f2a3c192a54832459a0935f020aa7392a23ec3e4d9771fdff89280b120e57219e14507c597f38266ce71ae6150f67118ff7459fb42cf33be
7
+ data.tar.gz: 7eb2cd49a592a382610031303a10a1eeb4313c2893aa1c5d40e48ce0d7446dd04af2cd119aefaf5d2a5860f21f6252e4fa3401f6ef804f57f4b41ce4c6b9f1a7
data/.codeclimate.yml ADDED
@@ -0,0 +1,33 @@
1
+ ---
2
+ engines:
3
+ brakeman:
4
+ enabled: false
5
+ bundler-audit:
6
+ enabled: true
7
+ duplication:
8
+ enabled: true
9
+ config:
10
+ languages:
11
+ - ruby
12
+ fixme:
13
+ enabled: true
14
+ rubocop:
15
+ enabled: true
16
+ ratings:
17
+ paths:
18
+ - Gemfile.lock
19
+ - "**.erb"
20
+ - "**.haml"
21
+ - "**.rb"
22
+ - "**.rhtml"
23
+ - "**.slim"
24
+ - "**.inc"
25
+ - "**.js"
26
+ - "**.jsx"
27
+ - "**.module"
28
+ - "**.php"
29
+ - "**.py"
30
+ exclude_paths:
31
+ - config/**/*
32
+ - db/**/*
33
+ - spec/**/*
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.6
4
+ - 2.2.3
5
+ - 2.3.0
6
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yodeler.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # Feel free to open issues for suggestions and improvements
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/README.md CHANGED
@@ -1,106 +1,197 @@
1
- Yodeler
2
- =======
1
+ # Yodeler
3
2
 
4
- Spouting noise to whoever is listening.
3
+ A generic instrumentation library thats supports reporting to multiple endpoints via pluggable backend adapters.
5
4
 
6
- Yodeler is an easy way to notify users of different events that occur in your app.
5
+ Spoutin' off noise to whoever is listening.
7
6
 
8
- * Notify your marketing team on the 10,000 sign-up.
9
- * Notify user's when their profile has been viewed
10
- * Notify your dev team about events that are not quite exception worthy, but you don't want to dig through a bunch of logs to find.
7
+ ![Build Status](https://travis-ci.org/coryodaniel/yodeler.svg "Build Status")
8
+ [![Test Coverage](https://codeclimate.com/github/coryodaniel/yodeler/badges/coverage.svg)](https://codeclimate.com/github/coryodaniel/yodeler/coverage)
9
+ [![Code Climate](https://codeclimate.com/github/coryodaniel/yodeler/badges/gpa.svg)](https://codeclimate.com/github/coryodaniel/yodeler)
11
10
 
11
+ ## Installation
12
12
 
13
- Features
14
- ========
13
+ Add this line to your application's Gemfile:
15
14
 
16
- * store notifications via ActiveRecord
17
- * benchmark event duration
18
- * i18n support
19
- * flexible payloads
20
-
21
-
22
- What's wrong with XMPP?
23
- =======================
15
+ ```ruby
16
+ gem 'yodeler', '~>0.1.1'
17
+ ```
24
18
 
25
- Why don't you fly to Walmart on a rocket?
19
+ And then execute:
26
20
 
27
- Don't even say it's because you don't have a rocket.
21
+ $ bundle
28
22
 
29
- Why don't you kill ants with a bazooka?
23
+ Or install it yourself as:
30
24
 
31
- Nevermind, you get it.
25
+ $ gem install yodeler
32
26
 
27
+ ## Usage
33
28
 
34
- Usage
35
- =====
29
+ ### Configuration
36
30
 
31
+ #### Single endpoint
32
+ In ```config/initializers/yodeler.rb```
37
33
  ```ruby
38
- # in config/initializers/yodeler.rb
39
- Yodeler.register :another_thousand_users
40
-
41
- Yodeler.register :view_user do
42
- # Default states are unread: 0, read: 1
43
- config.states = {
44
- unread: 0,
45
- read: 1,
46
- ignored: 2
47
- }
34
+ Yodeler.configure do |client|
35
+ # if no endpoint name is given, it defaults to :default
36
+ client.adapter(:http) do |http|
37
+ http.path = '/events'
38
+ http.host = 'example.com'
39
+ # http.port = 80
40
+ # http.use_ssl = false
41
+ # http.default_params = {}
42
+ end
48
43
  end
49
44
  ```
50
45
 
46
+ #### Multiple Endpoints
47
+ In ```config/initializers/yodeler.rb```
51
48
  ```ruby
52
- # in a controller or Anywhere you want to hear some noise
53
- class UserController < ApplicationController
54
- around_action :track_user_view, only: :show
55
-
56
- def show
57
- # do your User#show as normal
49
+
50
+ Yodeler.configure do |client|
51
+ client.endpoint(:sales_reporting).adapter(:http) do |http|
52
+ http.path = '/events'
53
+ http.host = 'sales.example.com'
58
54
  end
59
55
 
60
- private
56
+ client.endpoint(:devops_reporting).adapter(:http) do |http|
57
+ http.path = '/events'
58
+ http.host = 'devops.example.com'
59
+ end
61
60
 
62
- def track_user_view
63
- # This will also benchmark the action since it is in a block
64
- Yodeler.dispatch :view_user, {viewer_id: 3, viewee_id: 23} do |payload|
65
- yield #yield the action dispatch, the payload is available
61
+ # by default, the client dispatches to the first registered endpoint
62
+ # you can change it to a different one
63
+ # Alternatively you can dispatch to a set of endpoints when dispatching a metric
64
+ # Yodeler.gauge('users.count', 35, to: [:sales_reporting, :devops_reporting])
65
+ client.default_endpoint_name = :devops_reporting
66
+ end
67
+ ```
68
+
69
+ #### Full Configuration Example
70
+ In ```config/yodeler.yml```
71
+ ```yaml
72
+ development:
73
+ auth_token: SOSECUREZ
74
+ sales_reporting:
75
+ host: localhost
76
+ port: 3030
77
+ devops_reporting:
78
+ host: localhost
79
+ port: 3031
80
+ ```
81
+
82
+ In ```config/initializers/yodeler.rb```
83
+ ```ruby
84
+ config = YAML.load(File.read("./config/yodeler.yml"))[Rails.env]
85
+
86
+ Yodeler.configure do |client|
87
+ client.endpoint(:sales_reporting).adapter(:http) do |http|
88
+ http.path = '/events'
89
+ http.host = config[:sales_reporting][:host]
90
+ http.port = config[:sales_reporting][:port]
91
+ http.default_params = {
92
+ auth_token: config[:auth_token]
93
+ }
94
+ end
95
+
96
+ client.endpoint(:devops_reporting).adapter(:http) do |http|
97
+ http.path = '/events'
98
+ http.host = config[:devops_reporting][:host]
99
+ http.port = config[:devops_reporting][:port]
100
+ http.default_params = {
101
+ auth_token: config[:auth_token]
102
+ }
103
+
104
+ # Overwrite the default http dispatcher or overwrite an individual metric dispatcher
105
+ # http.handle(:gauge){|url, metric, default_params| ... something cool ... }
106
+ http.handle(:default) do |url, metric, default_params|
107
+ # This is the default handler definition, but you could change it
108
+ HTTP.post(url, json: default_params.merge(metric.to_hash))
66
109
  end
67
110
  end
111
+
112
+ client.default_endpoint_name = :devops_reporting
68
113
  end
114
+
69
115
  ```
70
116
 
117
+ #### [Dashing Example](https://github.com/shopify/dashing)
71
118
  ```ruby
72
- # off in some scorned rails-observer
73
- class UserObserver
74
- observers :user
75
-
76
- def after_create
77
- # let the marketing know another thousandth user has registered!
78
- if (User.count % 1000).zero?
79
- Yodeler.dispatch :another_thousand_users, {give_him_a_prize: @user.id}
80
- end
119
+ Yodeler.configure do |client|
120
+ client.endpoint(:karma_widget).adapter(:http) do |http|
121
+ http.path = '/widgets/karma'
122
+ http.host = 'localhost'
123
+ http.default_params = {
124
+ auth_token: config[:auth_token]
125
+ }
126
+ end
127
+
128
+ client.endpoint(:user_count_widget).adapter(:http) do |http|
129
+ http.path = '/widgets/user_count'
130
+ http.host = 'localhost'
131
+ http.default_params = {
132
+ auth_token: config[:auth_token]
133
+ }
81
134
  end
82
135
  end
83
136
  ```
84
137
 
138
+ ### Publishing Metrics and Events
85
139
 
86
- Getting around the objects
87
- ==========================
140
+ #### All instrumentation methods support an options hash
88
141
 
142
+ * :prefix - [~String] :prefix your metric/event names
143
+ * :tags - [Array<String,Symbol>, String, Symbol] :tags ([]) array of tags to apply to metric/event
144
+ * :sample_rate - [Float] :sample_rate (1.0) The sample rate to use
145
+ * :to - [Array<Symbol>, Symbol] :to array of endpoint names to send the metric/event to. If not set will send to Yodeler::Client#default_endpoint_name
146
+
147
+ #### Gauge
89
148
  ```ruby
90
- @event.subscriptions #=> Array<Yodeler::Subscription> all subscriber subscriptions
91
- @event.notifications #=> Array<Yodeler::Notifications>
149
+ Yodeler.gauge 'user.count', 35
150
+ Yodeler.gauge 'user.count', 35, prefix: 'test'
151
+ Yodeler.gauge 'user.count', 35, sample_rate: 0.5
152
+ Yodeler.gauge 'user.count', 35, prefix: 'test', tags: ['cool']
153
+ Yodeler.gauge 'user.count', 35, to: [:devops_reporting, :sales_reporting]
154
+ ```
92
155
 
93
- @notification.subscriber #=> Your 'subscriber' class, delegated to #subscription
94
- @notification.subscription.subscriber
95
- @notification.event #=> The subscriber to event, delegated to #subscription
96
- @notification.message #=> i18n message interpolated w/ the payload
156
+ #### Increment
157
+ ```ruby
158
+ Yodeler.increment 'users.count'
159
+ Yodeler.increment 'users.count', to: [:devops_reporting, :sales_reporting]
160
+ Yodeler.increment 'revenue', 10_000
161
+ Yodeler.increment 'revenue', 10_000, to: [:devops_reporting, :sales_reporting]
162
+
163
+ ```
97
164
 
98
- @subscription.subscriber #=> the 'subscriber' subscribed to this event
99
- @subscription.notifications #=> all notifications of this event type for this subscriber
100
- @subscription.event
165
+ #### Timing
166
+ ```ruby
167
+ Yodeler.timing('eat.sandwich', {prefix: :test}) do
168
+ user.eat(sandwich)
169
+ end #=> returns result of block
101
170
 
171
+ Yodeler.timing 'eat.sandwich', 250 #in ms
172
+ Yodeler.timing 'eat.sandwich', 250, to: [:devops_reporting, :sales_reporting]
102
173
  ```
103
174
 
104
- TODO
105
- =======
106
- * Pluggable back-end, redis, obvi
175
+ #### Event
176
+ ```ruby
177
+ wizz_bang = {name: 'Wizz Bang 3000', image_url: 'http://example.com/wizzbang.jpg'}
178
+ Yodeler.publish 'product.sold', wizz_bang
179
+ Yodeler.publish 'product.sold', wizz_bang, prefix: 'ecommerce'
180
+ Yodeler.publish 'product.sold', wizz_bang, sample_rate: 0.25
181
+ Yodeler.publish 'product.sold', wizz_bang, to: [:devops_reporting, :sales_reporting]
182
+ ```
183
+
184
+ ## Development
185
+
186
+ 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.
187
+
188
+ 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).
189
+
190
+ ## Contributing
191
+
192
+ Bug reports and pull requests are welcome on GitHub at https://github.com/coryodaniel/yodeler.
193
+
194
+ ## TODOs
195
+ * [ ] Custom adapter documentation
196
+ * [ ] Client#format_options -> Metric.format_options
197
+ * [ ] Client#default_endpoint_name accept array of names
data/Rakefile CHANGED
@@ -1,23 +1,6 @@
1
- begin
2
- require 'bundler/setup'
3
- rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
- end
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
6
3
 
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Yodeler'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.rdoc')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
- APP_RAKEFILE = File.expand_path("../spec/test_app/Rakefile", __FILE__)
18
- load 'rails/tasks/engine.rake'
19
-
20
-
21
-
22
- Bundler::GemHelper.install_tasks
4
+ RSpec::Core::RakeTask.new(:spec)
23
5
 
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "yodeler"
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,14 @@
1
+ module Yodeler
2
+ class AdapterNotRegisteredError < StandardError
3
+ attr_reader :name
4
+ def initialize(name:)
5
+ @name = name
6
+ msg = [
7
+ "No Yodeler Adapter registed for: ':#{name}'",
8
+ "Register an adapter with:",
9
+ "Yodeler.register_adapter(:#{name}, CustomAdapterClass)"
10
+ ].join("\n")
11
+ super(msg)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ require 'http'
2
+
3
+ module Yodeler::Adapters
4
+ class HttpAdapter
5
+ attr_accessor :host
6
+ attr_accessor :port
7
+ attr_accessor :path
8
+ attr_accessor :use_ssl
9
+ attr_accessor :default_params
10
+
11
+ def initialize(host=nil, port:nil, path:nil, use_ssl:false, params:{})
12
+ @host = host
13
+ @port = port
14
+ @path = path
15
+ @use_ssl = use_ssl
16
+ @default_params = params
17
+ @handlers = {}
18
+
19
+ handle(:default) do |url, metric, default_params|
20
+ HTTP.post(url, json: default_params.merge(metric.to_hash))
21
+ end
22
+ end
23
+
24
+ def handle(type, &block)
25
+ @handlers[type] = block
26
+ end
27
+
28
+ def dispatch(metric)
29
+ (@handlers[metric.type] || @handlers[:default]).call(url, metric, default_params)
30
+ end
31
+
32
+ def url
33
+ "#{protocol}://#{host_with_port}#{path}"
34
+ end
35
+
36
+ private
37
+ def host_with_port
38
+ if port
39
+ "#{host}:#{port}"
40
+ else
41
+ host
42
+ end
43
+ end
44
+
45
+ def protocol
46
+ use_ssl ? :https : :http
47
+ end
48
+
49
+ Yodeler.register_adapter(:http, self)
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ module Yodeler::Adapters
2
+ class MemoryAdapter
3
+ attr_reader :queue
4
+ attr_accessor :max_queue_size
5
+
6
+ def initialize
7
+ @max_queue_size = 1000
8
+ flush!
9
+ end
10
+
11
+ def flush!
12
+ @queue = []
13
+ end
14
+
15
+ def dispatch(metric)
16
+ @queue << metric
17
+ @queue.shift if @queue.length > @max_queue_size
18
+ metric
19
+ end
20
+
21
+ Yodeler.register_adapter(:memory, self)
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ module Yodeler::Adapters
2
+ class VoidAdapter
3
+
4
+ def dispatch(*)
5
+ #noop
6
+ end
7
+
8
+ Yodeler.register_adapter(:void, self)
9
+ end
10
+ end
@@ -0,0 +1,166 @@
1
+ require 'socket'
2
+
3
+ module Yodeler
4
+ class Client
5
+ attr_accessor :default_endpoint_name
6
+ attr_accessor :default_prefix
7
+ attr_accessor :default_sample_rate
8
+ attr_reader :endpoints
9
+
10
+ def initialize
11
+ @endpoints = {}
12
+ @default_sample_rate = 1.0
13
+ @default_prefix = nil
14
+ @hostname = Socket.gethostname
15
+ end
16
+
17
+ # Register a new endpoint
18
+ #
19
+ # @param [Symbol|String] name of endpoint, must be unique
20
+ # @return [Yodeler::Endpoint]
21
+ def endpoint(name=:default, &block)
22
+ raise DuplicateEndpointNameError.new(name: name) if @endpoints[name]
23
+ @default_endpoint_name ||= name
24
+ @endpoints[name] = Endpoint.new(name,&block)
25
+ end
26
+
27
+ # Get the default endpoint
28
+ #
29
+ # @return [Yodeler::Endpoint] Get the default endpoint
30
+ def default_endpoint
31
+ @endpoints[default_endpoint_name]
32
+ end
33
+
34
+ # Syntax sugar for creating the default endpoint and set the adapter
35
+ #
36
+ # Useful if you just have one endpoint and don't care about its name
37
+ # like during testing or for simple metric reporting scenarios
38
+ # ... is this useful or is it a big ol' booger?
39
+ #
40
+ # @param [Symbol] name registered adapter name
41
+ # @param [Type] &block configuration for adapter
42
+ # @return [~Yodeler::Adapters::Base] the adapter
43
+ def adapter(name ,&block)
44
+ endpoint() if @endpoints.empty?
45
+ default_endpoint.use(name, &block)
46
+ end
47
+
48
+ # Set a gauge
49
+ #
50
+ # @example
51
+ # client.gauge('users.count', 20_000_000)
52
+ # client.gauge('users.count', 20_000_000, { tags: %w(something) })
53
+ #
54
+ # @param [~String] name of the metric
55
+ # @param [~Fixnum] value of the metric
56
+ # @param [Hash] opts={} Examples {#format_options}
57
+ # @return [Yodeler::Metric, nil] the dispatched metric, nil if not sampled
58
+ def gauge(name, value, opts={})
59
+ dispatch(:gauge, name, value, opts)
60
+ end
61
+
62
+ # Increment a counter
63
+ #
64
+ # @example
65
+ # client.increment 'user.signup'
66
+ # client.increment 'user.signup', {}
67
+ # client.increment 'user.signup', 1, {}
68
+ #
69
+ # @param [~String] name of the metric
70
+ # @param [~Fixnum] value=1 of the metric
71
+ # @param [Hash] opts={} Examples {#format_options}
72
+ # @return [Yodeler::Metric, nil] the dispatched metric, nil if not sampled
73
+ def increment(name, value=1, opts={})
74
+ if value.kind_of?(Hash)
75
+ opts = value
76
+ value = 1
77
+ end
78
+ dispatch(:increment, name, value, opts)
79
+ end
80
+
81
+ # Measure how long something takes
82
+ #
83
+ # @example
84
+ # client.timing 'eat.sandwich', 250
85
+ # client.timing('eat.pizza') do
86
+ # user.eat(pizza) #=> THAT WAS QUICK FATSO!
87
+ # end
88
+ #
89
+ # @param [~String] name of the metric
90
+ # @param [~Fixnum] value time in ms
91
+ # @param [Hash] opts={} Examples {#format_options}
92
+ # @return [Yodeler::Metric, nil, Object]
93
+ # the dispatched metric, nil if not sampled
94
+ # if a block is given the result of the block is returned
95
+ def timing(name, value=nil, opts={})
96
+ if value.kind_of?(Hash)
97
+ opts = value
98
+ value = nil
99
+ end
100
+
101
+ _retval = nil
102
+
103
+ if block_given?
104
+ start = Time.now
105
+ _retval = yield
106
+ value = Time.now - start
107
+ end
108
+
109
+ metric = dispatch(:timing, name, value, opts)
110
+ _retval || metric
111
+ end
112
+
113
+ # Publish an event
114
+ #
115
+ # @example
116
+ # client.publish('item.sold', purchase.to_json)
117
+ # client.publish('user.sign_up', {name: user.name, avatar: user.image_url})
118
+ #
119
+ # @param [~String] name of the metric
120
+ # @param [~Hash] value of the metric
121
+ # @param [Hash] opts={} Examples {#format_options}
122
+ # @return [Yodeler::Metric, nil] the dispatched metric, nil if not sampled
123
+ def publish(name, payload, opts={})
124
+ dispatch(:event, name, payload, opts)
125
+ end
126
+
127
+ # Formats/Defaults metric options
128
+ #
129
+ # @param [Hash] opts metric options
130
+ # @option opts [Array<String,Symbol>, String, Symbol] :tags ([]) array of tags to apply to metric/event
131
+ # @option opts [Float] :sample_rate (1.0) The sample rate to use
132
+ # @option opts [Array<Symbol>, Symbol] :to array of endpoint names to send the metric to. If not set will send to {Yodeler::Client#default_endpoint_name}
133
+ # @return [Hash] formatted, defaulted options
134
+ def format_options(opts)
135
+ endpoint_names = opts.delete(:to) || [default_endpoint_name]
136
+ tags = opts.delete(:tags)
137
+ prefix = opts.delete(:prefix) || default_prefix
138
+
139
+ {
140
+ prefix: prefix,
141
+ to: [endpoint_names].flatten.compact,
142
+ sample_rate: opts.delete(:sample_rate) || default_sample_rate,
143
+ tags: [tags].flatten.compact,
144
+ hostname: @hostname
145
+ }
146
+ end
147
+
148
+ private
149
+
150
+ def dispatch(type, name, value, opts)
151
+ opts = format_options(opts)
152
+ destinations = opts.delete(:to)
153
+ metric = Metric.new(type, name, value, opts)
154
+
155
+ return nil unless metric.sample?
156
+
157
+ destinations.each do |endpoint_name|
158
+ if @endpoints[endpoint_name]
159
+ @endpoints[endpoint_name].adapter.dispatch(metric)
160
+ end
161
+ end
162
+
163
+ metric
164
+ end
165
+ end
166
+ end