scrolls 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Scrolls
2
2
 
3
- Logging gem, compiled from many different internal projects in order to make
4
- it easier to add logging to projects. Basically I grew tired of moving this
5
- file around.
3
+ Scrolls is a logging library that is focused on outputting logs in a
4
+ key=value structure. It's in use at Heroku where we use the event data
5
+ to drive metrics and monitoring services.
6
+
7
+ Scrolls is rather opinionated.
6
8
 
7
9
  ## Installation
8
10
 
@@ -20,31 +22,123 @@ Or install it yourself as:
20
22
 
21
23
  ## Usage
22
24
 
23
- It's pretty easy to use:
25
+ At Heroku we are big believers in "logs as data". We log everything so
26
+ that we can act upon that event stream of logs. Internally we use logs
27
+ to produce metrics and monitoring data that we can alert on.
28
+
29
+ Here's an example of a log you might specify in your application:
30
+
31
+ ```ruby
32
+ Scrolls.log(fn: "trap", signal: s, at: "exit", status: 0)
33
+ ```
34
+
35
+ The output of which might be:
36
+
37
+ fn=trap signal=TERM at=exit status=0
38
+
39
+ This provides a rich set of data that we can parse and act upon.
40
+
41
+ A feature of Scrolls is setting contexts. Scrolls has two types of
42
+ context. One is 'global_context' that prepends every log in your
43
+ application with that data and a local 'context' which can be used,
44
+ for example, to wrap requests with a request id.
45
+
46
+ In our example above, the log message is rather generic, so in order
47
+ to provide more context we might set a global context that links this
48
+ log data to our application and deployment:
49
+
50
+ ```ruby
51
+ Scrolls.global_context(app: "myapp", deploy: ENV["DEPLOY"])
52
+ ```
53
+
54
+ This would change our log output above to:
55
+
56
+ app=myapp deploy=production fn=trap signal=TERM at=exit status=0
57
+
58
+ If we were in a file and wanted to wrap a particular point of context
59
+ we might also do something similar to:
60
+
61
+ ```ruby
62
+ Scrolls.context(ns: "server") do
63
+ Scrolls.log(fn: "trap", signal: s, at: "exit", status: 0)
64
+ end
65
+ ```
66
+
67
+ This would be the output (taking into consideration our global context
68
+ above):
24
69
 
25
- require 'scrolls'
70
+ app=myapp deploy=production ns=server fn=trap signal=TERM at=exit status=0
26
71
 
27
- Put this somewhere early on in your application:
72
+ This allows us to track this log to `Server#trap` and we received a
73
+ 'TERM' signal and exited 0.
28
74
 
29
- Scrolls::Log.start
75
+ As you can see we have some standard nomenclature around logging.
76
+ Here's a cheat sheet for some of the methods we use:
30
77
 
31
- Use it:
78
+ * `app`: Application
79
+ * `lib`: Library
80
+ * `ns`: Namespace (Class, Module or files)
81
+ * `fn`: Function
82
+ * `at`: Execution point
83
+ * `deploy`: Our deployment (typically an environment variable i.e. `DEPLOY=staging`)
84
+ * `elapsed`: Measurements (Time)
85
+ * `count`: Measurements (Counters)
32
86
 
33
- Scrolls.log(:testing => true, :at => "readme")
87
+ Scrolls makes it easy to measure the run time of a portion of code.
88
+ For example:
89
+
90
+ ```ruby
91
+ Scrolls.log(fn: "test") do
92
+ Scrolls.log(status: "exec")
93
+ # Code here
94
+ end
95
+ ```
96
+
97
+ This will output the following log:
98
+
99
+ fn=test at=start
100
+ status=exec
101
+ fn=test at=finish elapsed=0.300
102
+
103
+ You can change the time unit that Scrolls uses to "milliseconds" (the
104
+ default is "seconds"):
105
+
106
+ ```ruby
107
+ Scrolls.time_unit = "ms"
108
+ ```
109
+
110
+ Scrolls has a rich #parse method to handle a number of cases. Here is
111
+ a look at some of the ways Scrolls handles certain values.
112
+
113
+ Time and nil:
114
+
115
+ ```ruby
116
+ Scrolls.log(t: Time.at(1340118167), this: nil)
117
+ t=t=2012-06-19T11:02:35-0400 this=nil
118
+ ```
119
+
120
+ True/False:
121
+
122
+ ```ruby
123
+ Scrolls.log(that: false, this: true)
124
+ that=false this=true
125
+ ```
126
+
127
+ ## History
128
+
129
+ This library originated from various logging methods used internally
130
+ at Heroku. Starting at version 0.2.0 Scrolls was ripped apart and
131
+ restructured to provide a better foundation for the future. Tests and
132
+ documentation were add at that point as well.
34
133
 
