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.
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +14 -0
- data/lib/steno.rb +134 -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 +56 -0
- data/lib/steno/context.rb +59 -0
- data/lib/steno/core_ext.rb +5 -0
- data/lib/steno/errors.rb +3 -0
- data/lib/steno/http_handler.rb +42 -0
- data/lib/steno/log_level.rb +24 -0
- data/lib/steno/logger.rb +177 -0
- data/lib/steno/record.rb +39 -0
- data/lib/steno/sink.rb +3 -0
- data/lib/steno/sink/base.rb +36 -0
- data/lib/steno/sink/io.rb +48 -0
- data/lib/steno/sink/syslog.rb +38 -0
- data/lib/steno/tagged_logger.rb +59 -0
- data/lib/steno/version.rb +3 -0
- data/spec/core_ext_spec.rb +15 -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/context_spec.rb +62 -0
- data/spec/unit/http_handler_spec.rb +73 -0
- data/spec/unit/io_sink_spec.rb +26 -0
- data/spec/unit/json_codec_spec.rb +48 -0
- data/spec/unit/log_level_spec.rb +18 -0
- data/spec/unit/logger_spec.rb +91 -0
- data/spec/unit/record_spec.rb +21 -0
- data/spec/unit/steno_spec.rb +86 -0
- data/spec/unit/syslog_sink_spec.rb +27 -0
- data/spec/unit/tagged_logger_spec.rb +33 -0
- data/steno.gemspec +26 -0
- metadata +195 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 mpage
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
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
|
+
## Getting started
|
15
|
+
config = Steno::Config.new(
|
16
|
+
:sinks => [Steno::Sink::IO.new(STDOUT)],
|
17
|
+
:codec => Steno::Codec::Json.new,
|
18
|
+
:context => Steno::Context::ThreadLocal.new)
|
19
|
+
|
20
|
+
Steno.init(config)
|
21
|
+
|
22
|
+
logger = Steno.logger("test")
|
23
|
+
|
24
|
+
logger.info("Hello world!")
|
25
|
+
|
26
|
+
## File a Bug
|
27
|
+
|
28
|
+
To file a bug against Cloud Foundry Open Source and its components, sign up and use our
|
29
|
+
bug tracking system: [http://cloudfoundry.atlassian.net](http://cloudfoundry.atlassian.net)
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "ci/reporter/rake/rspec"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
desc "Run all specs"
|
6
|
+
RSpec::Core::RakeTask.new("spec") do |t|
|
7
|
+
t.rspec_opts = %w[--color --format documentation]
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Run all specs and provide output for ci"
|
11
|
+
RSpec::Core::RakeTask.new("spec:ci" => "ci:setup:rspec") do |t|
|
12
|
+
t.rspec_opts = %w[--no-color --format documentation]
|
13
|
+
end
|
14
|
+
|
data/lib/steno.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
require "steno/codec"
|
4
|
+
require "steno/config"
|
5
|
+
require "steno/context"
|
6
|
+
require "steno/core_ext"
|
7
|
+
require "steno/errors"
|
8
|
+
require "steno/log_level"
|
9
|
+
require "steno/logger"
|
10
|
+
require "steno/tagged_logger"
|
11
|
+
require "steno/record"
|
12
|
+
require "steno/sink"
|
13
|
+
require "steno/version"
|
14
|
+
|
15
|
+
module Steno
|
16
|
+
class << self
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
attr_reader :logger_regexp
|
20
|
+
|
21
|
+
# Initializes the logging system. This must be called exactly once before
|
22
|
+
# attempting to use any Steno class methods.
|
23
|
+
#
|
24
|
+
# @param [Steno::Config]
|
25
|
+
#
|
26
|
+
# @return [nil]
|
27
|
+
def init(config)
|
28
|
+
@config = config
|
29
|
+
|
30
|
+
@loggers = {}
|
31
|
+
@loggers_lock = Mutex.new
|
32
|
+
|
33
|
+
@logger_regexp = nil
|
34
|
+
@logger_regexp_level = nil
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns (and memoizes) the logger identified by name.
|
40
|
+
#
|
41
|
+
# @param [String] name
|
42
|
+
#
|
43
|
+
# @return [Steno::Logger]
|
44
|
+
def logger(name)
|
45
|
+
@loggers_lock.synchronize do
|
46
|
+
logger = @loggers[name]
|
47
|
+
|
48
|
+
if logger.nil?
|
49
|
+
level = compute_level(name)
|
50
|
+
|
51
|
+
logger = Steno::Logger.new(name, @config.sinks,
|
52
|
+
:level => level,
|
53
|
+
:context => @config.context)
|
54
|
+
|
55
|
+
@loggers[name] = logger
|
56
|
+
end
|
57
|
+
|
58
|
+
logger
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets all loggers whose name matches _regexp_ to _level_. Resets any
|
63
|
+
# loggers whose name matches the previous regexp but not the supplied regexp.
|
64
|
+
#
|
65
|
+
# @param [Regexp] regexp
|
66
|
+
# @param [Symbol] level
|
67
|
+
#
|
68
|
+
# @return [nil]
|
69
|
+
def set_logger_regexp(regexp, level)
|
70
|
+
@loggers_lock.synchronize do
|
71
|
+
@loggers.each do |name, logger|
|
72
|
+
if name =~ regexp
|
73
|
+
logger.level = level
|
74
|
+
elsif @logger_regexp && (name =~ @logger_regexp)
|
75
|
+
# Reset loggers affected by the old regexp but not by the new
|
76
|
+
logger.level = @config.default_log_level
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@logger_regexp = regexp
|
81
|
+
@logger_regexp_level = level
|
82
|
+
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Clears the logger regexp, if set. Resets the level of any loggers matching
|
88
|
+
# the regex to the default log level.
|
89
|
+
#
|
90
|
+
# @return [nil]
|
91
|
+
def clear_logger_regexp
|
92
|
+
@loggers_lock.synchronize do
|
93
|
+
return if @logger_regexp.nil?
|
94
|
+
|
95
|
+
@loggers.each do |name, logger|
|
96
|
+
if name =~ @logger_regexp
|
97
|
+
logger.level = @config.default_log_level
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
@logger_regexp = nil
|
102
|
+
@logger_regexp_level = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# @return [Hash] Map of logger name => level
|
110
|
+
def logger_level_snapshot
|
111
|
+
@loggers_lock.synchronize do
|
112
|
+
snapshot = {}
|
113
|
+
|
114
|
+
@loggers.each { |name, logger| snapshot[name] = logger.level }
|
115
|
+
|
116
|
+
snapshot
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def compute_level(name)
|
123
|
+
if @logger_regexp && name =~ @logger_regexp
|
124
|
+
@logger_regexp_level
|
125
|
+
else
|
126
|
+
@config.default_log_level
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Initialize with an empty config. All log records will swallowed until Steno
|
133
|
+
# is re-initialized with sinks.
|
134
|
+
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,56 @@
|
|
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
|
+
def from_file(path, overrides = {})
|
14
|
+
h = YAML.load_file(path)
|
15
|
+
|
16
|
+
opts = {
|
17
|
+
:sinks => [],
|
18
|
+
:default_log_level => h["level"].to_sym,
|
19
|
+
}
|
20
|
+
|
21
|
+
if h["file"]
|
22
|
+
opts[:sinks] << Steno::Sink::IO.for_file(h["file"])
|
23
|
+
end
|
24
|
+
|
25
|
+
if h["syslog"]
|
26
|
+
Steno::Sink::Syslog.instance.open(h["syslog"])
|
27
|
+
opts[:sinks] << Steno::Sink::Syslog.instance
|
28
|
+
end
|
29
|
+
|
30
|
+
if opts[:sinks].empty?
|
31
|
+
opts[:sinks] << Steno::Sink::IO.new(STDOUT)
|
32
|
+
end
|
33
|
+
|
34
|
+
new(opts.merge(overrides))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :sinks
|
39
|
+
attr_reader :codec
|
40
|
+
attr_reader :context
|
41
|
+
attr_reader :default_log_level
|
42
|
+
|
43
|
+
def initialize(opts = {})
|
44
|
+
@sinks = opts[:sinks] || []
|
45
|
+
@codec = opts[:codec] || Steno::Codec::Json.new
|
46
|
+
@context = opts[:context] ||Steno::Context::Null.new
|
47
|
+
|
48
|
+
@sinks.each { |sink| sink.codec = @codec }
|
49
|
+
|
50
|
+
if opts[:default_log_level]
|
51
|
+
@default_log_level = opts[:default_log_level].to_sym
|
52
|
+
else
|
53
|
+
@default_log_level = :info
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -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,42 @@
|
|
1
|
+
require "steno"
|
2
|
+
|
3
|
+
require "grape"
|
4
|
+
|
5
|
+
module Steno
|
6
|
+
end
|
7
|
+
|
8
|
+
class Steno::HttpHandler < Grape::API
|
9
|
+
format :json
|
10
|
+
error_format :json
|
11
|
+
|
12
|
+
resource :loggers do
|
13
|
+
get :levels do
|
14
|
+
Steno.logger_level_snapshot
|
15
|
+
end
|
16
|
+
|
17
|
+
put :levels do
|
18
|
+
missing = [:regexp, :level].select { |p| !params.key?(p) }.map(&:to_s)
|
19
|
+
|
20
|
+
if !missing.empty?
|
21
|
+
error!("Missing query parameters: #{missing}", 400)
|
22
|
+
end
|
23
|
+
|
24
|
+
regexp = nil
|
25
|
+
begin
|
26
|
+
regexp = Regexp.new(params[:regexp])
|
27
|
+
rescue => e
|
28
|
+
error!("Invalid regexp", 400)
|
29
|
+
end
|
30
|
+
|
31
|
+
level = params[:level].to_sym
|
32
|
+
if !Steno::Logger::LEVELS.key?(level)
|
33
|
+
levels = Steno::Logger::LEVELS.keys.map(&:to_s)
|
34
|
+
error!("Unknown level: #{level}. Supported levels are: #{levels}", 400)
|
35
|
+
end
|
36
|
+
|
37
|
+
Steno.set_logger_regexp(regexp, level)
|
38
|
+
|
39
|
+
"ok"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|