steno 1.2.2-x86-mingw32
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.
- 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
|