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
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Steno
|
2
|
+
Steno is a lightweight, modular logging library written specifically to support
|
3
|
+
Cloud Foundry.
|
4
|
+
|
5
|
+
## Concepts
|
6
|
+
|
7
|
+
Steno is composed of three main classes: loggers, sinks, and formatters. Loggers
|
8
|
+
are the main entry point for Steno. They consume user input, create structured
|
9
|
+
records, and forward said records to the configured sinks. Sinks are the
|
10
|
+
ultimate destination for log records. They transform a structured record into
|
11
|
+
a string via a formatter and then typically write the transformed string to
|
12
|
+
another transport.
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
To use steno, you must configure one or more 'sinks', a 'codec' and a 'context'.
|
17
|
+
If you don't provide a codec, steno will encode your logs as JSON.
|
18
|
+
|
19
|
+
For example:
|
20
|
+
|
21
|
+
config = Steno::Config.new(
|
22
|
+
:sinks => [Steno::Sink::IO.new(STDOUT)],
|
23
|
+
:codec => Steno::Codec::Json.new,
|
24
|
+
:context => Steno::Context::ThreadLocal.new)
|
25
|
+
|
26
|
+
### from YAML file
|
27
|
+
|
28
|
+
Alternatively, Steno can read its configuration from a YAML file in the following format:
|
29
|
+
|
30
|
+
```yaml
|
31
|
+
# config.yml
|
32
|
+
---
|
33
|
+
logging:
|
34
|
+
file: /some/path # Optional - path a log file
|
35
|
+
max_retries: 3 # Optional - number of times to retry if a file write fails.
|
36
|
+
syslog: some_syslog.id # Optional - only works on *nix systems
|
37
|
+
eventlog: true # Optional - only works on Windows
|
38
|
+
fluentd: # Optional
|
39
|
+
host: fluentd.host
|
40
|
+
port: 9999
|
41
|
+
level: debug # Optional - Minimum log level that will be written.
|
42
|
+
# Defaults to 'info'
|
43
|
+
```
|
44
|
+
Then, in your code:
|
45
|
+
```ruby
|
46
|
+
config = Steno::Config.from_file("path/to/config.yml")
|
47
|
+
```
|
48
|
+
|
49
|
+
With this configuration method, if neither `file`, `syslog` or `fluentd` are provided,
|
50
|
+
steno will use its stdout as its sink. Also, note that the top-level field `logging` is required.
|
51
|
+
|
52
|
+
### from Hash
|
53
|
+
|
54
|
+
As a third option, steno can be configured using a hash with the same structure as the above
|
55
|
+
YAML file (without the top-level `logging` key):
|
56
|
+
```ruby
|
57
|
+
config = Steno::Config.from_hash(config_hash)
|
58
|
+
```
|
59
|
+
|
60
|
+
## Usage
|
61
|
+
|
62
|
+
Steno.init(config)
|
63
|
+
logger = Steno.logger("test")
|
64
|
+
logger.info("Hello world!")
|
65
|
+
|
66
|
+
### Log levels
|
67
|
+
|
68
|
+
LEVEL NUMERIC RANKING
|
69
|
+
off 0
|
70
|
+
fatal 1
|
71
|
+
error 5
|
72
|
+
warn 10
|
73
|
+
info 15
|
74
|
+
debug 16
|
75
|
+
debug1 17
|
76
|
+
debug2 18
|
77
|
+
all 30
|
78
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "ci/reporter/rake/rspec"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc "Run all specs"
|
8
|
+
RSpec::Core::RakeTask.new("spec") do |t|
|
9
|
+
t.rspec_opts = %w[--color --format documentation]
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Run all specs and provide output for ci"
|
13
|
+
RSpec::Core::RakeTask.new("spec:ci" => "ci:setup:rspec") do |t|
|
14
|
+
t.rspec_opts = %w[--no-color --format documentation]
|
15
|
+
end
|
16
|
+
|
data/bin/steno-prettify
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "yajl"
|
5
|
+
|
6
|
+
require "steno/json_prettifier"
|
7
|
+
|
8
|
+
Signal.trap("INT") { exit }
|
9
|
+
|
10
|
+
def prettify_io(io, prettifier, ignore_parse_error = false)
|
11
|
+
lineno = 1
|
12
|
+
json_regex = '([^{]*)({.*})'
|
13
|
+
|
14
|
+
io.sync = true
|
15
|
+
|
16
|
+
io.each_line do |line|
|
17
|
+
begin
|
18
|
+
match_data = line.match(json_regex) || []
|
19
|
+
prefix = match_data[1]
|
20
|
+
json = match_data[2]
|
21
|
+
|
22
|
+
prettified = prettifier.prettify_line(json)
|
23
|
+
print prefix + prettified
|
24
|
+
rescue Steno::JsonPrettifier::ParseError => e
|
25
|
+
if ignore_parse_error
|
26
|
+
print line
|
27
|
+
else
|
28
|
+
STDERR.puts "steno-prettify: Malformed json at line #{lineno}, '#{line.strip}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
lineno += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def prettify_file(path, prettifier, ignore_parse_error = false)
|
37
|
+
begin
|
38
|
+
f = File.open(path, "r")
|
39
|
+
rescue => e
|
40
|
+
STDERR.puts "steno-prettify: Failed opening '#{path}', #{e}"
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
prettify_io(f, prettifier, ignore_parse_error)
|
45
|
+
|
46
|
+
ensure
|
47
|
+
f.close if f
|
48
|
+
end
|
49
|
+
|
50
|
+
excluded_fields = []
|
51
|
+
ignore_parse_error = false
|
52
|
+
|
53
|
+
option_parser = OptionParser.new do |op|
|
54
|
+
op.banner =<<EOT
|
55
|
+
Usage: steno-prettify [OPTS] [FILE...]
|
56
|
+
|
57
|
+
Parses json formatted log lines from FILE(s), or stdin,
|
58
|
+
and displays a more human friendly version of each line to stdout.
|
59
|
+
|
60
|
+
Examples (shamelessly stolen from `man cat`):
|
61
|
+
|
62
|
+
steno-prettify f - g
|
63
|
+
Prettify f's contents, then standard input, then g's contents.
|
64
|
+
|
65
|
+
steno-prettify
|
66
|
+
Prettify contents of stdin.
|
67
|
+
|
68
|
+
Options:
|
69
|
+
EOT
|
70
|
+
|
71
|
+
op.on("-h", "--help", "Display help") do
|
72
|
+
puts op
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
op.on("-a", "--align", "Omit location and data in order to provide well-aligned logs") do
|
77
|
+
excluded_fields = %w[location data]
|
78
|
+
end
|
79
|
+
|
80
|
+
op.on("-s", "--no-messages", "Donot complain about errors in parsing logs") do
|
81
|
+
ignore_parse_error = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
option_parser.parse!(ARGV)
|
86
|
+
|
87
|
+
STDOUT.sync = true
|
88
|
+
prettifier = Steno::JsonPrettifier.new(excluded_fields)
|
89
|
+
|
90
|
+
inputs = ARGV.dup
|
91
|
+
inputs << "-" if inputs.empty?
|
92
|
+
|
93
|
+
inputs.each do |path|
|
94
|
+
if path == "-"
|
95
|
+
prettify_io(STDIN, prettifier, ignore_parse_error)
|
96
|
+
else
|
97
|
+
prettify_file(path, prettifier, ignore_parse_error)
|
98
|
+
end
|
99
|
+
end
|
data/lib/steno.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
require "steno/codec"
|
4
|
+
require "steno/config"
|
5
|
+
require "steno/context"
|
6
|
+
require "steno/errors"
|
7
|
+
require "steno/log_level"
|
8
|
+
require "steno/logger"
|
9
|
+
require "steno/tagged_logger"
|
10
|
+
require "steno/record"
|
11
|
+
require "steno/sink"
|
12
|
+
require "steno/version"
|
13
|
+
|
14
|
+
module Steno
|
15
|
+
class << self
|
16
|
+
|
17
|
+
attr_reader :config
|
18
|
+
attr_reader :logger_regexp
|
19
|
+
|
20
|
+
# Initializes the logging system. This must be called exactly once before
|
21
|
+
# attempting to use any Steno class methods.
|
22
|
+
#
|
23
|
+
# @param [Steno::Config]
|
24
|
+
#
|
25
|
+
# @return [nil]
|
26
|
+
def init(config)
|
27
|
+
@config = config
|
28
|
+
|
29
|
+
@loggers = {}
|
30
|
+
@loggers_lock = Mutex.new
|
31
|
+
|
32
|
+
@logger_regexp = nil
|
33
|
+
@logger_regexp_level = nil
|
34
|
+
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns (and memoizes) the logger identified by name.
|
39
|
+
#
|
40
|
+
# @param [String] name
|
41
|
+
#
|
42
|
+
# @return [Steno::Logger]
|
43
|
+
def logger(name)
|
44
|
+
@loggers_lock.synchronize do
|
45
|
+
logger = @loggers[name]
|
46
|
+
|
47
|
+
if logger.nil?
|
48
|
+
level = compute_level(name)
|
49
|
+
|
50
|
+
logger = Steno::Logger.new(name, @config.sinks,
|
51
|
+
:level => level,
|
52
|
+
:context => @config.context)
|
53
|
+
|
54
|
+
@loggers[name] = logger
|
55
|
+
end
|
56
|
+
|
57
|
+
logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets all loggers whose name matches _regexp_ to _level_. Resets any
|
62
|
+
# loggers whose name matches the previous regexp but not the supplied regexp.
|
63
|
+
#
|
64
|
+
# @param [Regexp] regexp
|
65
|
+
# @param [Symbol] level
|
66
|
+
#
|
67
|
+
# @return [nil]
|
68
|
+
def set_logger_regexp(regexp, level)
|
69
|
+
@loggers_lock.synchronize do
|
70
|
+
@loggers.each do |name, logger|
|
71
|
+
if name =~ regexp
|
72
|
+
logger.level = level
|
73
|
+
elsif @logger_regexp && (name =~ @logger_regexp)
|
74
|
+
# Reset loggers affected by the old regexp but not by the new
|
75
|
+
logger.level = @config.default_log_level
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@logger_regexp = regexp
|
80
|
+
@logger_regexp_level = level
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Clears the logger regexp, if set. Resets the level of any loggers matching
|
87
|
+
# the regex to the default log level.
|
88
|
+
#
|
89
|
+
# @return [nil]
|
90
|
+
def clear_logger_regexp
|
91
|
+
@loggers_lock.synchronize do
|
92
|
+
return if @logger_regexp.nil?
|
93
|
+
|
94
|
+
@loggers.each do |name, logger|
|
95
|
+
if name =~ @logger_regexp
|
96
|
+
logger.level = @config.default_log_level
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@logger_regexp = nil
|
101
|
+
@logger_regexp_level = nil
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# @return [Hash] Map of logger name => level
|
109
|
+
def logger_level_snapshot
|
110
|
+
@loggers_lock.synchronize do
|
111
|
+
snapshot = {}
|
112
|
+
|
113
|
+
@loggers.each { |name, logger| snapshot[name] = logger.level }
|
114
|
+
|
115
|
+
snapshot
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def compute_level(name)
|
122
|
+
if @logger_regexp && name =~ @logger_regexp
|
123
|
+
@logger_regexp_level
|
124
|
+
else
|
125
|
+
@config.default_log_level
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Initialize with an empty config. All log records will swallowed until Steno
|
132
|
+
# is re-initialized with sinks.
|
133
|
+
Steno.init(Steno::Config.new)
|
data/lib/steno/codec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Steno
|
2
|
+
module Codec
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
class Steno::Codec::Base
|
7
|
+
# Encodes the supplied record as a string.
|
8
|
+
#
|
9
|
+
# @param [Hash] record
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
def encode_record(record)
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Hex encodes non-printable ascii characters.
|
19
|
+
#
|
20
|
+
# @param [String] data
|
21
|
+
#
|
22
|
+
# @return [String]
|
23
|
+
def escape_nonprintable_ascii(data)
|
24
|
+
data.chars.map do |c|
|
25
|
+
ord_val = c.ord
|
26
|
+
|
27
|
+
if (ord_val > 31) && (ord_val < 127)
|
28
|
+
c
|
29
|
+
else
|
30
|
+
"\\x%02x" % [ord_val]
|
31
|
+
end
|
32
|
+
end.join
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "yajl"
|
2
|
+
|
3
|
+
require "steno/codec/base"
|
4
|
+
|
5
|
+
module Steno
|
6
|
+
module Codec
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Steno::Codec::Json < Steno::Codec::Base
|
11
|
+
def encode_record(record)
|
12
|
+
msg =
|
13
|
+
if record.message.valid_encoding?
|
14
|
+
record.message
|
15
|
+
else
|
16
|
+
# Treat the message as an arbitrary sequence of bytes.
|
17
|
+
escape_nonprintable_ascii(record.message.dup.force_encoding("BINARY"))
|
18
|
+
end
|
19
|
+
|
20
|
+
h = {
|
21
|
+
"timestamp" => record.timestamp.to_f,
|
22
|
+
"message" => msg,
|
23
|
+
"log_level" => record.log_level.to_s,
|
24
|
+
"source" => record.source,
|
25
|
+
"data" => record.data,
|
26
|
+
"thread_id" => record.thread_id,
|
27
|
+
"fiber_id" => record.fiber_id,
|
28
|
+
"process_id" => record.process_id,
|
29
|
+
"file" => record.file,
|
30
|
+
"lineno" => record.lineno,
|
31
|
+
"method" => record.method,
|
32
|
+
}
|
33
|
+
|
34
|
+
Yajl::Encoder.encode(h) + "\n"
|
35
|
+
end
|
36
|
+
end
|
data/lib/steno/config.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
require "steno/codec"
|
4
|
+
require "steno/context"
|
5
|
+
require "steno/logger"
|
6
|
+
require "steno/sink"
|
7
|
+
|
8
|
+
module Steno
|
9
|
+
end
|
10
|
+
|
11
|
+
class Steno::Config
|
12
|
+
class << self
|
13
|
+
# Creates a config given a yaml file of the following form:
|
14
|
+
#
|
15
|
+
# logging:
|
16
|
+
# level: <info, debug, etc>
|
17
|
+
# file: </path/to/logfile>
|
18
|
+
# syslog: <syslog name>
|
19
|
+
#
|
20
|
+
# @param [String] path Path to yaml config
|
21
|
+
# @param [Hash] overrides
|
22
|
+
#
|
23
|
+
# @return [Steno::Config]
|
24
|
+
def from_file(path, overrides = {})
|
25
|
+
h = YAML.load_file(path)
|
26
|
+
h = h["logging"] || {}
|
27
|
+
new(to_config_hash(h).merge(overrides))
|
28
|
+
end
|
29
|
+
|
30
|
+
def from_hash(hash)
|
31
|
+
new(to_config_hash(hash))
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_config_hash(hash)
|
35
|
+
hash ||= {}
|
36
|
+
hash = symbolize_keys(hash)
|
37
|
+
|
38
|
+
level = hash[:level] || hash[:default_log_level]
|
39
|
+
opts = {
|
40
|
+
:sinks => [],
|
41
|
+
:default_log_level => level.nil? ? :info : level.to_sym
|
42
|
+
}
|
43
|
+
|
44
|
+
if hash[:file]
|
45
|
+
max_retries = hash[:max_retries]
|
46
|
+
opts[:sinks] << Steno::Sink::IO.for_file(hash[:file], :max_retries => max_retries)
|
47
|
+
end
|
48
|
+
|
49
|
+
if Steno::Sink::WINDOWS
|
50
|
+
if hash[:eventlog]
|
51
|
+
Steno::Sink::Eventlog.instance.open(hash[:eventlog])
|
52
|
+
opts[:sinks] << Steno::Sink::Eventlog.instance
|
53
|
+
end
|
54
|
+
else
|
55
|
+
if hash[:syslog]
|
56
|
+
Steno::Sink::Syslog.instance.open(hash[:syslog])
|
57
|
+
opts[:sinks] << Steno::Sink::Syslog.instance
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if hash[:fluentd]
|
62
|
+
opts[:sinks] << Steno::Sink::Fluentd.new(hash[:fluentd])
|
63
|
+
end
|
64
|
+
|
65
|
+
if opts[:sinks].empty?
|
66
|
+
opts[:sinks] << Steno::Sink::IO.new(STDOUT)
|
67
|
+
end
|
68
|
+
|
69
|
+
opts
|
70
|
+
end
|
71
|
+
|
72
|
+
def symbolize_keys(hash)
|
73
|
+
Hash[hash.each_pair.map { |k, v| [k.to_sym, v] }]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :sinks
|
78
|
+
attr_reader :codec
|
79
|
+
attr_reader :context
|
80
|
+
attr_reader :default_log_level
|
81
|
+
|
82
|
+
def initialize(opts = {})
|
83
|
+
@sinks = opts[:sinks] || []
|
84
|
+
@codec = opts[:codec] || Steno::Codec::Json.new
|
85
|
+
@context = opts[:context] ||Steno::Context::Null.new
|
86
|
+
|
87
|
+
@sinks.each { |sink| sink.codec = @codec }
|
88
|
+
|
89
|
+
if opts[:default_log_level]
|
90
|
+
@default_log_level = opts[:default_log_level].to_sym
|
91
|
+
else
|
92
|
+
@default_log_level = :info
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private_class_method :symbolize_keys
|
97
|
+
end
|