steno 1.2.2-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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