steno 0.0.3

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