steno-capi 1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +7136 -0
- data/README.md +78 -0
- data/Rakefile +15 -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 +49 -0
- data/lib/steno/config.rb +101 -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/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 +62 -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 +229 -0
- data/spec/unit/context_spec.rb +62 -0
- data/spec/unit/core_ext_spec.rb +38 -0
- data/spec/unit/json_codec_spec.rb +68 -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 +75 -0
- data/spec/unit/steno_spec.rb +86 -0
- data/spec/unit/tagged_logger_spec.rb +35 -0
- data/steno-capi.gemspec +39 -0
- metadata +179 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Context::Null do
|
4
|
+
include_context :steno_context
|
5
|
+
|
6
|
+
let(:context) { Steno::Context::Null.new }
|
7
|
+
|
8
|
+
it "should store no data" do
|
9
|
+
expect(context.data).to eq({})
|
10
|
+
context.data["foo"] = "bar"
|
11
|
+
expect(context.data).to eq({})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Steno::Context::ThreadLocal do
|
16
|
+
include_context :steno_context
|
17
|
+
|
18
|
+
let (:context) { Steno::Context::ThreadLocal.new }
|
19
|
+
|
20
|
+
it "should store data local to threads" do
|
21
|
+
b1 = Barrier.new
|
22
|
+
b2 = Barrier.new
|
23
|
+
|
24
|
+
t1 = Thread.new do
|
25
|
+
context.data["thread"] = "t1"
|
26
|
+
b1.release
|
27
|
+
b2.wait
|
28
|
+
expect(context.data["thread"]).to eq("t1")
|
29
|
+
end
|
30
|
+
|
31
|
+
t2 = Thread.new do
|
32
|
+
b1.wait
|
33
|
+
expect(context.data["thread"]).to be_nil
|
34
|
+
context.data["thread"] = "t2"
|
35
|
+
b2.release
|
36
|
+
end
|
37
|
+
|
38
|
+
t1.join
|
39
|
+
t2.join
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Steno::Context::FiberLocal do
|
44
|
+
include_context :steno_context
|
45
|
+
|
46
|
+
let(:context) { Steno::Context::FiberLocal.new }
|
47
|
+
|
48
|
+
it "should store data local to fibers" do
|
49
|
+
f2 = Fiber.new do
|
50
|
+
expect(context.data["fiber"]).to be_nil
|
51
|
+
context.data["fiber"] = "f2"
|
52
|
+
end
|
53
|
+
|
54
|
+
f1 = Fiber.new do
|
55
|
+
context.data["fiber"] = "f1"
|
56
|
+
f2.resume
|
57
|
+
expect(context.data["fiber"]).to eq("f1")
|
58
|
+
end
|
59
|
+
|
60
|
+
f1.resume
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "steno/core_ext"
|
4
|
+
|
5
|
+
module Foo
|
6
|
+
class Bar
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Module do
|
11
|
+
describe "#logger" do
|
12
|
+
it "should request a logger named after itself" do
|
13
|
+
x = Foo.logger
|
14
|
+
expect(x).to be_a(Steno::Logger)
|
15
|
+
expect(x.name).to include("Foo")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Class do
|
21
|
+
describe "#logger" do
|
22
|
+
it "should request a logger named after itself" do
|
23
|
+
x = Foo::Bar.logger
|
24
|
+
expect(x).to be_a(Steno::Logger)
|
25
|
+
expect(x.name).to include("Foo::Bar")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe Object do
|
31
|
+
describe "#logger" do
|
32
|
+
it "should request a logger named after its class" do
|
33
|
+
x = Foo::Bar.new.logger
|
34
|
+
expect(x).to be_a(Steno::Logger)
|
35
|
+
expect(x.name).to include("Foo::Bar")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Steno::Codec::Json do
|
4
|
+
let(:codec) { Steno::Codec::Json.new }
|
5
|
+
let(:record) { make_record(:data => { "user" => "data" }) }
|
6
|
+
|
7
|
+
describe "#encode_record" do
|
8
|
+
it "should encode records as json hashes" do
|
9
|
+
parsed = Yajl::Parser.parse(codec.encode_record(record))
|
10
|
+
expect(parsed.class).to eq(Hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should encode the timestamp as a float" do
|
14
|
+
parsed = Yajl::Parser.parse(codec.encode_record(record))
|
15
|
+
expect(parsed["timestamp"].class).to eq(Float)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should escape newlines" do
|
19
|
+
rec = make_record(:message => "newline\ntest")
|
20
|
+
expect(codec.encode_record(rec)).to match(/newline\\ntest/)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should escape carriage returns" do
|
24
|
+
rec = make_record(:message => "newline\rtest")
|
25
|
+
expect(codec.encode_record(rec)).to match(/newline\\rtest/)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow messages with valid encodings to pass through untouched" do
|
29
|
+
msg = "HI\u2600"
|
30
|
+
rec = make_record(:message => msg)
|
31
|
+
expect(codec.encode_record(rec)).to match(/#{msg}/)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should treat messages with invalid encodings as binary data" do
|
35
|
+
msg = "HI\u2026".force_encoding("US-ASCII")
|
36
|
+
rec = make_record(:message => msg)
|
37
|
+
expect(codec.encode_record(rec)).to match(/HI\\\\xe2\\\\x80\\\\xa6/)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "shouldn't use readable dates by default" do
|
41
|
+
expect(codec.iso8601_timestamps?).to eq(false)
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when iso8601_timestamps is set" do
|
45
|
+
let(:codec) { Steno::Codec::Json.new( :iso8601_timestamps => true ) }
|
46
|
+
|
47
|
+
it "should encode timestamps as UTC-formatted strings" do
|
48
|
+
allow(record).to receive(:timestamp).and_return 1396473763.811278 # 2014-04-02 22:22:43 +01:00
|
49
|
+
parsed = Yajl::Parser.parse(codec.encode_record(record))
|
50
|
+
|
51
|
+
expect(parsed["timestamp"].class).to eq(String)
|
52
|
+
expect(parsed["timestamp"]).to eq("2014-04-02T21:22:43.811278Z")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should surface the property in a getter" do
|
56
|
+
expect(codec.iso8601_timestamps?).to eq(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def make_record(opts = {})
|
62
|
+
Steno::Record.new(opts[:source] || "my_source",
|
63
|
+
opts[:level] || :debug,
|
64
|
+
opts[:message] || "test message",
|
65
|
+
nil,
|
66
|
+
opts[:data] || {})
|
67
|
+
end
|
68
|
+
end
|
@@ -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
|
+
expect(prettified).to 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
|
+
expect(src_col.length).to eq(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
|
+
expect(line).to 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
|
+
expect(info_level > debug_level).to be_truthy
|
9
|
+
expect(debug_level > info_level).to be_falsey
|
10
|
+
expect(info_level == info_level).to be_truthy
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#to_s" do
|
14
|
+
it "should return the name of the level" do
|
15
|
+
expect(info_level.to_s).to eq("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
|
+
expect(logger.respond_to?(meth)).to be_truthy
|
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
|
+
expect(logger.level_active?(:error)).to be_truthy
|
17
|
+
expect(logger.level_active?(:info)).to be_truthy
|
18
|
+
expect(logger.level_active?(:debug)).to be_falsey
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#<level>?" do
|
23
|
+
it "should return a boolean indiciating if <level> is enabled" do
|
24
|
+
expect(logger.error?).to be_truthy
|
25
|
+
expect(logger.info?).to be_truthy
|
26
|
+
expect(logger.debug?).to be_falsey
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#level" do
|
31
|
+
it "should return the name of the currently active level" do
|
32
|
+
expect(logger.level).to eq(: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
|
+
expect(logger.level).to eq(:warn)
|
40
|
+
expect(logger.level_active?(:info)).to be_falsey
|
41
|
+
expect(logger.level_active?(:warn)).to be_truthy
|
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
|
+
expect(sink).to_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
|
+
expect(sink).to 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
|
+
expect(invoked).to be_falsey
|
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
|
+
expect(invoked).to be_truthy
|
74
|
+
end
|
75
|
+
|
76
|
+
it "creates a record with the proper level" do
|
77
|
+
sink = double("sink")
|
78
|
+
expect(Steno::Record).to receive(:new).with("test", :warn, "message", anything, anything).and_call_original
|
79
|
+
allow(sink).to receive(: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
|
+
expect(logger).to 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
|
+
expect(tagged_logger).to_not be_nil
|
98
|
+
expect(tagged_logger.user_data).to eq({ "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
|
+
expect(record.process_id).to eq(Process.pid)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should set the thread id" do
|
12
|
+
expect(record.thread_id).to eq(Thread.current.object_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set the fiber id(if available)", :needs_fibers => true do
|
16
|
+
expect(record.fiber_id).to eq(Fiber.current.object_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set the source" do
|
20
|
+
expect(record.source).to eq("test")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should stringify the message" do
|
24
|
+
expect(record.message).to be_a(String)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should use a UTC timestamp" do
|
28
|
+
expect(record.timestamp.to_f).to 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_TYPE,
|
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
|