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.
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