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,59 @@
1
+ require "fiber"
2
+ require "thread"
3
+
4
+ class Fiber
5
+ def __steno_context_data__
6
+ @__steno_context_data__ ||= {}
7
+ end
8
+
9
+ def __steno_clear_context_data__
10
+ @__steno_context_data__ = {}
11
+ end
12
+ end
13
+
14
+ module Steno
15
+ end
16
+
17
+ module Steno::Context
18
+ class Base
19
+ def data
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def clear
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+
28
+ class Null < Base
29
+ def data
30
+ {}
31
+ end
32
+
33
+ def clear
34
+ nil
35
+ end
36
+ end
37
+
38
+ class ThreadLocal < Base
39
+ THREAD_LOCAL_KEY = "__steno_locals__"
40
+
41
+ def data
42
+ Thread.current[THREAD_LOCAL_KEY] ||= {}
43
+ end
44
+
45
+ def clear
46
+ Thread.current[THREAD_LOCAL_KEY] = {}
47
+ end
48
+ end
49
+
50
+ class FiberLocal < Base
51
+ def data
52
+ Fiber.current.__steno_context_data__
53
+ end
54
+
55
+ def clear
56
+ Fiber.current.__steno_clear_context_data__
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,11 @@
1
+ class Module
2
+ def logger
3
+ Steno.logger(name)
4
+ end
5
+ end
6
+
7
+ class Object
8
+ def logger
9
+ self.class.logger
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Steno
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,131 @@
1
+ require "digest/md5"
2
+ require "set"
3
+ require "yajl"
4
+
5
+ module Steno
6
+ end
7
+
8
+ class Steno::JsonPrettifier
9
+ FIELD_ORDER = %w[
10
+ timestamp
11
+ source
12
+ process_id
13
+ thread_id
14
+ fiber_id
15
+ location
16
+ data
17
+ log_level
18
+ message
19
+ ]
20
+
21
+ MIN_COL_WIDTH = 14
22
+
23
+ class ParseError < StandardError
24
+ end
25
+
26
+ def initialize(excluded_fields = [])
27
+ @time_format = "%Y-%m-%d %H:%M:%S.%6N"
28
+ @excluded_fields = Set.new(excluded_fields)
29
+ @max_src_len = MIN_COL_WIDTH
30
+ end
31
+
32
+ def prettify_line(line)
33
+ begin
34
+ json_record = Yajl::Parser.parse(line)
35
+ rescue Yajl::ParseError => e
36
+ raise ParseError, e.to_s
37
+ end
38
+
39
+ format_record(json_record)
40
+ end
41
+
42
+ protected
43
+
44
+ def format_record(record)
45
+ record ||= {}
46
+ fields = []
47
+
48
+ FIELD_ORDER.each do |field_name|
49
+ next if @excluded_fields.include?(field_name)
50
+
51
+ exists = nil
52
+ pred_meth = "check_#{field_name}".to_sym
53
+ if respond_to?(pred_meth, true)
54
+ exists = send(pred_meth, record)
55
+ elsif record.respond_to?(:has_key?)
56
+ exists = record.has_key?(field_name)
57
+ else
58
+ msg = "Expected the record to be a hash, but received: #{record.class}."
59
+ raise ParseError, msg
60
+ end
61
+
62
+ if exists
63
+ fields << send("format_#{field_name}".to_sym, record)
64
+ else
65
+ fields << "-"
66
+ end
67
+ end
68
+
69
+ fields.join(" ") + "\n"
70
+ end
71
+
72
+ def format_timestamp(record)
73
+ Time.at(record["timestamp"]).strftime(@time_format)
74
+ end
75
+
76
+ def format_source(record)
77
+ @max_src_len = [@max_src_len, record["source"].length].max
78
+ record["source"].ljust(@max_src_len)
79
+ end
80
+
81
+ def format_process_id(record)
82
+ "pid=%-5s" % [record["process_id"]]
83
+ end
84
+
85
+ def format_thread_id(record)
86
+ "tid=%s" % [shortid(record["thread_id"])]
87
+ end
88
+
89
+ def format_fiber_id(record)
90
+ "fid=%s" % [shortid(record["fiber_id"])]
91
+ end
92
+
93
+ def check_location(record)
94
+ %w[file lineno method].reduce(true) { |ok, k| ok && record.has_key?(k) }
95
+ end
96
+
97
+ def format_location(record)
98
+ parts = record["file"].split("/")
99
+
100
+ trimmed_filename = nil
101
+ if parts.size == 1
102
+ trimmed_filename = parts[0]
103
+ else
104
+ trimmed_filename = parts.slice(-2, 2).join("/")
105
+ end
106
+
107
+ "%s/%s:%s" % [trimmed_filename, record["method"], record["lineno"]]
108
+ end
109
+
110
+ def check_data(record)
111
+ record["data"].is_a?(Hash)
112
+ end
113
+
114
+ def format_data(record)
115
+ record["data"].map { |k, v| "#{k}=#{v}" }.join(",")
116
+ end
117
+
118
+ def format_log_level(record)
119
+ "%7s" % [record["log_level"].upcase]
120
+ end
121
+
122
+ def format_message(record)
123
+ "-- %s" % [record["message"]]
124
+ end
125
+
126
+ def shortid(data)
127
+ return "-" if data.nil?
128
+ digest = Digest::MD5.hexdigest(data.to_s)
129
+ digest[0, 4]
130
+ end
131
+ end
@@ -0,0 +1,24 @@
1
+ module Steno
2
+ end
3
+
4
+ class Steno::LogLevel
5
+ include Comparable
6
+
7
+ attr_reader :name
8
+ attr_reader :priority
9
+
10
+ # @param [String] name "info", "debug", etc.
11
+ # @param [Integer] priority "info" > "debug", etc.
12
+ def initialize(name, priority)
13
+ @name = name
14
+ @priority = priority
15
+ end
16
+
17
+ def to_s
18
+ @name.to_s
19
+ end
20
+
21
+ def <=>(other)
22
+ @priority <=> other.priority
23
+ end
24
+ end
@@ -0,0 +1,174 @@
1
+ require "thread"
2
+
3
+ require "steno/errors"
4
+ require "steno/log_level"
5
+
6
+ module Steno
7
+ end
8
+
9
+ class Steno::Logger
10
+ LEVELS = {
11
+ :off => Steno::LogLevel.new(:off, 0),
12
+ :fatal => Steno::LogLevel.new(:fatal, 1),
13
+ :error => Steno::LogLevel.new(:error, 5),
14
+ :warn => Steno::LogLevel.new(:warn, 10),
15
+ :info => Steno::LogLevel.new(:info, 15),
16
+ :debug => Steno::LogLevel.new(:debug, 16),
17
+ :debug1 => Steno::LogLevel.new(:debug1, 17),
18
+ :debug2 => Steno::LogLevel.new(:debug2, 18),
19
+ :all => Steno::LogLevel.new(:all, 30),
20
+ }
21
+
22
+ class << self
23
+ # The following helpers are used to create a new scope for binding the log
24
+ # level.
25
+
26
+ def define_log_method(name)
27
+ define_method(name) { |*args, &blk| log(name, *args, &blk) }
28
+ end
29
+
30
+ def define_logf_method(name)
31
+ define_method(name.to_s + "f") { |fmt, *args| log(name, fmt % args) }
32
+ end
33
+
34
+ def define_level_active_predicate(name)
35
+ define_method(name.to_s + "?") { level_active?(name) }
36
+ end
37
+
38
+ def lookup_level(name)
39
+ level = LEVELS[name]
40
+
41
+ if level.nil?
42
+ raise Steno::Error.new("Unknown level: #{name}")
43
+ end
44
+
45
+ level
46
+ end
47
+ end
48
+
49
+ # This is magic, however, it's vastly simpler than declaring each method
50
+ # manually.
51
+ LEVELS.each do |name, _|
52
+ # Define #debug, for example
53
+ define_log_method(name)
54
+
55
+ # Define #debugf, for example
56
+ define_logf_method(name)
57
+
58
+ # Define #debug?, for example. These are provided to ensure compatibility
59
+ # with Ruby's standard library Logger class.
60
+ define_level_active_predicate(name)
61
+ end
62
+
63
+ attr_reader :name
64
+
65
+ # @param [String] name The logger name.
66
+ # @param [Array<Steno::Sink::Base>] sinks
67
+ # @param [Hash] opts
68
+ # @option opts [Symbol] :level The minimum level for which this logger will
69
+ # emit log records. Defaults to :info.
70
+ # @option opts [Steno::Context] :context
71
+ def initialize(name, sinks, opts = {})
72
+ @name = name
73
+ @min_level = self.class.lookup_level(opts[:level] || :info)
74
+ @min_level_lock = Mutex.new
75
+ @sinks = sinks
76
+ @context = opts[:context] || Steno::Context::Null.new
77
+ end
78
+
79
+ # Sets the minimum level for which records will be added to sinks.
80
+ #
81
+ # @param [Symbol] level_name The level name
82
+ #
83
+ # @return [nil]
84
+ def level=(level_name)
85
+ level = self.class.lookup_level(level_name)
86
+
87
+ @min_level_lock.synchronize { @min_level = level }
88
+
89
+ nil
90
+ end
91
+
92
+ # Returns the name of the current log level
93
+ #
94
+ # @return [Symbol]
95
+ def level
96
+ @min_level_lock.synchronize { @min_level.name }
97
+ end
98
+
99
+ # Returns whether or not records for the given level would be forwarded to
100
+ # sinks.
101
+ #
102
+ # @param [Symbol] level_name
103
+ #
104
+ # @return [true || false]
105
+ def level_active?(level_name)
106
+ level = self.class.lookup_level(level_name)
107
+ @min_level_lock.synchronize { level <= @min_level }
108
+ end
109
+
110
+ # Convenience method for logging an exception, along with its backtrace.
111
+ #
112
+ # @param [Exception] ex
113
+
114
+ # @return [nil]
115
+ def log_exception(ex, user_data = {})
116
+ warn("Caught exception: #{ex}", user_data.merge(:backtrace => ex.backtrace))
117
+ end
118
+
119
+ # Adds a record to the configured sinks.
120
+ #
121
+ # @param [Symbol] level_name The level associated with the record
122
+ # @param [String] message
123
+ # @param [Hash] user_data
124
+ #
125
+ # @return [nil]
126
+ def log(level_name, message = nil, user_data = nil, &blk)
127
+ return unless level_active?(level_name)
128
+
129
+ message = yield if block_given?
130
+
131
+ callstack = caller
132
+ loc = parse_record_loc(callstack)
133
+
134
+ data = @context.data.merge(user_data || {})
135
+
136
+ record = Steno::Record.new(@name, level_name, message, loc, data)
137
+
138
+ @sinks.each { |sink| sink.add_record(record) }
139
+
140
+ nil
141
+ end
142
+
143
+ # Returns a proxy that will emit the supplied user data along with each
144
+ # log record.
145
+ #
146
+ # @param [Hash] user_data
147
+ #
148
+ # @return [Steno::TaggedLogger]
149
+ def tag(user_data = {})
150
+ Steno::TaggedLogger.new(self, user_data)
151
+ end
152
+
153
+ private
154
+
155
+ def parse_record_loc(callstack)
156
+ file, lineno, method = nil, nil, nil
157
+
158
+ callstack.each do |frame|
159
+ next if frame =~ /logger\.rb/
160
+
161
+ file, lineno, method = frame.split(":")
162
+
163
+ lineno = lineno.to_i
164
+
165
+ if method =~ /in `([^']+)/
166
+ method = $1
167
+ end
168
+
169
+ break
170
+ end
171
+
172
+ [file, lineno, method]
173
+ end
174
+ end
@@ -0,0 +1,41 @@
1
+ require "digest/md5"
2
+ require "thread"
3
+
4
+ module Steno
5
+ end
6
+
7
+ class Steno::Record
8
+
9
+ attr_reader :timestamp
10
+ attr_reader :message
11
+ attr_reader :log_level
12
+ attr_reader :source
13
+ attr_reader :data
14
+ attr_reader :thread_id
15
+ attr_reader :fiber_id
16
+ attr_reader :process_id
17
+ attr_reader :file
18
+ attr_reader :lineno
19
+ attr_reader :method
20
+
21
+ # @param [String] source Identifies message source.
22
+ # @param [Symbol] log_level
23
+ # @param [String] message
24
+ # @param [Array] loc Location where the record was generated.
25
+ # Format is [<filename>, <lineno>, <method>].
26
+ # @param [Hash] data User-supplied data
27
+ def initialize(source, log_level, message, loc = [], data = {})
28
+ raise "Log level must be a Symbol" unless log_level.is_a? Symbol
29
+
30
+ @timestamp = Time.now
31
+ @source = source
32
+ @log_level = log_level
33
+ @message = message.to_s
34
+ @data = {}.merge(data)
35
+ @thread_id = Thread.current.object_id
36
+ @fiber_id = Fiber.current.object_id
37
+ @process_id = Process.pid
38
+
39
+ @file, @lineno, @method = loc
40
+ end
41
+ end