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.
@@ -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
@@ -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
@@ -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,3 @@
1
+ require "steno/sink/base"
2
+ require "steno/sink/io"
3
+ require "steno/sink/syslog"
@@ -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,3 @@
1
+ module Steno
2
+ VERSION = "0.0.3"
3
+ 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