steno 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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