camayoc 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ ## 0.2.0
2
+
3
+ * Added the Camayoc::Stats#event method to do general event logging
4
+ * Refactored handler event propagation; handlers now implement an event(stat_event) method instead of count and timing
5
+ * Logging and IO handler formatter Procs now take a single StatEvent as an argument
6
+
7
+ ## 0.1.0
8
+
9
+ * Initial release
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  Overview
2
2
  ========
3
- Camayoc is a flexible way to keep track of application stats. Inspired by
4
- various logging frameworks (especially Log4r) it makes it easy to send stats
5
- information to multiple destinations via the use of Handlers. Right out of the
6
- box, it supports sending stats to the following:
3
+ Camayoc is a flexible way to keep track of application stats and events.
4
+ Inspired by various logging frameworks (especially Log4r) it makes it easy to
5
+ send stats information to multiple destinations via the use of Handlers. Right
6
+ out of the box, it supports sending stats to the following:
7
7
 
8
8
  * A [statsd daemon](https://github.com/etsy/statsd/) for ridiculously easy [Graphite](http://graphite.wikidot.com/) stats
9
9
  * An in-memory hash for in-process stats
@@ -12,13 +12,25 @@ box, it supports sending stats to the following:
12
12
 
13
13
  Philosophy
14
14
  ----------
15
- Application stats are critical. But even critical things get ignored if they're
16
- hard (believe me, we know). Stats should be easy:
15
+ Keeping track of what goes on in your application is critical. But even
16
+ critical things get ignored if they're hard (believe me, we know). Camayoc's aim
17
+ is to make logging events and capturing stats easy:
17
18
 
18
- * Collecting stats should take just one line of code
19
- * All stat collection should be fire-and-forget with no error handling required
20
- * Organizing stats should be easy
21
- * There should be essentially no performance cost to collecting stats
19
+ * Collecting this data should take just one line of code
20
+ * All data collection should be fire-and-forget with no error handling required
21
+ * Organizing stats and events should be easy
22
+ * There should be essentially no performance cost to collecting this data
23
+
24
+ Events vs Stats
25
+ ---------------
26
+ In general, events are things that happen in your application that you care
27
+ about. There are two ways to keep track of events:
28
+
29
+ * Keep summary stats about event occurances around (counts, etc)
30
+ * Keep a detailed record of events that occur in a some sort of log or collection
31
+
32
+ Through its use of flexible handlers, Camayoc makes it possilbe to do one or
33
+ both of these through a single simple interface.
22
34
 
23
35
  Examples
24
36
  ==========
@@ -96,6 +108,24 @@ There are other options as well like :if and :unless that take Procs that can
96
108
  be executed to determine if a metric should be sent to the specified handler.
97
109
  See Camayoc::Handlers::Filter for more.
98
110
 
111
+ Event Logging
112
+ -------------
113
+ Sometimes you may want to keep a detailed log of events that occur instead of
114
+ summarizing then via counts. Below is an example of logging event data in a
115
+ space-delimited format to an IO stream:
116
+
117
+ event_log = Camayoc["events"]
118
+ fmt = Proc.new do |event|
119
+ ([event.ns_stat, Time.now.to_i] + Array(event.value)).join(" ")
120
+ end
121
+ event_log.add(Camayoc::Handlers::IO.new(STDOUT,:formatter=>fmt))
122
+
123
+ event_log.event("foo",["bar","baz"])
124
+
125
+ This will produce the following on stdout:
126
+
127
+ events:foo 1321564751 bar baz
128
+
99
129
  Available Handlers
100
130
  ==================
101
131
  Statsd
@@ -105,6 +135,12 @@ Class: Camayoc::Handlers::Statsd
105
135
  This handler sends data to the statd daemon for use in graphite. If you can get
106
136
  graphite and statsd going, then you'll get pretty graphs.
107
137
 
138
+ This handler does not support logging details about events since this isn't
139
+ really what statsd and graphite are for. Any calls to the event method will be
140
+ treated as counts in statsd. If the event value can be converted to an Integer
141
+ using Ruby's Integer method, then it will be sent as the count. Otherwise, a
142
+ value of 1 will be sent.
143
+
108
144
  Memory
109
145
  ------
110
146
  Class: Camayoc::Handlers::Memory
@@ -112,6 +148,10 @@ Class: Camayoc::Handlers::Memory
112
148
  Stores counts and timing data in an in-memory hash. This might be handy for
113
149
  storing some temporary in-process stats.
114
150
 
151
+ If you use this handler for event logging, data will be stored in an in-memory
152
+ array with a configurable max size. If that size is exceeded, older events will
153
+ be evicted to make room for new events.
154
+
115
155
  Logger
116
156
  ------
117
157
  Class: Camayoc::Handlers::Logger
@@ -120,6 +160,9 @@ Writes out stat values and a timestamp to a logger instance for later analysis.
120
160
  The format and method called on the logger can be controlled by constructor
121
161
  arguments. See the class for details.
122
162
 
163
+ This handler is best for event logging, especially when combined with a custom
164
+ formatter.
165
+
123
166
  IO
124
167
  --
125
168
  Class: Camayoc::Handlers::IO
@@ -127,6 +170,8 @@ Class: Camayoc::Handlers::IO
127
170
  Writes out stats values and a timestamp to some stream of your choice. See
128
171
  class for configuration details.
129
172
 
173
+ Another good event logging handler.
174
+
130
175
  Redis (Experimental)
131
176
  --------------------
132
177
  Class: Camayoc::Handlers::Redis (require "camayoc/handlers/redis" first)
@@ -137,23 +182,37 @@ time-based stats system. It does make it easy to collect some simple counts and
137
182
  some timing data. You can easily retrieve the stored data from redis by using
138
183
  the #get method.
139
184
 
185
+ This handler does not currently support event logging.
186
+
140
187
 
141
188
  Implementing a Handler
142
189
  ======================
143
- Let's say you want to implement your own handler, pehaps to MongoDB. Handlers
190
+
191
+ v0.2+ Incompatibility Notice
192
+ ----------------------------
193
+ As of Version 0.2.0, handlers must implement an event method. In 0.1.0,
194
+ handlers had to implement count and timing methods. Now handlers are encouraged
195
+ to dispatch events as described below.
196
+
197
+ Implementation Example
198
+ ----------------------
199
+ Let's say you want to implement your own handler, perhaps to MongoDB. Handlers
144
200
  just need to respond to a simple interface. See Camayoc::Handlers::StatEvent
145
- for info on the argument to the two methods.
201
+ for info on the argument to the event method.
146
202
 
147
203
  class SomeHandler
148
- def count(stat_event)
149
- # Do something
150
- end
151
-
152
- def timing(stat_event)
153
- # Do something
204
+ def event(evt)
205
+ case evt.type
206
+ when :count then handle_count(evt) # do something with a count stat
207
+ when :timing then handle_timing(evt) # do something with a timing stat
208
+ when :generic then handle_other(evt) # do something with a generic event
154
209
  end
155
210
  end
156
211
 
212
+ If you were writing a MongoDB handler, the above might increment a value in
213
+ a collection on :count and :timing and store raw data to a collection for
214
+ :generic events.
215
+
157
216
  If you write a handler and would like it included in Camayoc, please fork
158
217
  and send a pull request and we'll get it integrated in.
159
218
 
@@ -10,7 +10,7 @@ require 'camayoc/handlers/memory'
10
10
  require 'camayoc/handlers/statsd'
11
11
  require 'camayoc/stats'
12
12
 
13
- module Camayoc
13
+ module Camayoc
14
14
 
15
15
  DELIMITER = ":"
16
16
 
@@ -19,7 +19,7 @@ module Camayoc
19
19
 
20
20
  class << self
21
21
  def [](name)
22
- @lock.synchronize { @registry[name] ||= _new(name) }
22
+ @lock.synchronize { @registry[name] ||= _new(name) }
23
23
  end
24
24
 
25
25
  def new(name)
@@ -33,11 +33,15 @@ module Camayoc
33
33
  def thread_safe=(value)
34
34
  @thread_safe = value
35
35
  end
36
-
36
+
37
37
  def thread_safe?
38
38
  @thread_safe == true
39
39
  end
40
40
 
41
+ def join(*names)
42
+ names.flatten.join(DELIMITER)
43
+ end
44
+
41
45
  private
42
46
  def _new(name)
43
47
  stats = Stats.new(name,ancestor(name))
@@ -58,10 +62,10 @@ module Camayoc
58
62
  ancestor = nil
59
63
  path = name.split(DELIMITER)
60
64
  while path.pop && !ancestor
61
- ancestor = @registry[path.join(DELIMITER)]
65
+ ancestor = @registry[join(path)]
62
66
  end
63
67
  ancestor
64
68
  end
65
69
  end
66
70
 
67
- end
71
+ end
@@ -1,43 +1,38 @@
1
1
  module Camayoc
2
2
  module Handlers
3
3
  class Filter
4
-
4
+
5
+ # Constructor - by default, returns true
6
+ # * +dest+ :: Handler
7
+ # * +options[]+
8
+ # * + :with+ :: Regexp matching against event namespace
9
+ # * + :if+ :: Proc taking type and event returning true
10
+ # * + :unless+ :: Converse of +if+
5
11
  def initialize(dest,options={},&block)
6
12
  @dest = dest
7
13
  if block_given?
8
14
  @filter = block
9
15
  elsif options[:with]
10
16
  pattern = options[:with]
11
- @filter = Proc.new {|type,event| event.ns_stat =~ pattern }
17
+ @filter = Proc.new {|event| event.ns_stat =~ pattern }
12
18
  elsif options[:if]
13
19
  @filter = options[:if]
14
20
  elsif options[:unless]
15
21
  proc = options[:unless]
16
- @filter = Proc.new do |type,event|
17
- !proc.call(type,event)
22
+ @filter = Proc.new do |event|
23
+ !proc.call(event)
18
24
  end
19
25
  else
20
- @filter = Proc.new {|args| true }
21
- end
22
- end
23
-
24
- def count(event)
25
- if allowed?(:count,event)
26
- @dest.count(event)
26
+ @filter = Proc.new { true }
27
27
  end
28
28
  end
29
29
 
30
- def timing(event)
31
- if allowed?(:timing,event)
32
- @dest.timing(event)
30
+ def event(ev)
31
+ if @filter.call(ev)
32
+ @dest.event(ev)
33
33
  end
34
34
  end
35
35
 
36
- private
37
- def allowed?(type,event)
38
- @filter.call(type,event)
39
- end
40
-
41
36
  end
42
37
  end
43
- end
38
+ end
@@ -1,25 +1,25 @@
1
1
  module Camayoc
2
2
  module Handlers
3
3
 
4
- # Write to a raw IO stream with optional thread-safe locking before writing
5
- # to the stream. By default, each stat message is written on a single line
4
+ # Write to a raw IO stream with optional thread-safe locking before writing
5
+ # to the stream. By default, each stat message is written on a single line
6
6
  # using puts. See the options in Camayoc::Handlers:Logger for options.
7
7
  class IO < Logger
8
8
 
9
9
  include ThreadSafety
10
10
 
11
- def initialize(io=$stdout,options={})
12
- super(io,{:method=>:puts}.merge(options))
11
+ def initialize(io=$stdout,options={},&block)
12
+ super(io,{:method=>:puts}.merge(options),&block)
13
13
  self.thread_safe = Camayoc.thread_safe?
14
14
  end
15
15
 
16
16
  protected
17
- def write(type,event)
17
+ def write(event)
18
18
  synchronize do
19
- super(type,event)
19
+ super(event)
20
20
  end
21
21
  end
22
22
 
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -1,44 +1,39 @@
1
1
  module Camayoc
2
2
  module Handlers
3
-
4
- # Write stats to a logger. Specify the method to call on the logger instance
5
- # with :method (usually something like :info). If not :method is specified
6
- # :debug will be called on the logger. You can control the format of the
3
+
4
+ # Write stats to a logger. Specify the method to call on the logger instance
5
+ # with :method (usually something like :info). If not :method is specified
6
+ # :debug will be called on the logger. You can control the format of the
7
7
  # message passed to the logger method using the :formatter Proc.
8
8
  class Logger
9
9
 
10
10
  attr_accessor :logger, :method, :formatter
11
11
 
12
- def initialize(logger, options={})
12
+ def initialize(logger, options={}, &block)
13
13
  self.logger = logger
14
14
  self.method = options[:method]
15
- self.formatter = (options[:formatter] || default_formatter)
16
- end
17
-
18
- def count(event)
19
- write(:count,event)
15
+ if block_given?
16
+ self.formatter = block
17
+ else
18
+ self.formatter = (options[:formatter] || default_formatter)
19
+ end
20
20
  end
21
21
 
22
- def timing(event)
23
- write(:timing,event)
22
+ def event(ev)
23
+ msg = formatter.call(ev)
24
+ if @method
25
+ @logger.send(@method,msg)
26
+ else
27
+ @logger.debug(msg)
28
+ end
24
29
  end
25
30
 
26
31
  def default_formatter
27
- Proc.new do |type,event|
28
- "#{type} #{event.ns_stat} #{event.value} #{Time.now.utc.to_i}"
29
- end
30
- end
31
-
32
- protected
33
- def write(type,event)
34
- msg = formatter.call(type,event)
35
- if @method
36
- @logger.send(@method,msg)
37
- else
38
- @logger.debug(msg)
39
- end
32
+ Proc.new do |event|
33
+ "#{event.type} #{event.ns_stat} #{event.value} #{Time.now.utc.to_i}"
40
34
  end
35
+ end
41
36
 
42
37
  end
43
38
  end
44
- end
39
+ end
@@ -1,25 +1,25 @@
1
1
  module Camayoc
2
2
  module Handlers
3
3
  class Memory
4
-
4
+
5
5
  include ThreadSafety
6
6
 
7
+ DEFAULT_MAX_EVENTS = 1024
8
+
9
+ attr_reader :max_events
10
+
7
11
  def initialize(options={})
8
12
  @data = {}
13
+ @max_events = options[:max_events].to_i rescue 0
14
+ @max_events = DEFAULT_MAX_EVENTS if @max_events <= 0
9
15
  self.thread_safe = Camayoc.thread_safe?
10
16
  end
11
17
 
12
- def count(event)
13
- stat = event.ns_stat
14
- synchronize do
15
- @data[stat] ||= 0
16
- @data[stat] += event.value
17
- end
18
- end
19
-
20
- def timing(event)
21
- synchronize do
22
- (@data[event.ns_stat] ||= Timing.new) << event.value
18
+ def event(ev)
19
+ case ev.type
20
+ when :count then count(ev)
21
+ when :timing then timing(ev)
22
+ when :generic then generic(ev)
23
23
  end
24
24
  end
25
25
 
@@ -30,6 +30,32 @@ module Camayoc
30
30
  end
31
31
  alias_method :[], :get
32
32
 
33
+ private
34
+ def count(ev)
35
+ stat = ev.ns_stat
36
+ synchronize do
37
+ @data[stat] ||= 0
38
+ @data[stat] += ev.value
39
+ end
40
+ end
41
+
42
+ def timing(ev)
43
+ synchronize do
44
+ (@data[ev.ns_stat] ||= Timing.new) << ev.value
45
+ end
46
+ end
47
+
48
+ def generic(ev)
49
+ stat = ev.ns_stat
50
+ synchronize do
51
+ @data[stat] ||= []
52
+ @data[stat].unshift ev.value
53
+ if @data[stat].length > @max_events
54
+ @data[stat] = @data[stat][0,@max_events]
55
+ end
56
+ end
57
+ end
58
+
33
59
  end
34
60
  end
35
- end
61
+ end
@@ -5,29 +5,25 @@ module Camayoc
5
5
 
6
6
  # This class is experimental.
7
7
  class Redis
8
-
8
+
9
9
  def initialize(options={})
10
10
  @namespace = options.delete(:namespace)
11
11
  @redis = (options[:redis] || ::Redis.new(options))
12
12
  end
13
13
 
14
- def count(event)
15
- @redis.incrby(key(event.ns_stat),event.value)
16
- end
17
-
18
- def timing(event)
19
- stat = event.ns_stat
20
- ms = event.value
21
- @redis.set(key(stat),"timing")
22
- @redis.incrby(key("#{stat}:_count"),1)
23
- @redis.incrby(key("#{stat}:_total"),ms)
14
+ def event(ev)
15
+ case ev.type
16
+ when :count then count(ev)
17
+ when :timing then timing(ev)
18
+ # generic not implemented!
19
+ end
24
20
  end
25
21
 
26
22
  def get(stat)
27
23
  value = @redis.get(key(stat))
28
24
  if value == "timing"
29
25
  Timing.new(
30
- @redis.get(key("#{stat}:_total")).to_i,
26
+ @redis.get(key("#{stat}:_total")).to_i,
31
27
  @redis.get(key("#{stat}:_count")).to_i,
32
28
  nil,nil # Don't support min and max right now in redis
33
29
  )
@@ -40,10 +36,22 @@ module Camayoc
40
36
  alias_method :[], :get
41
37
 
42
38
  private
39
+ def count(event)
40
+ @redis.incrby(key(event.ns_stat),event.value)
41
+ end
42
+
43
+ def timing(event)
44
+ stat = event.ns_stat
45
+ ms = event.value
46
+ @redis.set(key(stat),"timing")
47
+ @redis.incrby(key("#{stat}:_count"),1)
48
+ @redis.incrby(key("#{stat}:_total"),ms)
49
+ end
50
+
43
51
  def key(stat)
44
52
  @namespace ? "#{@namespace}:#{stat}" : stat
45
53
  end
46
54
 
47
55
  end
48
56
  end
49
- end
57
+ end