steno 1.2.2-x86-mingw32

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 (50) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +7136 -0
  3. data/README.md +78 -0
  4. data/Rakefile +16 -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 +36 -0
  10. data/lib/steno/config.rb +97 -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/http_handler.rb +41 -0
  15. data/lib/steno/json_prettifier.rb +131 -0
  16. data/lib/steno/log_level.rb +24 -0
  17. data/lib/steno/logger.rb +174 -0
  18. data/lib/steno/record.rb +41 -0
  19. data/lib/steno/sink.rb +6 -0
  20. data/lib/steno/sink/base.rb +38 -0
  21. data/lib/steno/sink/counter.rb +44 -0
  22. data/lib/steno/sink/eventlog.rb +46 -0
  23. data/lib/steno/sink/fluentd.rb +31 -0
  24. data/lib/steno/sink/io.rb +72 -0
  25. data/lib/steno/sink/syslog.rb +59 -0
  26. data/lib/steno/tagged_logger.rb +59 -0
  27. data/lib/steno/version.rb +3 -0
  28. data/spec/spec_helper.rb +6 -0
  29. data/spec/support/barrier.rb +22 -0
  30. data/spec/support/null_sink.rb +17 -0
  31. data/spec/support/shared_context_specs.rb +7 -0
  32. data/spec/unit/config_spec.rb +221 -0
  33. data/spec/unit/context_spec.rb +62 -0
  34. data/spec/unit/core_ext_spec.rb +38 -0
  35. data/spec/unit/http_handler_spec.rb +73 -0
  36. data/spec/unit/json_codec_spec.rb +48 -0
  37. data/spec/unit/json_prettifier_spec.rb +84 -0
  38. data/spec/unit/log_level_spec.rb +19 -0
  39. data/spec/unit/logger_spec.rb +101 -0
  40. data/spec/unit/record_spec.rb +30 -0
  41. data/spec/unit/sink/counter_spec.rb +27 -0
  42. data/spec/unit/sink/eventlog_spec.rb +41 -0
  43. data/spec/unit/sink/fluentd_spec.rb +46 -0
  44. data/spec/unit/sink/io_spec.rb +111 -0
  45. data/spec/unit/sink/syslog_spec.rb +74 -0
  46. data/spec/unit/steno_spec.rb +86 -0
  47. data/spec/unit/tagged_logger_spec.rb +33 -0
  48. data/steno-1.2.1.gem +0 -0
  49. data/steno.gemspec +41 -0
  50. metadata +224 -0
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Steno
2
+ Steno is a lightweight, modular logging library written specifically to support
3
+ Cloud Foundry.
4
+
5
+ ## Concepts
6
+
7
+ Steno is composed of three main classes: loggers, sinks, and formatters. Loggers
8
+ are the main entry point for Steno. They consume user input, create structured
9
+ records, and forward said records to the configured sinks. Sinks are the
10
+ ultimate destination for log records. They transform a structured record into
11
+ a string via a formatter and then typically write the transformed string to
12
+ another transport.
13
+
14
+ ## Configuration
15
+
16
+ To use steno, you must configure one or more 'sinks', a 'codec' and a 'context'.
17
+ If you don't provide a codec, steno will encode your logs as JSON.
18
+
19
+ For example:
20
+
21
+ config = Steno::Config.new(
22
+ :sinks => [Steno::Sink::IO.new(STDOUT)],
23
+ :codec => Steno::Codec::Json.new,
24
+ :context => Steno::Context::ThreadLocal.new)
25
+
26
+ ### from YAML file
27
+
28
+ Alternatively, Steno can read its configuration from a YAML file in the following format:
29
+
30
+ ```yaml
31
+ # config.yml
32
+ ---
33
+ logging:
34
+ file: /some/path # Optional - path a log file
35
+ max_retries: 3 # Optional - number of times to retry if a file write fails.
36
+ syslog: some_syslog.id # Optional - only works on *nix systems
37
+ eventlog: true # Optional - only works on Windows
38
+ fluentd: # Optional
39
+ host: fluentd.host
40
+ port: 9999
41
+ level: debug # Optional - Minimum log level that will be written.
42
+ # Defaults to 'info'
43
+ ```
44
+ Then, in your code:
45
+ ```ruby
46
+ config = Steno::Config.from_file("path/to/config.yml")
47
+ ```
48
+
49
+ With this configuration method, if neither `file`, `syslog` or `fluentd` are provided,
50
+ steno will use its stdout as its sink. Also, note that the top-level field `logging` is required.
51
+
52
+ ### from Hash
53
+
54
+ As a third option, steno can be configured using a hash with the same structure as the above
55
+ YAML file (without the top-level `logging` key):
56
+ ```ruby
57
+ config = Steno::Config.from_hash(config_hash)
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ Steno.init(config)
63
+ logger = Steno.logger("test")
64
+ logger.info("Hello world!")
65
+
66
+ ### Log levels
67
+
68
+ LEVEL NUMERIC RANKING
69
+ off 0
70
+ fatal 1
71
+ error 5
72
+ warn 10
73
+ info 15
74
+ debug 16
75
+ debug1 17
76
+ debug2 18
77
+ all 30
78
+
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rake
2
+ require "ci/reporter/rake/rspec"
3
+ require "rspec/core/rake_task"
4
+
5
+ task :default => :spec
6
+
7
+ desc "Run all specs"
8
+ RSpec::Core::RakeTask.new("spec") do |t|
9
+ t.rspec_opts = %w[--color --format documentation]
10
+ end
11
+
12
+ desc "Run all specs and provide output for ci"
13
+ RSpec::Core::RakeTask.new("spec:ci" => "ci:setup:rspec") do |t|
14
+ t.rspec_opts = %w[--no-color --format documentation]
15
+ end
16
+
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "yajl"
5
+
6
+ require "steno/json_prettifier"
7
+
8
+ Signal.trap("INT") { exit }
9
+
10
+ def prettify_io(io, prettifier, ignore_parse_error = false)
11
+ lineno = 1
12
+ json_regex = '([^{]*)({.*})'
13
+
14
+ io.sync = true
15
+
16
+ io.each_line do |line|
17
+ begin
18
+ match_data = line.match(json_regex) || []
19
+ prefix = match_data[1]
20
+ json = match_data[2]
21
+
22
+ prettified = prettifier.prettify_line(json)
23
+ print prefix + prettified
24
+ rescue Steno::JsonPrettifier::ParseError => e
25
+ if ignore_parse_error
26
+ print line
27
+ else
28
+ STDERR.puts "steno-prettify: Malformed json at line #{lineno}, '#{line.strip}'"
29
+ end
30
+ end
31
+
32
+ lineno += 1
33
+ end
34
+ end
35
+
36
+ def prettify_file(path, prettifier, ignore_parse_error = false)
37
+ begin
38
+ f = File.open(path, "r")
39
+ rescue => e
40
+ STDERR.puts "steno-prettify: Failed opening '#{path}', #{e}"
41
+ return
42
+ end
43
+
44
+ prettify_io(f, prettifier, ignore_parse_error)
45
+
46
+ ensure
47
+ f.close if f
48
+ end
49
+
50
+ excluded_fields = []
51
+ ignore_parse_error = false
52
+
53
+ option_parser = OptionParser.new do |op|
54
+ op.banner =<<EOT
55
+ Usage: steno-prettify [OPTS] [FILE...]
56
+
57
+ Parses json formatted log lines from FILE(s), or stdin,
58
+ and displays a more human friendly version of each line to stdout.
59
+
60
+ Examples (shamelessly stolen from `man cat`):
61
+
62
+ steno-prettify f - g
63
+ Prettify f's contents, then standard input, then g's contents.
64
+
65
+ steno-prettify
66
+ Prettify contents of stdin.
67
+
68
+ Options:
69
+ EOT
70
+
71
+ op.on("-h", "--help", "Display help") do
72
+ puts op
73
+ exit
74
+ end
75
+
76
+ op.on("-a", "--align", "Omit location and data in order to provide well-aligned logs") do
77
+ excluded_fields = %w[location data]
78
+ end
79
+
80
+ op.on("-s", "--no-messages", "Donot complain about errors in parsing logs") do
81
+ ignore_parse_error = true
82
+ end
83
+ end
84
+
85
+ option_parser.parse!(ARGV)
86
+
87
+ STDOUT.sync = true
88
+ prettifier = Steno::JsonPrettifier.new(excluded_fields)
89
+
90
+ inputs = ARGV.dup
91
+ inputs << "-" if inputs.empty?
92
+
93
+ inputs.each do |path|
94
+ if path == "-"
95
+ prettify_io(STDIN, prettifier, ignore_parse_error)
96
+ else
97
+ prettify_file(path, prettifier, ignore_parse_error)
98
+ end
99
+ end
data/lib/steno.rb ADDED
@@ -0,0 +1,133 @@
1
+ require "thread"
2
+
3
+ require "steno/codec"
4
+ require "steno/config"
5
+ require "steno/context"
6
+ require "steno/errors"
7
+ require "steno/log_level"
8
+ require "steno/logger"
9
+ require "steno/tagged_logger"
10
+ require "steno/record"
11
+ require "steno/sink"
12
+ require "steno/version"
13
+
14
+ module Steno
15
+ class << self
16
+
17
+ attr_reader :config
18
+ attr_reader :logger_regexp
19
+
20
+ # Initializes the logging system. This must be called exactly once before
21
+ # attempting to use any Steno class methods.
22
+ #
23
+ # @param [Steno::Config]
24
+ #
25
+ # @return [nil]
26
+ def init(config)
27
+ @config = config
28
+
29
+ @loggers = {}
30
+ @loggers_lock = Mutex.new
31
+
32
+ @logger_regexp = nil
33
+ @logger_regexp_level = nil
34
+
35
+ nil
36
+ end
37
+
38
+ # Returns (and memoizes) the logger identified by name.
39
+ #
40
+ # @param [String] name
41
+ #
42
+ # @return [Steno::Logger]
43
+ def logger(name)
44
+ @loggers_lock.synchronize do
45
+ logger = @loggers[name]
46
+
47
+ if logger.nil?
48
+ level = compute_level(name)
49
+
50
+ logger = Steno::Logger.new(name, @config.sinks,
51
+ :level => level,
52
+ :context => @config.context)
53
+
54
+ @loggers[name] = logger
55
+ end
56
+
57
+ logger
58
+ end
59
+ end
60
+
61
+ # Sets all loggers whose name matches _regexp_ to _level_. Resets any
62
+ # loggers whose name matches the previous regexp but not the supplied regexp.
63
+ #
64
+ # @param [Regexp] regexp
65
+ # @param [Symbol] level
66
+ #
67
+ # @return [nil]
68
+ def set_logger_regexp(regexp, level)
69
+ @loggers_lock.synchronize do
70
+ @loggers.each do |name, logger|
71
+ if name =~ regexp
72
+ logger.level = level
73
+ elsif @logger_regexp && (name =~ @logger_regexp)
74
+ # Reset loggers affected by the old regexp but not by the new
75
+ logger.level = @config.default_log_level
76
+ end
77
+ end
78
+
79
+ @logger_regexp = regexp
80
+ @logger_regexp_level = level
81
+
82
+ nil
83
+ end
84
+ end
85
+
86
+ # Clears the logger regexp, if set. Resets the level of any loggers matching
87
+ # the regex to the default log level.
88
+ #
89
+ # @return [nil]
90
+ def clear_logger_regexp
91
+ @loggers_lock.synchronize do
92
+ return if @logger_regexp.nil?
93
+
94
+ @loggers.each do |name, logger|
95
+ if name =~ @logger_regexp
96
+ logger.level = @config.default_log_level
97
+ end
98
+ end
99
+
100
+ @logger_regexp = nil
101
+ @logger_regexp_level = nil
102
+ end
103
+
104
+ nil
105
+ end
106
+
107
+
108
+ # @return [Hash] Map of logger name => level
109
+ def logger_level_snapshot
110
+ @loggers_lock.synchronize do
111
+ snapshot = {}
112
+
113
+ @loggers.each { |name, logger| snapshot[name] = logger.level }
114
+
115
+ snapshot
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def compute_level(name)
122
+ if @logger_regexp && name =~ @logger_regexp
123
+ @logger_regexp_level
124
+ else
125
+ @config.default_log_level
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ # Initialize with an empty config. All log records will swallowed until Steno
132
+ # is re-initialized with sinks.
133
+ Steno.init(Steno::Config.new)
@@ -0,0 +1,2 @@
1
+ require "steno/codec/base"
2
+ require "steno/codec/json"
@@ -0,0 +1,34 @@
1
+ module Steno
2
+ module Codec
3
+ end
4
+ end
5
+
6
+ class Steno::Codec::Base
7
+ # Encodes the supplied record as a string.
8
+ #
9
+ # @param [Hash] record
10
+ #
11
+ # @return [String]
12
+ def encode_record(record)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ private
17
+
18
+ # Hex encodes non-printable ascii characters.
19
+ #
20
+ # @param [String] data
21
+ #
22
+ # @return [String]
23
+ def escape_nonprintable_ascii(data)
24
+ data.chars.map do |c|
25
+ ord_val = c.ord
26
+
27
+ if (ord_val > 31) && (ord_val < 127)
28
+ c
29
+ else
30
+ "\\x%02x" % [ord_val]
31
+ end
32
+ end.join
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ require "yajl"
2
+
3
+ require "steno/codec/base"
4
+
5
+ module Steno
6
+ module Codec
7
+ end
8
+ end
9
+
10
+ class Steno::Codec::Json < Steno::Codec::Base
11
+ def encode_record(record)
12
+ msg =
13
+ if record.message.valid_encoding?
14
+ record.message
15
+ else
16
+ # Treat the message as an arbitrary sequence of bytes.
17
+ escape_nonprintable_ascii(record.message.dup.force_encoding("BINARY"))
18
+ end
19
+
20
+ h = {
21
+ "timestamp" => record.timestamp.to_f,
22
+ "message" => msg,
23
+ "log_level" => record.log_level.to_s,
24
+ "source" => record.source,
25
+ "data" => record.data,
26
+ "thread_id" => record.thread_id,
27
+ "fiber_id" => record.fiber_id,
28
+ "process_id" => record.process_id,
29
+ "file" => record.file,
30
+ "lineno" => record.lineno,
31
+ "method" => record.method,
32
+ }
33
+
34
+ Yajl::Encoder.encode(h) + "\n"
35
+ end
36
+ end
@@ -0,0 +1,97 @@
1
+ require "yaml"
2
+
3
+ require "steno/codec"
4
+ require "steno/context"
5
+ require "steno/logger"
6
+ require "steno/sink"
7
+
8
+ module Steno
9
+ end
10
+
11
+ class Steno::Config
12
+ class << self
13
+ # Creates a config given a yaml file of the following form:
14
+ #
15
+ # logging:
16
+ # level: <info, debug, etc>
17
+ # file: </path/to/logfile>
18
+ # syslog: <syslog name>
19
+ #
20
+ # @param [String] path Path to yaml config
21
+ # @param [Hash] overrides
22
+ #
23
+ # @return [Steno::Config]
24
+ def from_file(path, overrides = {})
25
+ h = YAML.load_file(path)
26
+ h = h["logging"] || {}
27
+ new(to_config_hash(h).merge(overrides))
28
+ end
29
+
30
+ def from_hash(hash)
31
+ new(to_config_hash(hash))
32
+ end
33
+
34
+ def to_config_hash(hash)
35
+ hash ||= {}
36
+ hash = symbolize_keys(hash)
37
+
38
+ level = hash[:level] || hash[:default_log_level]
39
+ opts = {
40
+ :sinks => [],
41
+ :default_log_level => level.nil? ? :info : level.to_sym
42
+ }
43
+
44
+ if hash[:file]
45
+ max_retries = hash[:max_retries]
46
+ opts[:sinks] << Steno::Sink::IO.for_file(hash[:file], :max_retries => max_retries)
47
+ end
48
+
49
+ if Steno::Sink::WINDOWS
50
+ if hash[:eventlog]
51
+ Steno::Sink::Eventlog.instance.open(hash[:eventlog])
52
+ opts[:sinks] << Steno::Sink::Eventlog.instance
53
+ end
54
+ else
55
+ if hash[:syslog]
56
+ Steno::Sink::Syslog.instance.open(hash[:syslog])
57
+ opts[:sinks] << Steno::Sink::Syslog.instance
58
+ end
59
+ end
60
+
61
+ if hash[:fluentd]
62
+ opts[:sinks] << Steno::Sink::Fluentd.new(hash[:fluentd])
63
+ end
64
+
65
+ if opts[:sinks].empty?
66
+ opts[:sinks] << Steno::Sink::IO.new(STDOUT)
67
+ end
68
+
69
+ opts
70
+ end
71
+
72
+ def symbolize_keys(hash)
73
+ Hash[hash.each_pair.map { |k, v| [k.to_sym, v] }]
74
+ end
75
+ end
76
+
77
+ attr_reader :sinks
78
+ attr_reader :codec
79
+ attr_reader :context
80
+ attr_reader :default_log_level
81
+
82
+ def initialize(opts = {})
83
+ @sinks = opts[:sinks] || []
84
+ @codec = opts[:codec] || Steno::Codec::Json.new
85
+ @context = opts[:context] ||Steno::Context::Null.new
86
+
87
+ @sinks.each { |sink| sink.codec = @codec }
88
+
89
+ if opts[:default_log_level]
90
+ @default_log_level = opts[:default_log_level].to_sym
91
+ else
92
+ @default_log_level = :info
93
+ end
94
+ end
95
+
96
+ private_class_method :symbolize_keys
97
+ end