steno 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +14 -0
- data/lib/steno.rb +134 -0
- data/lib/steno/codec.rb +2 -0
- data/lib/steno/codec/base.rb +34 -0
- data/lib/steno/codec/json.rb +36 -0
- data/lib/steno/config.rb +56 -0
- data/lib/steno/context.rb +59 -0
- data/lib/steno/core_ext.rb +5 -0
- data/lib/steno/errors.rb +3 -0
- data/lib/steno/http_handler.rb +42 -0
- data/lib/steno/log_level.rb +24 -0
- data/lib/steno/logger.rb +177 -0
- data/lib/steno/record.rb +39 -0
- data/lib/steno/sink.rb +3 -0
- data/lib/steno/sink/base.rb +36 -0
- data/lib/steno/sink/io.rb +48 -0
- data/lib/steno/sink/syslog.rb +38 -0
- data/lib/steno/tagged_logger.rb +59 -0
- data/lib/steno/version.rb +3 -0
- data/spec/core_ext_spec.rb +15 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/barrier.rb +22 -0
- data/spec/support/null_sink.rb +17 -0
- data/spec/support/shared_context_specs.rb +7 -0
- data/spec/unit/context_spec.rb +62 -0
- data/spec/unit/http_handler_spec.rb +73 -0
- data/spec/unit/io_sink_spec.rb +26 -0
- data/spec/unit/json_codec_spec.rb +48 -0
- data/spec/unit/log_level_spec.rb +18 -0
- data/spec/unit/logger_spec.rb +91 -0
- data/spec/unit/record_spec.rb +21 -0
- data/spec/unit/steno_spec.rb +86 -0
- data/spec/unit/syslog_sink_spec.rb +27 -0
- data/spec/unit/tagged_logger_spec.rb +33 -0
- data/steno.gemspec +26 -0
- metadata +195 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Steno
|
2
|
+
end
|
3
|
+
|
4
|
+
class Steno::LogLevel
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :priority
|
9
|
+
|
10
|
+
# @param [String] name "info", "debug", etc.
|
11
|
+
# @param [Integer] priority "info" > "debug", etc.
|
12
|
+
def initialize(name, priority)
|
13
|
+
@name = name
|
14
|
+
@priority = priority
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@name.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
@priority <=> other.priority
|
23
|
+
end
|
24
|
+
end
|
data/lib/steno/logger.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
require "steno/errors"
|
4
|
+
require "steno/log_level"
|
5
|
+
|
6
|
+
module Steno
|
7
|
+
end
|
8
|
+
|
9
|
+
class Steno::Logger
|
10
|
+
LEVELS = {
|
11
|
+
:off => Steno::LogLevel.new(:off, 0),
|
12
|
+
:fatal => Steno::LogLevel.new(:fatal, 1),
|
13
|
+
:error => Steno::LogLevel.new(:error, 5),
|
14
|
+
:warn => Steno::LogLevel.new(:warn, 10),
|
15
|
+
:info => Steno::LogLevel.new(:info, 15),
|
16
|
+
:debug => Steno::LogLevel.new(:debug, 16),
|
17
|
+
:debug1 => Steno::LogLevel.new(:debug1, 17),
|
18
|
+
:debug2 => Steno::LogLevel.new(:debug2, 18),
|
19
|
+
:all => Steno::LogLevel.new(:all, 30),
|
20
|
+
}
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# The following helpers are used to create a new scope for binding the log
|
24
|
+
# level.
|
25
|
+
|
26
|
+
def define_log_method(name)
|
27
|
+
define_method(name) { |*args, &blk| log(name, *args, &blk) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def define_logf_method(name)
|
31
|
+
define_method(name.to_s + "f") { |fmt, *args| log(name, fmt % args) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_level_active_predicate(name)
|
35
|
+
define_method(name.to_s + "?") { level_active?(name) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def lookup_level(name)
|
39
|
+
level = LEVELS[name]
|
40
|
+
|
41
|
+
if level.nil?
|
42
|
+
raise Steno::Error.new("Unknown level: #{name}")
|
43
|
+
end
|
44
|
+
|
45
|
+
level
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is magic, however, it's vastly simpler than declaring each method
|
50
|
+
# manually.
|
51
|
+
LEVELS.each do |name, _|
|
52
|
+
# Define #debug, for example
|
53
|
+
define_log_method(name)
|
54
|
+
|
55
|
+
# Define #debugf, for example
|
56
|
+
define_logf_method(name)
|
57
|
+
|
58
|
+
# Define #debug?, for example. These are provided to ensure compatibility
|
59
|
+
# with Ruby's standard library Logger class.
|
60
|
+
define_level_active_predicate(name)
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :name
|
64
|
+
|
65
|
+
# @param [String] name The logger name.
|
66
|
+
# @param [Array<Steno::Sink::Base>] sinks
|
67
|
+
# @param [Hash] opts
|
68
|
+
# @option opts [Symbol] :level The minimum level for which this logger will
|
69
|
+
# emit log records. Defaults to :info.
|
70
|
+
# @option opts [Steno::Context] :context
|
71
|
+
def initialize(name, sinks, opts = {})
|
72
|
+
@name = name
|
73
|
+
@min_level = self.class.lookup_level(opts[:level] || :info)
|
74
|
+
@min_level_lock = Mutex.new
|
75
|
+
@sinks = sinks
|
76
|
+
@context = opts[:context] || Steno::Context::Null.new
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sets the minimum level for which records will be added to sinks.
|
80
|
+
#
|
81
|
+
# @param [Symbol] level_name The level name
|
82
|
+
#
|
83
|
+
# @return [nil]
|
84
|
+
def level=(level_name)
|
85
|
+
level = self.class.lookup_level(level_name)
|
86
|
+
|
87
|
+
@min_level_lock.synchronize { @min_level = level }
|
88
|
+
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the name of the current log level
|
93
|
+
#
|
94
|
+
# @return [Symbol]
|
95
|
+
def level
|
96
|
+
@min_level_lock.synchronize { @min_level.name }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns whether or not records for the given level would be forwarded to
|
100
|
+
# sinks.
|
101
|
+
#
|
102
|
+
# @param [Symbol] level_name
|
103
|
+
#
|
104
|
+
# @return [true || false]
|
105
|
+
def level_active?(level_name)
|
106
|
+
level = self.class.lookup_level(level_name)
|
107
|
+
|
108
|
+
@min_level_lock.synchronize { level <= @min_level }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Convenience method for logging an exception, along with its backtrace.
|
112
|
+
#
|
113
|
+
# @param [Exception] ex
|
114
|
+
|
115
|
+
# @return [nil]
|
116
|
+
def log_exception(ex, user_data = {})
|
117
|
+
warn("Caught exception: #{ex}", user_data.merge(:backtrace => ex.backtrace))
|
118
|
+
end
|
119
|
+
|
120
|
+
# Adds a record to the configured sinks.
|
121
|
+
#
|
122
|
+
# @param [Symbol] level_name The level associated with the record
|
123
|
+
# @param [String] message
|
124
|
+
# @param [Hash] user_data
|
125
|
+
#
|
126
|
+
# @return [nil]
|
127
|
+
def log(level_name, message = nil, user_data = nil, &blk)
|
128
|
+
return unless level_active?(level_name)
|
129
|
+
|
130
|
+
level = self.class.lookup_level(level_name)
|
131
|
+
|
132
|
+
message = yield if block_given?
|
133
|
+
|
134
|
+
callstack = caller
|
135
|
+
loc = parse_record_loc(callstack)
|
136
|
+
|
137
|
+
data = @context.data.merge(user_data || {})
|
138
|
+
|
139
|
+
record = Steno::Record.new(@name, level, message, loc, data)
|
140
|
+
|
141
|
+
@sinks.each { |sink| sink.add_record(record) }
|
142
|
+
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a proxy that will emit the supplied user data along with each
|
147
|
+
# log record.
|
148
|
+
#
|
149
|
+
# @param [Hash] user_data
|
150
|
+
#
|
151
|
+
# @return [Steno::TaggedLogger]
|
152
|
+
def tag(user_data = {})
|
153
|
+
Steno::TaggedLogger.new(self, user_data)
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def parse_record_loc(callstack)
|
159
|
+
file, lineno, method = nil, nil, nil
|
160
|
+
|
161
|
+
callstack.each do |frame|
|
162
|
+
next if frame =~ /logger\.rb/
|
163
|
+
|
164
|
+
file, lineno, method = frame.split(":")
|
165
|
+
|
166
|
+
lineno = lineno.to_i
|
167
|
+
|
168
|
+
if method =~ /in `([^']+)/
|
169
|
+
method = $1
|
170
|
+
end
|
171
|
+
|
172
|
+
break
|
173
|
+
end
|
174
|
+
|
175
|
+
[file, lineno, method]
|
176
|
+
end
|
177
|
+
end
|
data/lib/steno/record.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
module Steno
|
5
|
+
end
|
6
|
+
|
7
|
+
class Steno::Record
|
8
|
+
|
9
|
+
attr_reader :timestamp
|
10
|
+
attr_reader :message
|
11
|
+
attr_reader :log_level
|
12
|
+
attr_reader :source
|
13
|
+
attr_reader :data
|
14
|
+
attr_reader :thread_id
|
15
|
+
attr_reader :fiber_id
|
16
|
+
attr_reader :process_id
|
17
|
+
attr_reader :file
|
18
|
+
attr_reader :lineno
|
19
|
+
attr_reader :method
|
20
|
+
|
21
|
+
# @param [String] source Identifies message source.
|
22
|
+
# @param [Symbol] log_level
|
23
|
+
# @param [String] message
|
24
|
+
# @param [Array] loc Location where the record was generated.
|
25
|
+
# Format is [<filename>, <lineno>, <method>].
|
26
|
+
# @param [Hash] data User-supplied data
|
27
|
+
def initialize(source, log_level, message, loc = [], data = {})
|
28
|
+
@timestamp = Time.now
|
29
|
+
@source = source
|
30
|
+
@log_level = log_level
|
31
|
+
@message = message
|
32
|
+
@data = {}.merge(data)
|
33
|
+
@thread_id = Thread.current.object_id
|
34
|
+
@fiber_id = Fiber.current.object_id
|
35
|
+
@process_id = Process.pid
|
36
|
+
|
37
|
+
@file, @lineno, @method = loc
|
38
|
+
end
|
39
|
+
end
|
data/lib/steno/sink.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Steno
|
4
|
+
module Sink
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Sinks represent the final destination for log records. They abstract storage
|
9
|
+
# mediums (like files) and transport layers (like sockets).
|
10
|
+
class Steno::Sink::Base
|
11
|
+
|
12
|
+
attr_accessor :codec
|
13
|
+
|
14
|
+
# @param [Steno::Codec::Base] formatter Transforms log records to their
|
15
|
+
# raw, string-based representation that will be written to the underlying
|
16
|
+
# sink.
|
17
|
+
def initialize(codec = nil)
|
18
|
+
@codec = codec
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds a record to be flushed at a later time.
|
22
|
+
#
|
23
|
+
# @param [Hash] record
|
24
|
+
#
|
25
|
+
# @return [nil]
|
26
|
+
def add_record(record)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# Flushes any buffered records.
|
31
|
+
#
|
32
|
+
# @return [nil]
|
33
|
+
def flush
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "steno/sink/base"
|
2
|
+
|
3
|
+
module Steno
|
4
|
+
module Sink
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Steno::Sink::IO < Steno::Sink::Base
|
9
|
+
class << self
|
10
|
+
# Returns a new sink configured to append to the file at path.
|
11
|
+
#
|
12
|
+
# @param [String] path
|
13
|
+
# @param [True, False] autoflush If true, encoded records will not be
|
14
|
+
# buffered by Ruby.
|
15
|
+
#
|
16
|
+
# @return [Steno::Sink::IO]
|
17
|
+
def for_file(path, autoflush = true)
|
18
|
+
io = File.open(path, "a+")
|
19
|
+
|
20
|
+
io.sync = autoflush
|
21
|
+
|
22
|
+
new(io)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [IO] io The IO object that will be written to
|
27
|
+
# @param [Steno::Codec::Base] codec
|
28
|
+
def initialize(io, codec = nil)
|
29
|
+
super(codec)
|
30
|
+
|
31
|
+
@io_lock = Mutex.new
|
32
|
+
@io = io
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_record(record)
|
36
|
+
bytes = @codec.encode_record(record)
|
37
|
+
|
38
|
+
@io_lock.synchronize { @io.write(bytes) }
|
39
|
+
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def flush
|
44
|
+
@io_lock.synchronize { @io.flush }
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "steno/sink/base"
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
require "thread"
|
5
|
+
require "syslog"
|
6
|
+
|
7
|
+
class Steno::Sink::Syslog < Steno::Sink::Base
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
LOG_LEVEL_MAP = {
|
11
|
+
:fatal => Syslog::LOG_CRIT,
|
12
|
+
:error => Syslog::LOG_ERR,
|
13
|
+
:warn => Syslog::LOG_WARNING,
|
14
|
+
:info => Syslog::LOG_INFO,
|
15
|
+
:debug => Syslog::LOG_DEBUG,
|
16
|
+
:debug1 => Syslog::LOG_DEBUG,
|
17
|
+
:debug2 => Syslog::LOG_DEBUG,
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
super
|
22
|
+
|
23
|
+
@syslog = nil
|
24
|
+
@syslog_lock = Mutex.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def open(identity)
|
28
|
+
@identity = identity
|
29
|
+
@syslog = Syslog.open(@identity, Syslog::LOG_PID, Syslog::LOG_USER)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_record(record)
|
33
|
+
msg = @codec.encode_record(record)
|
34
|
+
pri = LOG_LEVEL_MAP[record.log_level]
|
35
|
+
@syslog_lock.synchronize { @syslog.log(pri, "%s", msg) }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "steno/logger"
|
2
|
+
|
3
|
+
module Steno
|
4
|
+
end
|
5
|
+
|
6
|
+
# Provides a proxy that allows persistent user data
|
7
|
+
class Steno::TaggedLogger
|
8
|
+
|
9
|
+
attr_reader :proxied_logger
|
10
|
+
attr_accessor :user_data
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# The following helpers are used to create a new scope for binding the log
|
14
|
+
# level.
|
15
|
+
|
16
|
+
def define_log_method(name)
|
17
|
+
define_method(name) { |*args, &blk| log(name, *args, &blk) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_logf_method(name)
|
21
|
+
define_method(name.to_s + "f") { |fmt, *args| log(name, fmt % args) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Steno::Logger::LEVELS.each do |name, _|
|
26
|
+
# Define #debug, for example
|
27
|
+
define_log_method(name)
|
28
|
+
|
29
|
+
# Define #debugf, for example
|
30
|
+
define_logf_method(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(proxied_logger, user_data = {})
|
34
|
+
@proxied_logger = proxied_logger
|
35
|
+
@user_data = user_data
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method, *args, &blk)
|
39
|
+
@proxied_logger.send(method, *args, &blk)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see Steno::Logger#log
|
43
|
+
def log(level_name, message = nil, user_data = nil, &blk)
|
44
|
+
ud = @user_data.merge(user_data || {})
|
45
|
+
|
46
|
+
@proxied_logger.log(level_name, message, ud, &blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @see Steno::Logger#log_exception
|
50
|
+
def log_exception(ex, user_data = {})
|
51
|
+
ud = @user_data.merge(user_data || {})
|
52
|
+
|
53
|
+
@proxied_logger.log_exception(ex, ud)
|
54
|
+
end
|
55
|
+
|
56
|
+
def tag(new_user_data = {})
|
57
|
+
Steno::TaggedLogger.new(proxied_logger, user_data.merge(new_user_data))
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Foo
|
4
|
+
class Bar
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Class do
|
9
|
+
describe "#logger" do
|
10
|
+
it "should request a logger named after the class" do
|
11
|
+
Steno.should_receive(:logger).with("Foo::Bar")
|
12
|
+
x = Foo::Bar.logger
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|