steno-capi 1.3.4
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 +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
|