35
134
  ## Thanks
36
135
 
37
- I only compiled this library, many others made it work. Here are some of the
38
- authors of projects that I've used to get an idea of a consistent log gem:
136
+ Most of the ideas used in Scrolls are those of other engineers at
137
+ Heroku, I simply ripped them off to create a single library. Huge
138
+ thanks to:
39
139
 
40
140
  * Mark McGranaghan
41
141
  * Noah Zoschke
42
142
  * Mark Fine
43
-
44
- ## Contributing
45
-
46
- 1. Fork it
47
- 2. Create your feature branch (`git checkout -b my-new-feature`)
48
- 3. Commit your changes (`git commit -am 'Added some feature'`)
49
- 4. Push to the branch (`git push origin my-new-feature`)
50
- 5. Create new Pull Request
143
+ * Fabio Kung
144
+ * Ryan Smith
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
3
 
4
+ ENV['TESTOPTS'] = "-v"
5
+
4
6
  require "rake/testtask"
5
7
  Rake::TestTask.new do |t|
6
8
  t.pattern = "test/test_*.rb"
@@ -0,0 +1,58 @@
1
+ # The result of issues with an update I made to Scrolls. After talking with
2
+ # Fabio Kung about a fix I started work on an atomic object, but he added some
3
+ # fixes to #context without it and then used Headius' atomic gem.
4
+ #
5
+ # The code below is the start and cleanup of my atomic object. It's slim on
6
+ # details and eventually cleaned up around inspiration from Headius' code.
7
+ #
8
+ # LICENSE: Apache 2.0
9
+ #
10
+ # See Headius' atomic gem here:
11
+ # https://github.com/headius/ruby-atomic
12
+
13
+ require 'thread'
14
+
15
+ class AtomicObject
16
+ def initialize(o)
17
+ @mtx = Mutex.new
18
+ @o = o
19
+ end
20
+
21
+ def get
22
+ @mtx.synchronize { @o }
23
+ end
24
+
25
+ def set(n)
26
+ @mtx.synchronize { @o = n }
27
+ end
28
+
29
+ def verify_set(o, n)
30
+ return false unless @mtx.try_lock
31
+ begin
32
+ return false unless @o.equal? o
33
+ @o = n
34
+ ensure
35
+ @mtx.unlock
36
+ end
37
+ end
38
+ end
39
+
40
+ class Atomic < AtomicObject
41
+ def initialize(v=nil)
42
+ super(v)
43
+ end
44
+
45
+ def value
46
+ self.get
47
+ end
48
+
49
+ def value=(v)
50
+ self.set(v)
51
+ v
52
+ end
53
+
54
+ def update
55
+ true until self.verify_set(o = self.get, n = yield(o))
56
+ n
57
+ end
58
+ end
@@ -0,0 +1,196 @@
1
+ require "scrolls/parser"
2
+ require "scrolls/utils"
3
+
4
+ module Scrolls
5
+
6
+ class TimeUnitError < RuntimeError; end
7
+
8
+ module Log
9
+ extend self
10
+
11
+ extend Parser
12
+ extend Utils
13
+
14
+ LOG_LEVEL = (ENV['LOG_LEVEL'] || 3).to_i
15
+ LOG_LEVEL_MAP = {
16
+ "emergency" => 0,
17
+ "alert" => 1,
18
+ "critical" => 2,
19
+ "error" => 3,
20
+ "warning" => 4,
21
+ "notice" => 5,
22
+ "info" => 6,
23
+ "debug" => 7
24
+ }
25
+
26
+ def context
27
+ Thread.current[:scrolls_context] ||= {}
28
+ end
29
+
30
+ def context=(h)
31
+ Thread.current[:scrolls_context] = h
32
+ end
33
+
34
+ def global_context
35
+ get_global_context
36
+ end
37
+
38
+ def global_context=(data)
39
+ set_global_context(data)
40
+ end
41
+
42
+ def stream=(out=nil)
43
+ @defined = out.nil? ? false : true
44
+
45
+ @stream = sync_stream(out)
46
+ end
47
+
48
+ def stream
49
+ @stream ||= sync_stream
50
+ end
51
+
52
+ def time_unit=(u)
53
+ set_time_unit(u)
54
+ end
55
+
56
+ def time_unit
57
+ @tunit ||= default_time_unit
58
+ end
59
+
60
+ def log(data, &blk)
61
+ if gc = get_global_context
62
+ ctx = gc.merge(context)
63
+ logdata = ctx.merge(data)
64
+ end
65
+
66
+ unless blk
67
+ write(logdata)
68
+ else
69
+ start = Time.now
70
+ res = nil
71
+ log(logdata.merge(at: "start"))
72
+ begin
73
+ res = yield
74
+ rescue StandardError, Timeout::Error => e
75
+ log(
76
+ :at => "exception",
77
+ :reraise => true,
78
+ :class => e.class,
79
+ :message => e.message,
80
+ :exception_id => e.object_id.abs,
81
+ :elapsed => calc_time(start, Time.now)
82
+ )
83
+ raise e
84
+ end
85
+ log(logdata.merge(at: "finish", elapsed: calc_time(start, Time.now)))
86
+ res
87
+ end
88
+ end
89
+
90
+ def log_exception(data, e)
91
+ sync_stream(STDERR) unless @defined
92
+
93
+ if gc = get_global_context
94
+ logdata = gc.merge(data)
95
+ end
96
+
97
+ log(logdata.merge(
98
+ :at => "exception",
99
+ :class => e.class,
100
+ :message => e.message,
101
+ :exception_id => e.object_id.abs
102
+ ))
103
+ if e.backtrace
104
+ bt = e.backtrace.reverse
105
+ bt[0, bt.size-6].each do |line|
106
+ log(logdata.merge(
107
+ :at => "exception",
108
+ :class => e.message,
109
+ :exception_id => e.object_id.abs,
110
+ :site => line.gsub(/[`'"]/, "")
111
+ ))
112
+ end
113
+ end
114
+ end
115
+
116
+ def with_context(prefix)
117
+ return unless block_given?
118
+ old = context
119
+ self.context = old.merge(prefix)
120
+ yield if block_given?
121
+ self.context = old
122
+ end
123
+
124
+ private
125
+
126
+ def get_global_context
127
+ default_global_context unless @global_context
128
+ @global_context.value
129
+ end
130
+
131
+ def set_global_context(data=nil)
132
+ default_global_context unless @global_context
133
+ @global_context.update { |_| data }
134
+ end
135
+
136
+ def default_global_context
137
+ @global_context = Atomic.new({})
138
+ end
139
+
140
+ def set_time_unit(u=nil)
141
+ unless ["ms","milli","milliseconds","s","seconds"].include?(u)
142
+ raise TimeUnitError, "Specify only 'seconds' or 'milliseconds'"
143
+ end
144
+
145
+ if ["ms", "milli", "milliseconds", 1000].include?(u)
146
+ @tunit = "milliseconds"
147
+ @t = 1000.0
148
+ else
149
+ default_time_unit
150
+ end
151
+ end
152
+
153
+ def default_time_unit
154
+ @t = 1.0
155
+ @tunit = "seconds"
156
+ end
157
+
158
+ def calc_time(start, finish)
159
+ default_time_unit unless @t
160
+ ((finish - start).to_f * @t)
161
+ end
162
+
163
+ def mtx
164
+ @mtx ||= Mutex.new
165
+ end
166
+
167
+ def sync_stream(out=nil)
168
+ out = STDOUT if out.nil?
169
+ s = out
170
+ s.sync = true
171
+ s
172
+ end
173
+
174
+ def write(data)
175
+ if log_level_ok?(data[:level])
176
+ msg = unparse(data)
177
+ mtx.synchronize do
178
+ begin
179
+ stream.puts(msg)
180
+ rescue NoMethodError => e
181
+ raise
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def log_level_ok?(level)
188
+ if level
189
+ LOG_LEVEL_MAP[level.to_s] <= LOG_LEVEL
190
+ else
191
+ true
192
+ end
193
+ end
194
+
195
+ end
196
+ end
@@ -0,0 +1,64 @@
1
+ module Scrolls
2
+ module Parser
3
+ extend self
4
+
5
+ def unparse(data)
6
+ data.map do |(k,v)|
7
+ if (v == true)
8
+ "#{k}=true"
9
+ elsif (v == false)
10
+ "#{k}=false"
11
+ elsif v.is_a?(Float)
12
+ "#{k}=#{format("%.3f", v)}"
13
+ elsif v.nil?
14
+ "#{k}=nil"
15
+ elsif v.is_a?(Time)
16
+ "#{k}=#{Time.at(v).strftime("%FT%H:%M:%S%z")}"
17
+ elsif v.is_a?(String) && v =~ /\\|\"| /
18
+ v = v.gsub(/\\|"/) { |c| "\\#{c}" }
19
+ "#{k}=\"#{v}\""
20
+ else
21
+ "#{k}=#{v}"
22
+ end
23
+ end.compact.join(" ")
24
+ end
25
+
26
+ def parse(data)
27
+ vals = {}
28
+ str = data.dup if data.is_a?(String)
29
+
30
+ patterns = [
31
+ /([^= ]+)="([^"\\]*(\\.[^"\\]*)*)"/, # key="\"literal\" escaped val"
32
+ /([^= ]+)=([^ =]+)/ # key=value
33
+ ]
34
+
35
+ patterns.each do |pattern|
36
+ str.scan(pattern) do |match|
37
+ v = match[1]
38
+ v.gsub!(/\\"/, '"') # unescape \"
39
+ v.gsub!(/\\\\/, "\\") # unescape \\
40
+
41
+ if v.to_i.to_s == v # cast value to int or float
42
+ v = v.to_i
43
+ elsif format("%.3f", v.to_f) == v
44
+ v = v.to_f
45
+ elsif v == "false"
46
+ v = false
47
+ elsif v == "true"
48
+ v = true
49
+ end
50
+
51
+ vals[match[0]] = v
52
+ end
53
+ # sub value, leaving keys in order
54
+ str.gsub!(pattern, "\\1")
55
+ end
56
+
57
+ # rebuild in-order key: value hash
58
+ str.split.inject({}) do |h,k|
59
+ h[k.to_sym] = vals[k]
60
+ h
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ module Scrolls
2
+ module Utils
3
+
4
+ def hashify(d)
5
+ last = d.pop
6
+ return {} unless last
7
+ return hashified_list(d).merge(last) if last.is_a?(Hash)
8
+ d.push(last)
9
+ hashified_list(d)
10
+ end
11
+
12
+ def hashified_list(l)
13
+ return {} if l.empty?
14
+ l.inject({}) do |h, i|
15
+ h[i.to_sym] = true
16
+ h
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Scrolls
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/lib/scrolls.rb CHANGED
@@ -1,172 +1,117 @@
1
1
  require "thread"
2
- require "atomic"
3
-
2
+ require "scrolls/atomic"
3
+ require "scrolls/log"
4
4
  require "scrolls/version"
5
5
 
6
6
  module Scrolls
7
7
  extend self
8
8
 
9
+ # Public: Set a context in a block for logs
10
+ #
11
+ # data - A hash of key/values to prepend to each log in a block
12
+ # blk - The block that our context wraps
13
+ #
14
+ # Examples:
15
+ #
16
+ def context(data, &blk)
17
+ Log.with_context(data, &blk)
18
+ end
19
+
20
+ # Public: Get or set a global context that prefixs all logs
21
+ #
22
+ # data - A hash of key/values to prepend to each log
23
+ #
24
+ def global_context(data=nil)
25
+ if data
26
+ Log.global_context = data
27
+ else
28
+ Log.global_context
29
+ end
30
+ end
31
+
32
+ # Public: Log data and/or wrap a block with start/finish
33
+ #
34
+ # data - A hash of key/values to log
35
+ # blk - A block to be wrapped by log lines
36
+ #
37
+ # Examples:
38
+ #
39
+ # Scrolls.log(test: "test")
40
+ # test=test
41
+ # => nil
42
+ #
43
+ # Scrolls.log(test: "test") { puts "inner block" }
44
+ # at=start
45
+ # inner block
46
+ # at=finish elapsed=0.000
47
+ # => nil
48
+ #
9
49
  def log(data, &blk)
10
50
  Log.log(data, &blk)
11
51
  end
12
52
 
53
+ # Public: Log an exception
54
+ #
55
+ # data - A hash of key/values to log
56
+ # e - An exception to pass to the logger
57
+ #
58
+ # Examples:
59
+ #
60
+ # begin
61
+ # raise Exception
62
+ # rescue Exception => e
63
+ # Scrolls.log_exception({test: "test"}, e)
64
+ # end
65
+ # test=test at=exception class=Exception message=Exception exception_id=70321999017240
66
+ # ...
67
+ #
13
68
  def log_exception(data, e)
14
69
  Log.log_exception(data, e)
15
70
  end
16
71
 
17
- def global_context(data)
18
- Log.global_context = data
72
+ # Public: Setup a new output (default: STDOUT)
73
+ #
74
+ # out - New output
75
+ #
76
+ # Examples
77
+ #
78
+ # Scrolls.stream = StringIO.new
79
+ #
80
+ def stream=(out)
81
+ Log.stream=(out)
19
82
  end
20
83
 
21
- def context(data, &blk)
22
- Log.with_context(data, &blk)
84
+ # Public: Return the stream
85
+ #
86
+ # Examples
87
+ #
88
+ # Scrolls.stream
89
+ # => #<IO:<STDOUT>>
90
+ #
91
+ def stream
92
+ Log.stream
23
93
  end
24
94
 
25
- module Log
26
- extend self
27
-
28
- LOG_LEVEL = (ENV["LOG_LEVEL"] || 3).to_i
29
-
30
- #http://tools.ietf.org/html/rfc5424#page-11
31
- LOG_LEVEL_MAP = {
32
- "emergency" => 0,
33
- "alert" => 1,
34
- "critical" => 2,
35
- "error" => 3,
36
- "warning" => 4,
37
- "notice" => 5,
38
- "info" => 6,
39
- "debug" => 7
40
- }
41
-
42
- attr_accessor :stream
43
-
44
- def context
45
- Thread.current[:scrolls_context] ||= {}
46
- end
47
-
48
- def context=(hash)
49
- Thread.current[:scrolls_context] = hash
50
- end
51
-
52
- def start(out = nil)
53
- # This allows log_exceptions below to pick up the defined output,
54
- # otherwise stream out to STDERR
55
- @defined = out.nil? ? false : true
56
-
57
- sync_stream(out)
58
- @global_context = Atomic.new({})
59
- end
60
-
61
- def sync_stream(out = nil)
62
- out = STDOUT if out.nil?
63
- @stream = out
64
- @stream.sync = true
65
- end
66
-
67
- def mtx
68
- @mtx ||= Mutex.new
69
- end
70
-
71
- def write(data)
72
- if log_level_ok?(data[:level])
73
- msg = unparse(data)
74
- mtx.synchronize do
75
- begin
76
- @stream.puts(msg)
77
- rescue NoMethodError => e
78
- puts "You need to start your logger, `Scrolls::Log.start`"
79
- end
80
- end
81
- end
82
- end
83
-
84
- def unparse(data)
85
- data.map do |(k, v)|
86
- if (v == true)
87
- k.to_s
88
- elsif v.is_a?(Float)
89
- "#{k}=#{format("%.3f", v)}"
90
- elsif v.nil?
91
- nil
92
- else
93
- v_str = v.to_s
94
- if (v_str =~ /^[a-zA-z0-9\-\_\.]+$/)
95
- "#{k}=#{v_str}"
96
- else
97
- "#{k}=\"#{v_str.sub(/".*/, "...")}\""
98
- end
99
- end
100
- end.compact.join(" ")
101
- end
102
-
103
- def log(data, &blk)
104
- merged_context = @global_context.value.merge(context)
105
- logdata = merged_context.merge(data)
106
-
107
- unless blk
108
- write(logdata)
109
- else
110
- start = Time.now
111
- res = nil
112
- log(logdata.merge(:at => :start))
113
- begin
114
- res = yield
115
- rescue StandardError, Timeout::Error => e
116
- log(logdata.merge(
117
- :at => :exception,
118
- :reraise => true,
119
- :class => e.class,
120
- :message => e.message,
121
- :exception_id => e.object_id.abs,
122
- :elapsed => Time.now - start
123
- ))
124
- raise(e)
125
- end
126
- log(logdata.merge(:at => :finish, :elapsed => Time.now - start))
127
- res
128
- end
129
- end
130
-
131
- def log_exception(data, e)
132
- sync_stream(STDERR) unless @defined
133
- log(data.merge(
134
- :exception => true,
135
- :class => e.class,
136
- :message => e.message,
137
- :exception_id => e.object_id.abs
138
- ))
139
- if e.backtrace
140
- bt = e.backtrace.reverse
141
- bt[0, bt.size-6].each do |line|
142
- log(data.merge(
143
- :exception => true,
144
- :exception_id => e.object_id.abs,
145
- :site => line.gsub(/[`'"]/, "")
146
- ))
147
- end
148
- end
149
- end
150
-
151
- def log_level_ok?(level)
152
- if level
153
- LOG_LEVEL_MAP[level.to_s] <= LOG_LEVEL
154
- else
155
- true
156
- end
157
- end
158
-
159
- def with_context(prefix)
160
- return unless block_given?
161
- old_context = context
162
- self.context = old_context.merge(prefix)
163
- yield if block_given?
164
- self.context = old_context
165
- end
166
-
167
- def global_context=(data)
168
- @global_context.update { |_| data }
169
- end
95
+ # Public: Set the time unit we use for 'elapsed' (default: "seconds")
96
+ #
97
+ # unit - The time unit ("milliseconds" currently supported)
98
+ #
99
+ # Examples
100
+ #
101
+ # Scrolls.time_unit = "milliseconds"
102
+ #
103
+ def time_unit=(unit)
104
+ Log.time_unit=(unit)
105
+ end
170
106
 
107
+ # Public: Return the time unit currently configured
108
+ #
109
+ # Examples
110
+ #
111
+ # Scrolls.time_unit
112
+ # => "seconds"
113
+ #
114
+ def time_unit
115
+ Log.time_unit
171
116
  end
172
117
  end
data/scrolls.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  require File.expand_path('../lib/scrolls/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Heroku"]
6
- gem.email = ["curt@heroku.com"]
7
- gem.description = "Logging, easier, more consistent."
8
- gem.summary = "When do we log? All the time."
5
+ gem.authors = ["Curt Micol"]
6
+ gem.email = ["asenchi@asenchi.com"]
7
+ gem.description = %q{Logging, easier, more consistent.}
8
+ gem.summary = %q{When do we log? All the time.}
9
9
  gem.homepage = "https://github.com/asenchi/scrolls"
10
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
- gem.files = `git ls-files`.split("\n")
12
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
14
  gem.name = "scrolls"
14
15
  gem.require_paths = ["lib"]
15
16
  gem.version = Scrolls::VERSION
16
- gem.add_dependency("atomic", "~> 1.0.0")
17
17
  end
@@ -0,0 +1,33 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestAtomic < Test::Unit::TestCase
4
+ def test_construct
5
+ atomic = Atomic.new
6
+ assert_equal nil, atomic.value
7
+
8
+ atomic = Atomic.new(0)
9
+ assert_equal 0, atomic.value
10
+ end
11
+
12
+ def test_value
13
+ atomic = Atomic.new(0)
14
+ atomic.value = 1
15
+
16
+ assert_equal 1, atomic.value
17
+ end
18
+
19
+ def test_update
20
+ atomic = Atomic.new(1000)
21
+ res = atomic.update {|v| v + 1}
22
+
23
+ assert_equal 1001, atomic.value
24
+ assert_equal 1001, res
25
+ end
26
+
27
+ def test_update_retries
28
+ tries = 0
29
+ atomic = Atomic.new(1000)
30
+ atomic.update{|v| tries += 1 ; atomic.value = 1001 ; v + 1}
31
+ assert_equal 2, tries
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ require "test/unit"
2
+ require "stringio"
3
+
4
+ $: << File.expand_path("../../lib", __FILE__)
5
+
6
+ require "scrolls"
@@ -0,0 +1,52 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestScrollsParser < Test::Unit::TestCase
4
+ include Scrolls::Parser
5
+
6
+ def test_parse_bool
7
+ data = { test: true, exec: false }
8
+ assert_equal "test=true exec=false", unparse(data)
9
+ assert_equal data.inspect, parse(unparse(data)).inspect
10
+ end
11
+
12
+ def test_parse_numbers
13
+ data = { elapsed: 12.00000, __time: 0 }
14
+ assert_equal "elapsed=12.000 __time=0", unparse(data)
15
+ assert_equal data.inspect, parse(unparse(data)).inspect
16
+ end
17
+
18
+ def test_parse_strings
19
+ # Strings are all double quoted, with " or \ escaped
20
+ data = { s: "echo 'hello' \"world\"" }
21
+ assert_equal 's="echo \'hello\' \\"world\\""', unparse(data)
22
+ assert_equal data.inspect, parse(unparse(data)).inspect
23
+
24
+ data = { s: "hello world" }
25
+ assert_equal 's="hello world"', unparse(data)
26
+ assert_equal data.inspect, parse(unparse(data)).inspect
27
+
28
+ data = { s: "slasher\\" }
29
+ assert_equal 's="slasher\\\\"', unparse(data)
30
+ assert_equal data.inspect, parse(unparse(data)).inspect
31
+
32
+ # simple value is unquoted
33
+ data = { s: "hi" }
34
+ assert_equal 's=hi', unparse(data)
35
+ assert_equal data.inspect, parse(unparse(data)).inspect
36
+ end
37
+
38
+ def test_parse_constants
39
+ data = { s1: :symbol, s2: Scrolls }
40
+ assert_equal "s1=symbol s2=Scrolls", unparse(data)
41
+ end
42
+
43
+ def test_parse_time
44
+ data = { t: Time.at(1340118155) }
45
+ assert_equal "t=2012-06-19T11:02:35-0400", unparse(data)
46
+ end
47
+
48
+ def test_parse_nil
49
+ data = { n: nil }
50
+ assert_equal "n=nil", unparse(data)
51
+ end
52
+ end
data/test/test_scrolls.rb CHANGED
@@ -1,31 +1,110 @@
1
- require "stringio"
2
- require "minitest/autorun"
1
+ require_relative "test_helper"
3
2
 
4
- $: << "../lib"
5
- require "scrolls"
3
+ class TestScrolls < Test::Unit::TestCase
4
+ def setup
5
+ @out = StringIO.new
6
+ Scrolls.stream = @out
7
+ end
8
+
9
+ def teardown
10
+ Scrolls.global_context({})
11
+ end
12
+
13
+ def test_construct
14
+ assert_equal StringIO, Scrolls.stream.class
15
+ end
16
+
17
+ def test_default_global_context
18
+ assert_equal Hash.new, Scrolls.global_context
19
+ end
20
+
21
+ def test_setting_global_context
22
+ Scrolls.global_context(g: "g")
23
+ Scrolls.log(d: "d")
24
+ assert_equal "g=g d=d\n", @out.string
25
+ end
26
+
27
+ def test_default_context
28
+ Scrolls.log(data: "d")
29
+ assert_equal Hash.new, Scrolls::Log.context
30
+ end
6
31
 
7
- class TestScrollsParser < MiniTest::Unit::TestCase
8
- def test_unparse_tags
9
- data = {:test => true, :tag => true}
10
- assert "test tag" == Scrolls::Log.unparse(data)
32
+ def test_setting_context
33
+ Scrolls.context(c: "c") { Scrolls.log(i: "i") }
34
+ output = "c=c i=i\n"
35
+ assert_equal output, @out.string
11
36
  end
12
37
 
13
- def test_unparse_strings
14
- data = {:test => "strings"}
15
- assert "test=strings" == Scrolls::Log.unparse(data)
38
+ def test_all_the_contexts
39
+ Scrolls.global_context(g: "g")
40
+ Scrolls.log(o: "o") do
41
+ Scrolls.context(c: "c") do
42
+ Scrolls.log(ic: "i")
43
+ end
44
+ Scrolls.log(i: "i")
45
+ end
46
+ @out.truncate(37)
47
+ output = "g=g o=o at=start\ng=g c=c ic=i\ng=g i=i"
48
+ assert_equal output, @out.string
49
+ end
50
+
51
+ def test_deeply_nested_context
52
+ Scrolls.log(o: "o") do
53
+ Scrolls.context(c: "c") do
54
+ Scrolls.log(ic: "i")
55
+ end
56
+ Scrolls.log(i: "i")
57
+ end
58
+ @out.truncate(21)
59
+ output = "o=o at=start\nc=c ic=i"
60
+ assert_equal output, @out.string
61
+ end
16
62
 
17
- data = {:s => "echo 'hello' \"world\""}
18
- assert 's="echo \'hello\' ..."' == Scrolls::Log.unparse(data)
63
+ def test_deeply_nested_context_dropped
64
+ Scrolls.log(o: "o") do
65
+ Scrolls.context(c: "c") do
66
+ Scrolls.log(ic: "i")
67
+ end
68
+ Scrolls.log(i: "i")
69
+ end
70
+ @out.truncate(25)
71
+ output = "o=o at=start\nc=c ic=i\ni=i"
72
+ assert_equal output, @out.string
73
+ end
74
+
75
+ def test_default_time_unit
76
+ assert_equal "seconds", Scrolls.time_unit
77
+ end
19
78
 
20
- data = {:s => "hello world"}
21
- assert 's="hello world"' == Scrolls::Log.unparse(data)
79
+ def test_setting_time_unit
80
+ Scrolls.time_unit = "milliseconds"
81
+ assert_equal "milliseconds", Scrolls.time_unit
82
+ end
83
+
84
+ def test_setting_incorrect_time_unit
85
+ assert_raise Scrolls::TimeUnitError do
86
+ Scrolls.time_unit = "years"
87
+ end
88
+ end
89
+
90
+ def test_logging
91
+ Scrolls.log(test: "basic")
92
+ assert_equal "test=basic\n", @out.string
93
+ end
22
94
 
23
- data = {:s => "hello world\\"}
24
- assert 's="hello world\"' == Scrolls::Log.unparse(data)
95
+ def test_logging_block
96
+ Scrolls.log(outer: "o") { Scrolls.log(inner: "i") }
97
+ output = "outer=o at=start\ninner=i\nouter=o at=finish elapsed=0.000\n"
98
+ assert_equal output, @out.string
25
99
  end
26
100
 
27
- def test_unparse_floats
28
- data = {:test => 0.3}
29
- assert "test=0.300" == Scrolls::Log.unparse(data)
101
+ def test_log_exception
102
+ begin
103
+ raise Exception
104
+ rescue Exception => e
105
+ Scrolls.log_exception({test: "exception"}, e)
106
+ end
107
+ @out.truncate(27)
108
+ assert_equal "test=exception at=exception", @out.string
30
109
  end
31
110
  end
metadata CHANGED
@@ -1,30 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scrolls
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Heroku
8
+ - Curt Micol
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-26 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: atomic
16
- requirement: &70343059161980 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ~>
20
- - !ruby/object:Gem::Version
21
- version: 1.0.0
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: *70343059161980
12
+ date: 2012-06-20 00:00:00.000000000 Z
13
+ dependencies: []
25
14
  description: Logging, easier, more consistent.
26
15
  email:
27
- - curt@heroku.com
16
+ - asenchi@asenchi.com
28
17
  executables: []
29
18
  extensions: []
30
19
  extra_rdoc_files: []
@@ -35,8 +24,15 @@ files:
35
24
  - README.md
36
25
  - Rakefile
37
26
  - lib/scrolls.rb
27
+ - lib/scrolls/atomic.rb
28
+ - lib/scrolls/log.rb
29
+ - lib/scrolls/parser.rb
30
+ - lib/scrolls/utils.rb
38
31
  - lib/scrolls/version.rb
39
32
  - scrolls.gemspec
33
+ - test/test_atomic.rb
34
+ - test/test_helper.rb
35
+ - test/test_parser.rb
40
36
  - test/test_scrolls.rb
41
37
  homepage: https://github.com/asenchi/scrolls
42
38
  licenses: []
@@ -63,4 +59,7 @@ signing_key:
63
59
  specification_version: 3
64
60
  summary: When do we log? All the time.
65
61
  test_files:
62
+ - test/test_atomic.rb
63
+ - test/test_helper.rb
64
+ - test/test_parser.rb
66
65
  - test/test_scrolls.rb