cabin 0.4.2 → 0.4.3
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/examples/sample.rb +2 -4
- data/lib/cabin/channel.rb +47 -50
- data/lib/cabin/mixins/CAPSLOCK.rb +11 -19
- data/lib/cabin/mixins/colors.rb +24 -0
- data/lib/cabin/mixins/logger.rb +29 -11
- data/lib/cabin/mixins/timer.rb +25 -0
- data/lib/cabin/mixins/timestamp.rb +13 -0
- data/lib/cabin/outputs/io.rb +67 -18
- metadata +7 -4
data/examples/sample.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "cabin"
|
3
|
-
require "logger"
|
4
3
|
|
5
4
|
# Logging::... is something I'm implemented and experimenting with.
|
6
5
|
@logger = Cabin::Channel.new
|
@@ -8,9 +7,8 @@ require "logger"
|
|
8
7
|
# A logging channel can have any number of subscribers.
|
9
8
|
# Any subscriber is simply expected to respond to '<<' and take a single
|
10
9
|
# argument (the event)
|
11
|
-
# Special case of stdlib Logger
|
12
|
-
|
13
|
-
@logger.subscribe(Logger.new(STDOUT))
|
10
|
+
# Special case handling of stdlib Logger and IO objects comes for free, though.
|
11
|
+
@logger.subscribe(STDOUT)
|
14
12
|
|
15
13
|
# You can store arbitrary key-value pairs in the logging channel.
|
16
14
|
# These are emitted with every event.
|
data/lib/cabin/channel.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "cabin/mixins/logger"
|
2
|
+
require "cabin/mixins/timestamp"
|
3
|
+
require "cabin/mixins/timer"
|
2
4
|
require "cabin/namespace"
|
3
|
-
require "cabin/timer"
|
4
5
|
require "cabin/context"
|
5
6
|
require "cabin/outputs/stdlib-logger"
|
6
7
|
require "cabin/outputs/io"
|
@@ -45,26 +46,42 @@ require "logger"
|
|
45
46
|
# I, [2011-10-11T01:00:57.993575 #1209] INFO -- : {:timestamp=>"2011-10-11T01:00:57.993517-0700", :message=>"Done in foo", :level=>:info}
|
46
47
|
#
|
47
48
|
class Cabin::Channel
|
49
|
+
class << self
|
50
|
+
# Get a channel for a given identifier. If this identifier has never been
|
51
|
+
# used, a new channel is created for it.
|
52
|
+
# The default identifier is the application executable name.
|
53
|
+
#
|
54
|
+
# This is useful for using the same Cabin::Channel across your
|
55
|
+
# entire application.
|
56
|
+
def get(identifier=$0)
|
57
|
+
@channels ||= Hash.new { |h,k| h[k] = Cabin::Channel.new }
|
58
|
+
return @channels[identifier]
|
59
|
+
end # def Cabin::Channel.get
|
60
|
+
|
61
|
+
# Get a list of filters included in this class.
|
62
|
+
def filters
|
63
|
+
@filters ||= []
|
64
|
+
end # def Cabin::Channel.filters
|
65
|
+
|
66
|
+
# Register a new filter. The block is passed the event. It is expected to
|
67
|
+
# modify that event or otherwise do nothing.
|
68
|
+
def filter(&block)
|
69
|
+
@filters ||= []
|
70
|
+
@filters << block
|
71
|
+
end
|
72
|
+
end # class << self
|
73
|
+
|
48
74
|
include Cabin::Mixins::Logger
|
75
|
+
include Cabin::Mixins::Timestamp
|
76
|
+
include Cabin::Mixins::Timer
|
49
77
|
|
50
78
|
# All channels come with a metrics provider.
|
51
79
|
attr_accessor :metrics
|
52
|
-
|
53
|
-
|
54
|
-
# used, a new channel is created for it.
|
55
|
-
# The default identifier is the application executable name.
|
56
|
-
#
|
57
|
-
# This is useful for using the same Cabin::Channel across your
|
58
|
-
# entire application.
|
59
|
-
public
|
60
|
-
def self.get(identifier=$0)
|
61
|
-
@channels ||= Hash.new { |h,k| h[k] = Cabin::Channel.new }
|
62
|
-
return @channels[identifier]
|
63
|
-
end # def Cabin::Channel.get
|
80
|
+
|
81
|
+
private
|
64
82
|
|
65
83
|
# Create a new logging channel.
|
66
84
|
# The default log level is 'info'
|
67
|
-
public
|
68
85
|
def initialize
|
69
86
|
@outputs = []
|
70
87
|
@data = {}
|
@@ -76,7 +93,6 @@ class Cabin::Channel
|
|
76
93
|
# Subscribe a new input
|
77
94
|
# New events will be sent to the subscriber using the '<<' method
|
78
95
|
# foo << event
|
79
|
-
public
|
80
96
|
def subscribe(output)
|
81
97
|
# Wrap ruby stdlib Logger if given.
|
82
98
|
if output.is_a?(::Logger)
|
@@ -90,19 +106,16 @@ class Cabin::Channel
|
|
90
106
|
end # def subscribe
|
91
107
|
|
92
108
|
# Set some contextual map value
|
93
|
-
public
|
94
109
|
def []=(key, value)
|
95
110
|
@data[key] = value
|
96
111
|
end # def []=
|
97
112
|
|
98
113
|
# Get a context value by name.
|
99
|
-
public
|
100
114
|
def [](key)
|
101
115
|
@data[key]
|
102
116
|
end # def []
|
103
117
|
|
104
118
|
# Remove a context value by name.
|
105
|
-
public
|
106
119
|
def remove(key)
|
107
120
|
@data.delete(key)
|
108
121
|
end # def remove
|
@@ -114,52 +127,36 @@ class Cabin::Channel
|
|
114
127
|
#
|
115
128
|
# A special key :timestamp is set at the time of this method call. The value
|
116
129
|
# is a string ISO8601 timestamp with microsecond precision.
|
117
|
-
public
|
118
130
|
def publish(data)
|
119
|
-
event = {
|
120
|
-
|
121
|
-
|
122
|
-
event.merge!(@data)
|
123
|
-
# TODO(sissel): need to refactor string->hash shoving.
|
131
|
+
event = {}
|
132
|
+
event.merge!(@data) # Merge any logger context
|
133
|
+
|
124
134
|
if data.is_a?(String)
|
125
135
|
event[:message] = data
|
126
136
|
else
|
127
137
|
event.merge!(data)
|
128
138
|
end
|
129
139
|
|
140
|
+
self.class.filters.each do |filter|
|
141
|
+
filter.call(event)
|
142
|
+
end
|
143
|
+
|
130
144
|
@outputs.each do |out|
|
131
145
|
out << event
|
132
146
|
end
|
133
147
|
end # def publish
|
134
148
|
|
135
|
-
# Start timing something.
|
136
|
-
# Returns an instance of Cabin::Timer bound to this Cabin::Channel.
|
137
|
-
# To stop the timer and immediately emit the result to this channel, invoke
|
138
|
-
# the Cabin::Timer#stop method.
|
139
|
-
public
|
140
|
-
def time(data, &block)
|
141
|
-
# TODO(sissel): need to refactor string->hash shoving.
|
142
|
-
if data.is_a?(String)
|
143
|
-
data = { :message => data }
|
144
|
-
end
|
145
|
-
|
146
|
-
timer = Cabin::Timer.new do |duration|
|
147
|
-
# TODO(sissel): Document this field
|
148
|
-
data[:duration] = duration
|
149
|
-
publish(data)
|
150
|
-
end
|
151
|
-
|
152
|
-
if block_given?
|
153
|
-
block.call
|
154
|
-
return timer.stop
|
155
|
-
else
|
156
|
-
return timer
|
157
|
-
end
|
158
|
-
end # def time
|
159
|
-
|
160
|
-
public
|
161
149
|
def context
|
162
150
|
ctx = Cabin::Context.new(self)
|
163
151
|
return ctx
|
164
152
|
end # def context
|
153
|
+
|
154
|
+
def dataify(data)
|
155
|
+
if data.is_a?(String)
|
156
|
+
data = { :message => data }
|
157
|
+
end
|
158
|
+
return data
|
159
|
+
end # def dataify
|
160
|
+
|
161
|
+
public(:initialize, :context, :subscribe, :[]=, :[], :remove, :publish, :time, :context)
|
165
162
|
end # class Cabin::Channel
|
@@ -2,23 +2,15 @@ require "cabin/namespace"
|
|
2
2
|
|
3
3
|
# ALL CAPS MEANS SERIOUS BUSINESS
|
4
4
|
module Cabin::Mixins::CAPSLOCK
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def self.extended(instance)
|
6
|
+
self.included(instance.class)
|
7
|
+
end
|
8
|
+
def self.included(klass)
|
9
|
+
klass.filter do |event|
|
10
|
+
# CAPITALIZE ALL THE STRINGS
|
11
|
+
event.each do |key, value|
|
12
|
+
event[key] = value.upcase if value.respond_to?(:upcase)
|
13
|
+
end
|
10
14
|
end
|
11
|
-
|
12
|
-
|
13
|
-
data.each do |key, value|
|
14
|
-
value.upcase! if value.respond_to?(:upcase!)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Add extra debugging bits (file, line, method) if level is debug.
|
18
|
-
debugharder(caller.collect { |c| c.upcase }, data) if @level == :debug
|
19
|
-
|
20
|
-
data[:level] = level.upcase
|
21
|
-
|
22
|
-
publish(data)
|
23
|
-
end # def log
|
24
|
-
end # module Cabin::Mixins::CAPSLOCK
|
15
|
+
end
|
16
|
+
end # MODULE CABIN::MIXINS::CAPSLOCK
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "cabin/mixins/logger"
|
3
|
+
|
4
|
+
# Colorful logging.
|
5
|
+
module Cabin::Mixins::Colors
|
6
|
+
def included(klass)
|
7
|
+
klass.extend(Cabin::Mixins::Logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
COLORS = [ :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white ]
|
11
|
+
|
12
|
+
COLORS.each do |color|
|
13
|
+
# define the color first
|
14
|
+
define_method(color) do |message, data={}|
|
15
|
+
log(message, data.merge(:color => color))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Exclamation marks mean bold. You should probably use bold all the time
|
19
|
+
# because it's awesome.
|
20
|
+
define_method("#{color}!".to_sym) do |message, data={}|
|
21
|
+
log(message, data.merge(:color => color, :bold => true))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end # module Cabin::Mixins::Colors
|
data/lib/cabin/mixins/logger.rb
CHANGED
@@ -12,6 +12,8 @@ module Cabin::Mixins::Logger
|
|
12
12
|
:debug => 4
|
13
13
|
}
|
14
14
|
|
15
|
+
BACKTRACE_RE = /([^:]+):([0-9]+):in `(.*)'/
|
16
|
+
|
15
17
|
def level=(value)
|
16
18
|
if value.respond_to?(:downcase)
|
17
19
|
@level = value.downcase.to_sym
|
@@ -59,7 +61,7 @@ module Cabin::Mixins::Logger
|
|
59
61
|
"a #{data.class.name}, I require a Hash.")
|
60
62
|
end
|
61
63
|
|
62
|
-
|
64
|
+
log_with_level(level, message, data) if send(predicate)
|
63
65
|
end
|
64
66
|
|
65
67
|
# def info?, def warn? ...
|
@@ -71,26 +73,40 @@ module Cabin::Mixins::Logger
|
|
71
73
|
end # end defining level-based log methods
|
72
74
|
|
73
75
|
private
|
74
|
-
def
|
76
|
+
def log_with_level(level, message, data={})
|
75
77
|
# Invoke 'info?' etc to ask if we should act.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
data[:level] = level
|
79
|
+
_log(message, data)
|
80
|
+
end # def log_with_level
|
81
|
+
|
82
|
+
def log(message, data={})
|
83
|
+
_log(message, data)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _log(message, data={})
|
87
|
+
case message
|
88
|
+
when Hash
|
89
|
+
data.merge!(message)
|
90
|
+
when Exception
|
91
|
+
# message is an exception
|
92
|
+
data[:message] = message.to_s
|
93
|
+
data[:exception] = message.class
|
94
|
+
data[:backtrace] = message.backtrace
|
95
|
+
else
|
96
|
+
data[:message] = message
|
80
97
|
end
|
81
98
|
|
82
99
|
# Add extra debugging bits (file, line, method) if level is debug.
|
83
|
-
debugharder(caller, data) if @level == :debug
|
100
|
+
debugharder(caller[2], data) if @level == :debug
|
84
101
|
|
85
|
-
data[:level] = level
|
86
102
|
publish(data)
|
87
103
|
end # def log
|
88
104
|
|
89
105
|
# This method is used to pull useful information about the caller
|
90
106
|
# of the logging method such as the caller's file, method, and line number.
|
91
|
-
|
92
|
-
|
93
|
-
path, line, method =
|
107
|
+
def debugharder(callinfo, data)
|
108
|
+
m = BACKTRACE_RE.match(callinfo)
|
109
|
+
path, line, method = m[1..3]
|
94
110
|
whence = $:.detect { |p| path.start_with?(p) }
|
95
111
|
if whence
|
96
112
|
# Remove the RUBYLIB path portion of the full file name
|
@@ -104,4 +120,6 @@ module Cabin::Mixins::Logger
|
|
104
120
|
data[:line] = line
|
105
121
|
data[:method] = method
|
106
122
|
end # def debugharder
|
123
|
+
|
124
|
+
public(:log)
|
107
125
|
end # module Cabin::Mixins::Logger
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "cabin/timer"
|
3
|
+
|
4
|
+
module Cabin::Mixins::Timer
|
5
|
+
# Start timing something.
|
6
|
+
# Returns an instance of Cabin::Timer bound to this Cabin::Channel.
|
7
|
+
# To stop the timer and immediately emit the result to this channel, invoke
|
8
|
+
# the Cabin::Timer#stop method.
|
9
|
+
def time(data, &block)
|
10
|
+
# TODO(sissel): need to refactor string->hash shoving.
|
11
|
+
data = dataify(data)
|
12
|
+
|
13
|
+
timer = Cabin::Timer.new do |duration|
|
14
|
+
data[:duration] = duration
|
15
|
+
publish(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
if block_given?
|
19
|
+
block.call
|
20
|
+
return timer.stop
|
21
|
+
else
|
22
|
+
return timer
|
23
|
+
end
|
24
|
+
end # def time
|
25
|
+
end # module Cabin::Mixins::Timer
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
|
3
|
+
# Timestamp events before publishing.
|
4
|
+
module Cabin::Mixins::Timestamp
|
5
|
+
def self.extended(instance)
|
6
|
+
self.included(instance.class)
|
7
|
+
end
|
8
|
+
def self.included(klass)
|
9
|
+
klass.filter do |event|
|
10
|
+
event[:timestamp] = Time.now.strftime("%Y-%m-%dT%H:%M:%S.%6N%z")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/cabin/outputs/io.rb
CHANGED
@@ -3,35 +3,84 @@ require "json"
|
|
3
3
|
|
4
4
|
# Wrap IO objects with a reasonable log output.
|
5
5
|
#
|
6
|
-
# If the IO
|
7
|
-
#
|
6
|
+
# If the IO is *not* attached to a tty (io#tty? returns false), then
|
7
|
+
# the event will be written in json format terminated by a newline:
|
8
8
|
#
|
9
|
-
# message
|
9
|
+
# { "timestamp": ..., "message": message, ... }
|
10
10
|
#
|
11
|
-
# If the IO
|
12
|
-
# representation of the event:
|
11
|
+
# If the IO is attached to a TTY, there are # human-friendly in this format:
|
13
12
|
#
|
14
|
-
#
|
13
|
+
# message {json data}
|
14
|
+
#
|
15
|
+
# Additionally, colorized output may be available. If the event has :level,
|
16
|
+
# :color, or :bold. Any of the Cabin::Mixins::Logger methods (info, error, etc)
|
17
|
+
# will result in colored output. See the LEVELMAP for the mapping of levels
|
18
|
+
# to colors.
|
15
19
|
class Cabin::Outputs::IO
|
16
|
-
|
20
|
+
# Mapping of color/style names to ANSI control values
|
21
|
+
CODEMAP = {
|
22
|
+
:normal => 0,
|
23
|
+
:bold => 1,
|
24
|
+
:black => 30,
|
25
|
+
:red => 31,
|
26
|
+
:green => 32,
|
27
|
+
:yellow => 33,
|
28
|
+
:blue => 34,
|
29
|
+
:magenta => 35,
|
30
|
+
:cyan => 36,
|
31
|
+
:white => 37
|
32
|
+
}
|
33
|
+
|
34
|
+
# Map of log levels to colors
|
35
|
+
LEVELMAP = {
|
36
|
+
:fatal => :red,
|
37
|
+
:error => :red,
|
38
|
+
:warn => :yellow,
|
39
|
+
:info => :green, # default color
|
40
|
+
:debug => :cyan,
|
41
|
+
}
|
42
|
+
|
17
43
|
def initialize(io)
|
18
44
|
@io = io
|
19
45
|
end # def initialize
|
20
46
|
|
21
47
|
# Receive an event
|
22
|
-
public
|
23
48
|
def <<(event)
|
24
|
-
if
|
25
|
-
data = event.clone
|
26
|
-
# delete things from the 'data' portion that's not really data.
|
27
|
-
data.delete(:message)
|
28
|
-
data.delete(:timestamp)
|
29
|
-
message = "#{event[:message]} #{data.to_json}"
|
30
|
-
|
31
|
-
@io.puts(message)
|
32
|
-
@io.flush if @io.tty?
|
33
|
-
else
|
49
|
+
if !@io.tty?
|
34
50
|
@io.puts(event.to_json)
|
51
|
+
else
|
52
|
+
tty_write(event)
|
35
53
|
end
|
36
54
|
end # def <<
|
55
|
+
|
56
|
+
private
|
57
|
+
def tty_write(event)
|
58
|
+
# The io is attached to a tty, so make pretty colors.
|
59
|
+
# delete things from the 'data' portion that's not really data.
|
60
|
+
data = event.clone
|
61
|
+
data.delete(:message)
|
62
|
+
data.delete(:timestamp)
|
63
|
+
|
64
|
+
color = data.delete(:color)
|
65
|
+
# :bold is expected to be truthy
|
66
|
+
bold = data.delete(:bold) ? :bold : nil
|
67
|
+
|
68
|
+
# Make 'error' and other log levels have color
|
69
|
+
if color.nil? and data[:level]
|
70
|
+
color = LEVELMAP[data[:level]]
|
71
|
+
end
|
72
|
+
|
73
|
+
if data.empty?
|
74
|
+
message = [event[:message]]
|
75
|
+
else
|
76
|
+
message = ["#{event[:message]} #{data.to_json}"]
|
77
|
+
end
|
78
|
+
message.unshift("\e[#{CODEMAP[color.to_sym]}m") if !color.nil?
|
79
|
+
message.unshift("\e[#{CODEMAP[bold]}m") if !bold.nil?
|
80
|
+
message.push("\e[#{CODEMAP[:normal]}m") if !(bold.nil? and color.nil?)
|
81
|
+
@io.puts(message.join(""))
|
82
|
+
@io.flush
|
83
|
+
end # def <<
|
84
|
+
|
85
|
+
public(:initialize, :<<)
|
37
86
|
end # class Cabin::Outputs::StdlibLogger
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cabin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
16
|
-
requirement: &
|
16
|
+
requirement: &17116580 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *17116580
|
25
25
|
description: This is an experiment to try and make logging more flexible and more
|
26
26
|
consumable. Plain text logs are bullshit, let's emit structured and contextual logs.
|
27
27
|
Metrics, too!
|
@@ -40,8 +40,11 @@ files:
|
|
40
40
|
- lib/cabin/publisher.rb
|
41
41
|
- lib/cabin/channel.rb
|
42
42
|
- lib/cabin/mixins/logger.rb
|
43
|
+
- lib/cabin/mixins/colors.rb
|
43
44
|
- lib/cabin/mixins/dragons.rb
|
44
45
|
- lib/cabin/mixins/CAPSLOCK.rb
|
46
|
+
- lib/cabin/mixins/timer.rb
|
47
|
+
- lib/cabin/mixins/timestamp.rb
|
45
48
|
- lib/cabin/timer.rb
|
46
49
|
- lib/cabin/metric.rb
|
47
50
|
- lib/cabin/metrics.rb
|