steno 0.0.3

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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ spec/reports
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in steno.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 mpage
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
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
+ ## Getting started
15
+ config = Steno::Config.new(
16
+ :sinks => [Steno::Sink::IO.new(STDOUT)],
17
+ :codec => Steno::Codec::Json.new,
18
+ :context => Steno::Context::ThreadLocal.new)
19
+
20
+ Steno.init(config)
21
+
22
+ logger = Steno.logger("test")
23
+
24
+ logger.info("Hello world!")
25
+
26
+ ## File a Bug
27
+
28
+ To file a bug against Cloud Foundry Open Source and its components, sign up and use our
29
+ bug tracking system: [http://cloudfoundry.atlassian.net](http://cloudfoundry.atlassian.net)
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require "ci/reporter/rake/rspec"
3
+ require "rspec/core/rake_task"
4
+
5
+ desc "Run all specs"
6
+ RSpec::Core::RakeTask.new("spec") do |t|
7
+ t.rspec_opts = %w[--color --format documentation]
8
+ end
9
+
10
+ desc "Run all specs and provide output for ci"
11
+ RSpec::Core::RakeTask.new("spec:ci" => "ci:setup:rspec") do |t|
12
+ t.rspec_opts = %w[--no-color --format documentation]
13
+ end
14
+
data/lib/steno.rb ADDED
@@ -0,0 +1,134 @@
1
+ require "thread"
2
+
3
+ require "steno/codec"
4
+ require "steno/config"
5
+ require "steno/context"
6
+ require "steno/core_ext"
7
+ require "steno/errors"
8
+ require "steno/log_level"
9
+ require "steno/logger"
10
+ require "steno/tagged_logger"
11
+ require "steno/record"
12
+ require "steno/sink"
13
+ require "steno/version"
14
+
15
+ module Steno
16
+ class << self
17
+
18
+ attr_reader :config
19
+ attr_reader :logger_regexp
20
+
21
+ # Initializes the logging system. This must be called exactly once before
22
+ # attempting to use any Steno class methods.
23
+ #
24
+ # @param [Steno::Config]
25
+ #
26
+ # @return [nil]
27
+ def init(config)
28
+ @config = config
29
+
30
+ @loggers = {}
31
+ @loggers_lock = Mutex.new
32
+
33
+ @logger_regexp = nil
34
+ @logger_regexp_level = nil
35
+
36
+ nil
37
+ end
38
+
39
+ # Returns (and memoizes) the logger identified by name.
40
+ #
41
+ # @param [String] name
42
+ #
43
+ # @return [Steno::Logger]
44
+ def logger(name)
45
+ @loggers_lock.synchronize do
46
+ logger = @loggers[name]
47
+
48
+ if logger.nil?
49
+ level = compute_level(name)
50
+
51
+ logger = Steno::Logger.new(name, @config.sinks,
52
+ :level => level,
53
+ :context => @config.context)
54
+
55
+ @loggers[name] = logger
56
+ end
57
+
58
+ logger
59
+ end
60
+ end
61
+
62
+ # Sets all loggers whose name matches _regexp_ to _level_. Resets any
63
+ # loggers whose name matches the previous regexp but not the supplied regexp.
64
+ #
65
+ # @param [Regexp] regexp
66
+ # @param [Symbol] level
67
+ #
68
+ # @return [nil]
69
+ def set_logger_regexp(regexp, level)
70
+ @loggers_lock.synchronize do
71
+ @loggers.each do |name, logger|
72
+ if name =~ regexp
73
+ logger.level = level
74
+ elsif @logger_regexp && (name =~ @logger_regexp)
75
+ # Reset loggers affected by the old regexp but not by the new
76
+ logger.level = @config.default_log_level
77
+ end
78
+ end
79
+
80
+ @logger_regexp = regexp
81
+ @logger_regexp_level = level
82
+
83
+ nil
84
+ end
85
+ end
86
+
87
+ # Clears the logger regexp, if set. Resets the level of any loggers matching
88
+ # the regex to the default log level.
89
+ #
90
+ # @return [nil]
91
+ def clear_logger_regexp
92
+ @loggers_lock.synchronize do
93
+ return if @logger_regexp.nil?
94
+
95
+ @loggers.each do |name, logger|
96
+ if name =~ @logger_regexp
97
+ logger.level = @config.default_log_level
98
+ end
99
+ end
100
+
101
+ @logger_regexp = nil
102
+ @logger_regexp_level = nil
103
+ end
104
+
105
+ nil
106
+ end
107
+
108
+
109
+ # @return [Hash] Map of logger name => level
110
+ def logger_level_snapshot
111
+ @loggers_lock.synchronize do
112
+ snapshot = {}
113
+
114
+ @loggers.each { |name, logger| snapshot[name] = logger.level }
115
+
116
+ snapshot
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def compute_level(name)
123
+ if @logger_regexp && name =~ @logger_regexp
124
+ @logger_regexp_level
125
+ else
126
+ @config.default_log_level
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ # Initialize with an empty config. All log records will swallowed until Steno
133
+ # is re-initialized with sinks.
134
+ 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,56 @@
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
+ def from_file(path, overrides = {})
14
+ h = YAML.load_file(path)
15
+
16
+ opts = {
17
+ :sinks => [],
18
+ :default_log_level => h["level"].to_sym,
19
+ }
20
+
21
+ if h["file"]
22
+ opts[:sinks] << Steno::Sink::IO.for_file(h["file"])
23
+ end
24
+
25
+ if h["syslog"]
26
+ Steno::Sink::Syslog.instance.open(h["syslog"])
27
+ opts[:sinks] << Steno::Sink::Syslog.instance
28
+ end
29
+
30
+ if opts[:sinks].empty?
31
+ opts[:sinks] << Steno::Sink::IO.new(STDOUT)
32
+ end
33
+
34
+ new(opts.merge(overrides))
35
+ end
36
+ end
37
+
38
+ attr_reader :sinks
39
+ attr_reader :codec
40
+ attr_reader :context
41
+ attr_reader :default_log_level
42
+
43
+ def initialize(opts = {})
44
+ @sinks = opts[:sinks] || []
45
+ @codec = opts[:codec] || Steno::Codec::Json.new
46
+ @context = opts[:context] ||Steno::Context::Null.new
47
+
48
+ @sinks.each { |sink| sink.codec = @codec }
49
+
50
+ if opts[:default_log_level]
51
+ @default_log_level = opts[:default_log_level].to_sym
52
+ else
53
+ @default_log_level = :info
54
+ end
55
+ end
56
+ end
@@ -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,5 @@
1
+ class Class
2
+ def logger
3
+ Steno.logger(name)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Steno
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,42 @@
1
+ require "steno"
2
+
3
+ require "grape"
4
+
5
+ module Steno
6
+ end
7
+
8
+ class Steno::HttpHandler < Grape::API
9
+ format :json
10
+ error_format :json
11
+
12
+ resource :loggers do
13
+ get :levels do
14
+ Steno.logger_level_snapshot
15
+ end
16
+
17
+ put :levels do
18
+ missing = [:regexp, :level].select { |p| !params.key?(p) }.map(&:to_s)
19
+
20
+ if !missing.empty?
21
+ error!("Missing query parameters: #{missing}", 400)
22
+ end
23
+
24
+ regexp = nil
25
+ begin
26
+ regexp = Regexp.new(params[:regexp])
27
+ rescue => e
28
+ error!("Invalid regexp", 400)
29
+ end
30
+
31
+ level = params[:level].to_sym
32
+ if !Steno::Logger::LEVELS.key?(level)
33
+ levels = Steno::Logger::LEVELS.keys.map(&:to_s)
34
+ error!("Unknown level: #{level}. Supported levels are: #{levels}", 400)
35
+ end
36
+
37
+ Steno.set_logger_regexp(regexp, level)
38
+
39
+ "ok"
40
+ end
41
+ end
42
+ end