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,84 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "steno/json_prettifier"
|
4
|
+
|
5
|
+
describe Steno::JsonPrettifier do
|
6
|
+
let(:prettifier) { Steno::JsonPrettifier.new }
|
7
|
+
let(:codec) { Steno::Codec::Json.new }
|
8
|
+
|
9
|
+
describe "#prettify_line" do
|
10
|
+
it "should return a properly formatted string" do
|
11
|
+
record = Steno::Record.new("test", :info, "message",
|
12
|
+
["filename", "line", "method"], "test" => "data")
|
13
|
+
encoded = codec.encode_record(record)
|
14
|
+
prettified = prettifier.prettify_line(encoded)
|
15
|
+
|
16
|
+
exp_regex = ['\d{4}-\d{2}-\d{2}', # YYYY-MM-DD
|
17
|
+
'\d{2}:\d{2}:\d{2}\.\d{6}', # HH:MM:SS.uS
|
18
|
+
'test', # Source
|
19
|
+
'pid=\d+', # Process id
|
20
|
+
'tid=\w{4}', # Thread shortid
|
21
|
+
'fid=\w{4}', # Fiber shortid
|
22
|
+
'filename\/method:line', # Location
|
23
|
+
'test=data', # User supplied data
|
24
|
+
'INFO', # Level
|
25
|
+
'--',
|
26
|
+
'message', # Log message
|
27
|
+
].join("\s+") + "\n"
|
28
|
+
prettified.should match(exp_regex)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should always use the largest src len to determine src column width" do
|
32
|
+
test_srcs = [
|
33
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH - 3),
|
34
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH - 1),
|
35
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH),
|
36
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH + 1),
|
37
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH - 3),
|
38
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH + 3),
|
39
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH - 2),
|
40
|
+
'a' * (Steno::JsonPrettifier::MIN_COL_WIDTH + 2)
|
41
|
+
]
|
42
|
+
|
43
|
+
regex = ['\d{4}-\d{2}-\d{2}', # YYYY-MM-DD
|
44
|
+
'\d{2}:\d{2}:\d{2}\.\d{6}', # HH:MM:SS.uS
|
45
|
+
'([a-zA-Z0-9\ ]+)', # Source (to be captured)
|
46
|
+
'pid=\d+', # Process id
|
47
|
+
'.+' # Everything else
|
48
|
+
].join("\s") + "\n"
|
49
|
+
|
50
|
+
max_src_len = Steno::JsonPrettifier::MIN_COL_WIDTH
|
51
|
+
test_srcs.each do |src|
|
52
|
+
record = Steno::Record.new(src,
|
53
|
+
:info,
|
54
|
+
"message",
|
55
|
+
["filename", "line", "method"],
|
56
|
+
"test" => "data")
|
57
|
+
|
58
|
+
encoded = codec.encode_record(record)
|
59
|
+
prettified = prettifier.prettify_line(encoded)
|
60
|
+
src_col = prettified.match(regex)[1]
|
61
|
+
|
62
|
+
max_src_len = [max_src_len, src.length].max
|
63
|
+
src_col.length.should == max_src_len
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should raise a parse error when the json-encoded string is not a hash" do
|
68
|
+
expect {
|
69
|
+
prettifier.prettify_line("[1,2,3]")
|
70
|
+
}.to raise_error(Steno::JsonPrettifier::ParseError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should raise a parse error when the json-encoded string is malformed" do
|
74
|
+
expect {
|
75
|
+
prettifier.prettify_line("blah")
|
76
|
+
}.to raise_error(Steno::JsonPrettifier::ParseError)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should work with a nil data field" do
|
80
|
+
line = prettifier.prettify_line(%@{"data":null}@)
|
81
|
+
line.should include(" - ")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::LogLevel do
|
4
|
+
let(:info_level) { Steno::LogLevel.new(:info, 2) }
|
5
|
+
let(:debug_level) { Steno::LogLevel.new(:debug, 1) }
|
6
|
+
|
7
|
+
it "should be comparable" do
|
8
|
+
(info_level > debug_level).should be_true
|
9
|
+
(debug_level > info_level).should be_false
|
10
|
+
(info_level == info_level).should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#to_s" do
|
14
|
+
it "should return the name of the level" do
|
15
|
+
info_level.to_s.should == "info"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Logger do
|
4
|
+
let(:logger) { Steno::Logger.new("test", []) }
|
5
|
+
|
6
|
+
it "should provide #level, #levelf, and #level? methods for each log level" do
|
7
|
+
Steno::Logger::LEVELS.each do |name, _|
|
8
|
+
[name, name.to_s + "f", name.to_s + "?"].each do |meth|
|
9
|
+
logger.respond_to?(meth).should be_true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#level_active?" do
|
15
|
+
it "should return a boolean indicating if the level is enabled" do
|
16
|
+
logger.level_active?(:error).should be_true
|
17
|
+
logger.level_active?(:info).should be_true
|
18
|
+
logger.level_active?(:debug).should be_false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#<level>?" do
|
23
|
+
it "should return a boolean indiciating if <level> is enabled" do
|
24
|
+
logger.error?.should be_true
|
25
|
+
logger.info?.should be_true
|
26
|
+
logger.debug?.should be_false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#level" do
|
31
|
+
it "should return the name of the currently active level" do
|
32
|
+
logger.level.should == :info
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#level=" do
|
37
|
+
it "should allow the level to be changed" do
|
38
|
+
logger.level = :warn
|
39
|
+
logger.level.should == :warn
|
40
|
+
logger.level_active?(:info).should be_false
|
41
|
+
logger.level_active?(:warn).should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#log" do
|
46
|
+
it "should not forward any messages for levels that are inactive" do
|
47
|
+
sink = double("sink")
|
48
|
+
sink.should_not_receive(:add_record)
|
49
|
+
|
50
|
+
my_logger = Steno::Logger.new("test", [sink])
|
51
|
+
|
52
|
+
my_logger.debug("test")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should forward messages for levels that are active" do
|
56
|
+
sink = double("sink")
|
57
|
+
sink.should_receive(:add_record).with(any_args())
|
58
|
+
|
59
|
+
my_logger = Steno::Logger.new("test", [sink])
|
60
|
+
|
61
|
+
my_logger.warn("test")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should not invoke a supplied block if the level is inactive" do
|
65
|
+
invoked = false
|
66
|
+
logger.debug { invoked = true }
|
67
|
+
invoked.should be_false
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should invoke a supplied block if the level is active" do
|
71
|
+
invoked = false
|
72
|
+
logger.warn { invoked = true }
|
73
|
+
invoked.should be_true
|
74
|
+
end
|
75
|
+
|
76
|
+
it "creates a record with the proper level" do
|
77
|
+
sink = double("sink")
|
78
|
+
Steno::Record.should_receive(:new).with("test", :warn, "message", anything, anything).and_call_original
|
79
|
+
sink.stub(:add_record)
|
80
|
+
|
81
|
+
my_logger = Steno::Logger.new("test", [sink])
|
82
|
+
|
83
|
+
my_logger.warn("message")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#logf" do
|
88
|
+
it "should format messages according to the supplied format string" do
|
89
|
+
logger.should_receive(:log).with(:debug, "test 1 2.20")
|
90
|
+
logger.debugf("test %d %0.2f", 1, 2.2)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#tag" do
|
95
|
+
it "should return a tagged logger" do
|
96
|
+
tagged_logger = logger.tag("foo" => "bar")
|
97
|
+
tagged_logger.should_not be_nil
|
98
|
+
tagged_logger.user_data.should == { "foo" => "bar" }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Record do
|
4
|
+
let(:message) { Array("test message") }
|
5
|
+
let(:record) { Steno::Record.new("test", :info, message) }
|
6
|
+
|
7
|
+
it "should set the process id" do
|
8
|
+
record.process_id.should == Process.pid
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should set the thread id" do
|
12
|
+
record.thread_id.should == Thread.current.object_id
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set the fiber id(if available)", :needs_fibers => true do
|
16
|
+
record.fiber_id.should == Fiber.current.object_id
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set the source" do
|
20
|
+
record.source.should == "test"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should stringify the message" do
|
24
|
+
record.message.should be_a(String)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should use a UTC timestamp" do
|
28
|
+
record.timestamp.to_f.should be_within(0.1).of(Time.now.utc.to_f)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Sink::Counter do
|
4
|
+
let(:level) do
|
5
|
+
Steno::Logger.lookup_level(:info)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:record) do
|
9
|
+
Steno::Record.new("source", level.name, "message")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "add_record" do
|
13
|
+
it "counts added records" do
|
14
|
+
expect(subject.counts).to be_empty
|
15
|
+
subject.add_record(record)
|
16
|
+
expect(subject.counts.size).to eq 1
|
17
|
+
expect(subject.counts['info']).to eq 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "to_json" do
|
22
|
+
it "produces a valid json representation" do
|
23
|
+
subject.add_record(record)
|
24
|
+
expect(subject.to_json).to match '"info":1'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
if Steno::Sink::WINDOWS
|
3
|
+
describe Steno::Sink::Eventlog do
|
4
|
+
let(:level) do
|
5
|
+
Steno::Logger.lookup_level(:info)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:record) do
|
9
|
+
Steno::Record.new("source", level.name, "message")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#add_record" do
|
13
|
+
|
14
|
+
it "should append an encoded record with the correct priority" do
|
15
|
+
eventlog = double("Win32::EventLog")
|
16
|
+
Win32::EventLog.should_receive(:open) \
|
17
|
+
.with('Application') \
|
18
|
+
.and_return(eventlog)
|
19
|
+
|
20
|
+
sink = Steno::Sink::Eventlog.instance
|
21
|
+
sink.open
|
22
|
+
|
23
|
+
codec = double("codec")
|
24
|
+
codec.should_receive(:encode_record).with(record).and_return(record.message)
|
25
|
+
sink.codec = codec
|
26
|
+
|
27
|
+
eventlog.should_receive(:report_event).with(:source => "CloudFoundry",
|
28
|
+
:event_type => Win32::EventLog::INFO,
|
29
|
+
:data => record.message)
|
30
|
+
|
31
|
+
sink.add_record(record)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#flush" do
|
36
|
+
it "should do nothing" do
|
37
|
+
Steno::Sink::Eventlog.instance.flush
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Sink::IO do
|
4
|
+
let(:level) do
|
5
|
+
Steno::Logger.lookup_level(:info)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:record) do
|
9
|
+
Steno::Record.new("source", level.name, "message")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#initialize" do
|
13
|
+
it "should initialize FluentLogger with the default option" do
|
14
|
+
Fluent::Logger::FluentLogger.should_receive(:new).with("steno", {
|
15
|
+
:host => "127.0.0.1",
|
16
|
+
:port => 24224,
|
17
|
+
:buffer_limit => Fluent::Logger::FluentLogger::BUFFER_LIMIT,
|
18
|
+
}).and_return()
|
19
|
+
sink = Steno::Sink::Fluentd.new()
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should initialize FliuentLogger with override options" do
|
23
|
+
Fluent::Logger::FluentLogger.should_receive(:new).with("vcap", {
|
24
|
+
:host => "localhost",
|
25
|
+
:port => 8080,
|
26
|
+
:buffer_limit => 1024,
|
27
|
+
}).and_return()
|
28
|
+
sink = Steno::Sink::Fluentd.new({
|
29
|
+
:tag_prefix => "vcap",
|
30
|
+
:host => "localhost",
|
31
|
+
:port => 8080,
|
32
|
+
:buffer_limit => 1024
|
33
|
+
})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#add_record" do
|
38
|
+
it "should post an record with the correct tag" do
|
39
|
+
fluentd = double("fluentd")
|
40
|
+
Fluent::Logger::FluentLogger.should_receive(:new).and_return(fluentd)
|
41
|
+
fluentd.should_receive(:post).with("source", record)
|
42
|
+
sink = Steno::Sink::Fluentd.new()
|
43
|
+
sink.add_record(record)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Sink::IO do
|
4
|
+
let(:level) do
|
5
|
+
Steno::Logger.lookup_level(:info)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:record) do
|
9
|
+
Steno::Record.new("source", level.name, "message")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".for_file" do
|
13
|
+
it "should return a new sink configured to append to the file at path with autosync set to true by default" do
|
14
|
+
mock_handle = double("file handle")
|
15
|
+
|
16
|
+
File.should_receive(:open).with("path", "a+").and_return(mock_handle)
|
17
|
+
mock_handle.should_receive(:sync=).with(true)
|
18
|
+
|
19
|
+
mock_sink = double("sink")
|
20
|
+
Steno::Sink::IO.should_receive(:new).with(mock_handle,
|
21
|
+
:max_retries => 10).
|
22
|
+
and_return(mock_sink)
|
23
|
+
|
24
|
+
returned = Steno::Sink::IO.for_file("path",
|
25
|
+
:max_retries => 10)
|
26
|
+
returned.should == mock_sink
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return a new sink configured to append to the file at path with specified options" do
|
30
|
+
mock_handle = double("file handle")
|
31
|
+
|
32
|
+
File.should_receive(:open).with("path", "a+").and_return(mock_handle)
|
33
|
+
mock_handle.should_receive(:sync=).with(false)
|
34
|
+
|
35
|
+
mock_sink = double("sink")
|
36
|
+
Steno::Sink::IO.should_receive(:new).with(mock_handle,
|
37
|
+
:max_retries => 10).
|
38
|
+
and_return(mock_sink)
|
39
|
+
|
40
|
+
returned = Steno::Sink::IO.for_file("path",
|
41
|
+
:autoflush => false,
|
42
|
+
:max_retries => 10)
|
43
|
+
returned.should == mock_sink
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#add_record" do
|
48
|
+
it "should encode the record and write it to the underlying io object" do
|
49
|
+
codec = double("codec")
|
50
|
+
codec.should_receive(:encode_record).with(record).and_return(record.message)
|
51
|
+
|
52
|
+
io = double("io")
|
53
|
+
io.should_receive(:write).with(record.message)
|
54
|
+
|
55
|
+
Steno::Sink::IO.new(io, :codec => codec).add_record(record)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should by default not retry on IOError" do
|
59
|
+
codec = double("codec")
|
60
|
+
codec.should_receive(:encode_record).with(record).and_return(record.message)
|
61
|
+
|
62
|
+
io = double("io")
|
63
|
+
|
64
|
+
io.should_receive(:write).with(record.message).ordered.and_raise(IOError)
|
65
|
+
|
66
|
+
expect do
|
67
|
+
Steno::Sink::IO.new(io, :codec => codec).add_record(record)
|
68
|
+
end.to raise_error(IOError)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should retry not more than specified number of times on IOError" do
|
72
|
+
codec = double("codec")
|
73
|
+
codec.should_receive(:encode_record).with(record).and_return(record.message)
|
74
|
+
|
75
|
+
io = double("io")
|
76
|
+
|
77
|
+
io.should_receive(:write).exactly(3).times.with(record.message).
|
78
|
+
and_raise(IOError)
|
79
|
+
|
80
|
+
expect do
|
81
|
+
Steno::Sink::IO.new(io, :codec => codec, :max_retries => 2).
|
82
|
+
add_record(record)
|
83
|
+
end.to raise_error(IOError)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should retry on IOError and succeed" do
|
87
|
+
codec = double("codec")
|
88
|
+
codec.should_receive(:encode_record).with(record).and_return(record.message)
|
89
|
+
|
90
|
+
io = double("io")
|
91
|
+
io.should_receive(:write).with(record.message).once.
|
92
|
+
and_raise(IOError)
|
93
|
+
io.should_receive(:write).with(record.message).once.ordered.
|
94
|
+
and_return(record.message)
|
95
|
+
|
96
|
+
expect do
|
97
|
+
Steno::Sink::IO.new(io, :codec => codec, :max_retries => 1).
|
98
|
+
add_record(record)
|
99
|
+
end.to_not raise_error(IOError)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#flush" do
|
104
|
+
it "should call flush on the underlying io object" do
|
105
|
+
io = double("io")
|
106
|
+
io.should_receive(:flush)
|
107
|
+
|
108
|
+
Steno::Sink::IO.new(io).flush
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|