steno-capi 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +7136 -0
  3. data/README.md +78 -0
  4. data/Rakefile +15 -0
  5. data/bin/steno-prettify +99 -0
  6. data/lib/steno.rb +133 -0
  7. data/lib/steno/codec.rb +2 -0
  8. data/lib/steno/codec/base.rb +34 -0
  9. data/lib/steno/codec/json.rb +49 -0
  10. data/lib/steno/config.rb +101 -0
  11. data/lib/steno/context.rb +59 -0
  12. data/lib/steno/core_ext.rb +11 -0
  13. data/lib/steno/errors.rb +3 -0
  14. data/lib/steno/json_prettifier.rb +131 -0
  15. data/lib/steno/log_level.rb +24 -0
  16. data/lib/steno/logger.rb +174 -0
  17. data/lib/steno/record.rb +41 -0
  18. data/lib/steno/sink.rb +6 -0
  19. data/lib/steno/sink/base.rb +38 -0
  20. data/lib/steno/sink/counter.rb +44 -0
  21. data/lib/steno/sink/eventlog.rb +46 -0
  22. data/lib/steno/sink/fluentd.rb +31 -0
  23. data/lib/steno/sink/io.rb +72 -0
  24. data/lib/steno/sink/syslog.rb +62 -0
  25. data/lib/steno/tagged_logger.rb +59 -0
  26. data/lib/steno/version.rb +3 -0
  27. data/spec/spec_helper.rb +6 -0
  28. data/spec/support/barrier.rb +22 -0
  29. data/spec/support/null_sink.rb +17 -0
  30. data/spec/support/shared_context_specs.rb +7 -0
  31. data/spec/unit/config_spec.rb +229 -0
  32. data/spec/unit/context_spec.rb +62 -0
  33. data/spec/unit/core_ext_spec.rb +38 -0
  34. data/spec/unit/json_codec_spec.rb +68 -0
  35. data/spec/unit/json_prettifier_spec.rb +84 -0
  36. data/spec/unit/log_level_spec.rb +19 -0
  37. data/spec/unit/logger_spec.rb +101 -0
  38. data/spec/unit/record_spec.rb +30 -0
  39. data/spec/unit/sink/counter_spec.rb +27 -0
  40. data/spec/unit/sink/eventlog_spec.rb +41 -0
  41. data/spec/unit/sink/fluentd_spec.rb +46 -0
  42. data/spec/unit/sink/io_spec.rb +111 -0
  43. data/spec/unit/sink/syslog_spec.rb +75 -0
  44. data/spec/unit/steno_spec.rb +86 -0
  45. data/spec/unit/tagged_logger_spec.rb +35 -0
  46. data/steno-capi.gemspec +39 -0
  47. 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