steno 1.2.2-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +7136 -0
- data/README.md +78 -0
- data/Rakefile +16 -0
- data/bin/steno-prettify +99 -0
- data/lib/steno.rb +133 -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 +97 -0
- data/lib/steno/context.rb +59 -0
- data/lib/steno/core_ext.rb +11 -0
- data/lib/steno/errors.rb +3 -0
- data/lib/steno/http_handler.rb +41 -0
- data/lib/steno/json_prettifier.rb +131 -0
- data/lib/steno/log_level.rb +24 -0
- data/lib/steno/logger.rb +174 -0
- data/lib/steno/record.rb +41 -0
- data/lib/steno/sink.rb +6 -0
- data/lib/steno/sink/base.rb +38 -0
- data/lib/steno/sink/counter.rb +44 -0
- data/lib/steno/sink/eventlog.rb +46 -0
- data/lib/steno/sink/fluentd.rb +31 -0
- data/lib/steno/sink/io.rb +72 -0
- data/lib/steno/sink/syslog.rb +59 -0
- data/lib/steno/tagged_logger.rb +59 -0
- data/lib/steno/version.rb +3 -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/config_spec.rb +221 -0
- data/spec/unit/context_spec.rb +62 -0
- data/spec/unit/core_ext_spec.rb +38 -0
- data/spec/unit/http_handler_spec.rb +73 -0
- data/spec/unit/json_codec_spec.rb +48 -0
- data/spec/unit/json_prettifier_spec.rb +84 -0
- data/spec/unit/log_level_spec.rb +19 -0
- data/spec/unit/logger_spec.rb +101 -0
- data/spec/unit/record_spec.rb +30 -0
- data/spec/unit/sink/counter_spec.rb +27 -0
- data/spec/unit/sink/eventlog_spec.rb +41 -0
- data/spec/unit/sink/fluentd_spec.rb +46 -0
- data/spec/unit/sink/io_spec.rb +111 -0
- data/spec/unit/sink/syslog_spec.rb +74 -0
- data/spec/unit/steno_spec.rb +86 -0
- data/spec/unit/tagged_logger_spec.rb +33 -0
- data/steno-1.2.1.gem +0 -0
- data/steno.gemspec +41 -0
- metadata +224 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require "fiber"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
class Fiber
|
5
|
+
def __steno_context_data__
|
6
|
+
@__steno_context_data__ ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def __steno_clear_context_data__
|
10
|
+
@__steno_context_data__ = {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Steno
|
15
|
+
end
|
16
|
+
|
17
|
+
module Steno::Context
|
18
|
+
class Base
|
19
|
+
def data
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Null < Base
|
29
|
+
def data
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ThreadLocal < Base
|
39
|
+
THREAD_LOCAL_KEY = "__steno_locals__"
|
40
|
+
|
41
|
+
def data
|
42
|
+
Thread.current[THREAD_LOCAL_KEY] ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear
|
46
|
+
Thread.current[THREAD_LOCAL_KEY] = {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class FiberLocal < Base
|
51
|
+
def data
|
52
|
+
Fiber.current.__steno_context_data__
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear
|
56
|
+
Fiber.current.__steno_clear_context_data__
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/steno/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "steno"
|
2
|
+
|
3
|
+
require "grape"
|
4
|
+
|
5
|
+
module Steno
|
6
|
+
end
|
7
|
+
|
8
|
+
class Steno::HttpHandler < Grape::API
|
9
|
+
format :json
|
10
|
+
|
11
|
+
resource :loggers do
|
12
|
+
get :levels do
|
13
|
+
Steno.logger_level_snapshot
|
14
|
+
end
|
15
|
+
|
16
|
+
put :levels do
|
17
|
+
missing = [:regexp, :level].select { |p| !params.key?(p) }.map(&:to_s)
|
18
|
+
|
19
|
+
if !missing.empty?
|
20
|
+
error!("Missing query parameters: #{missing}", 400)
|
21
|
+
end
|
22
|
+
|
23
|
+
regexp = nil
|
24
|
+
begin
|
25
|
+
regexp = Regexp.new(params[:regexp])
|
26
|
+
rescue => e
|
27
|
+
error!("Invalid regexp", 400)
|
28
|
+
end
|
29
|
+
|
30
|
+
level = params[:level].to_sym
|
31
|
+
if !Steno::Logger::LEVELS.key?(level)
|
32
|
+
levels = Steno::Logger::LEVELS.keys.map(&:to_s)
|
33
|
+
error!("Unknown level: #{level}. Supported levels are: #{levels}", 400)
|
34
|
+
end
|
35
|
+
|
36
|
+
Steno.set_logger_regexp(regexp, level)
|
37
|
+
|
38
|
+
"ok"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "set"
|
3
|
+
require "yajl"
|
4
|
+
|
5
|
+
module Steno
|
6
|
+
end
|
7
|
+
|
8
|
+
class Steno::JsonPrettifier
|
9
|
+
FIELD_ORDER = %w[
|
10
|
+
timestamp
|
11
|
+
source
|
12
|
+
process_id
|
13
|
+
thread_id
|
14
|
+
fiber_id
|
15
|
+
location
|
16
|
+
data
|
17
|
+
log_level
|
18
|
+
message
|
19
|
+
]
|
20
|
+
|
21
|
+
MIN_COL_WIDTH = 14
|
22
|
+
|
23
|
+
class ParseError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(excluded_fields = [])
|
27
|
+
@time_format = "%Y-%m-%d %H:%M:%S.%6N"
|
28
|
+
@excluded_fields = Set.new(excluded_fields)
|
29
|
+
@max_src_len = MIN_COL_WIDTH
|
30
|
+
end
|
31
|
+
|
32
|
+
def prettify_line(line)
|
33
|
+
begin
|
34
|
+
json_record = Yajl::Parser.parse(line)
|
35
|
+
rescue Yajl::ParseError => e
|
36
|
+
raise ParseError, e.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
format_record(json_record)
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def format_record(record)
|
45
|
+
record ||= {}
|
46
|
+
fields = []
|
47
|
+
|
48
|
+
FIELD_ORDER.each do |field_name|
|
49
|
+
next if @excluded_fields.include?(field_name)
|
50
|
+
|
51
|
+
exists = nil
|
52
|
+
pred_meth = "check_#{field_name}".to_sym
|
53
|
+
if respond_to?(pred_meth)
|
54
|
+
exists = send(pred_meth, record)
|
55
|
+
elsif record.respond_to?(:has_key?)
|
56
|
+
exists = record.has_key?(field_name)
|
57
|
+
else
|
58
|
+
msg = "Expected the record to be a hash, but received: #{record.class}."
|
59
|
+
raise ParseError, msg
|
60
|
+
end
|
61
|
+
|
62
|
+
if exists
|
63
|
+
fields << send("format_#{field_name}".to_sym, record)
|
64
|
+
else
|
65
|
+
fields << "-"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
fields.join(" ") + "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_timestamp(record)
|
73
|
+
Time.at(record["timestamp"]).strftime(@time_format)
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_source(record)
|
77
|
+
@max_src_len = [@max_src_len, record["source"].length].max
|
78
|
+
record["source"].ljust(@max_src_len)
|
79
|
+
end
|
80
|
+
|
81
|
+
def format_process_id(record)
|
82
|
+
"pid=%-5s" % [record["process_id"]]
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_thread_id(record)
|
86
|
+
"tid=%s" % [shortid(record["thread_id"])]
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_fiber_id(record)
|
90
|
+
"fid=%s" % [shortid(record["fiber_id"])]
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_location(record)
|
94
|
+
%w[file lineno method].reduce(true) { |ok, k| ok && record.has_key?(k) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_location(record)
|
98
|
+
parts = record["file"].split("/")
|
99
|
+
|
100
|
+
trimmed_filename = nil
|
101
|
+
if parts.size == 1
|
102
|
+
trimmed_filename = parts[0]
|
103
|
+
else
|
104
|
+
trimmed_filename = parts.slice(-2, 2).join("/")
|
105
|
+
end
|
106
|
+
|
107
|
+
"%s/%s:%s" % [trimmed_filename, record["method"], record["lineno"]]
|
108
|
+
end
|
109
|
+
|
110
|
+
def check_data(record)
|
111
|
+
record["data"].is_a?(Hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
def format_data(record)
|
115
|
+
record["data"].map { |k, v| "#{k}=#{v}" }.join(",")
|
116
|
+
end
|
117
|
+
|
118
|
+
def format_log_level(record)
|
119
|
+
"%7s" % [record["log_level"].upcase]
|
120
|
+
end
|
121
|
+
|
122
|
+
def format_message(record)
|
123
|
+
"-- %s" % [record["message"]]
|
124
|
+
end
|
125
|
+
|
126
|
+
def shortid(data)
|
127
|
+
return "-" if data.nil?
|
128
|
+
digest = Digest::MD5.hexdigest(data.to_s)
|
129
|
+
digest[0, 4]
|
130
|
+
end
|
131
|
+
end
|
@@ -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,174 @@
|
|
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
|
+
@min_level_lock.synchronize { level <= @min_level }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convenience method for logging an exception, along with its backtrace.
|
111
|
+
#
|
112
|
+
# @param [Exception] ex
|
113
|
+
|
114
|
+
# @return [nil]
|
115
|
+
def log_exception(ex, user_data = {})
|
116
|
+
warn("Caught exception: #{ex}", user_data.merge(:backtrace => ex.backtrace))
|
117
|
+
end
|
118
|
+
|
119
|
+
# Adds a record to the configured sinks.
|
120
|
+
#
|
121
|
+
# @param [Symbol] level_name The level associated with the record
|
122
|
+
# @param [String] message
|
123
|
+
# @param [Hash] user_data
|
124
|
+
#
|
125
|
+
# @return [nil]
|
126
|
+
def log(level_name, message = nil, user_data = nil, &blk)
|
127
|
+
return unless level_active?(level_name)
|
128
|
+
|
129
|
+
message = yield if block_given?
|
130
|
+
|
131
|
+
callstack = caller
|
132
|
+
loc = parse_record_loc(callstack)
|
133
|
+
|
134
|
+
data = @context.data.merge(user_data || {})
|
135
|
+
|
136
|
+
record = Steno::Record.new(@name, level_name, message, loc, data)
|
137
|
+
|
138
|
+
@sinks.each { |sink| sink.add_record(record) }
|
139
|
+
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns a proxy that will emit the supplied user data along with each
|
144
|
+
# log record.
|
145
|
+
#
|
146
|
+
# @param [Hash] user_data
|
147
|
+
#
|
148
|
+
# @return [Steno::TaggedLogger]
|
149
|
+
def tag(user_data = {})
|
150
|
+
Steno::TaggedLogger.new(self, user_data)
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def parse_record_loc(callstack)
|
156
|
+
file, lineno, method = nil, nil, nil
|
157
|
+
|
158
|
+
callstack.each do |frame|
|
159
|
+
next if frame =~ /logger\.rb/
|
160
|
+
|
161
|
+
file, lineno, method = frame.split(":")
|
162
|
+
|
163
|
+
lineno = lineno.to_i
|
164
|
+
|
165
|
+
if method =~ /in `([^']+)/
|
166
|
+
method = $1
|
167
|
+
end
|
168
|
+
|
169
|
+
break
|
170
|
+
end
|
171
|
+
|
172
|
+
[file, lineno, method]
|
173
|
+
end
|
174
|
+
end
|