steno 0.0.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/.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
|