termite 0.0.2 → 0.0.10

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 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+ source "http://gems.sv2"
3
+
4
+ # Specify your gem's dependencies in your gemspec
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ termite (0.0.10)
5
+ ecology (~> 0.0.6)
6
+ multi_json
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ remote: http://gems.sv2/
11
+ specs:
12
+ ecology (0.0.7)
13
+ multi_json
14
+ minitest (2.3.0)
15
+ mocha (0.9.12)
16
+ multi_json (1.0.3)
17
+ rake (0.9.2)
18
+ scope (0.2.1)
19
+ minitest
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.10)
26
+ mocha
27
+ rake
28
+ scope (~> 0.2.1)
29
+ termite!
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Ooyala, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ Termite
2
+ =======
3
+
4
+ Termite is a gem to handle local logs. Specifically, it logs to
5
+ Syslog in a simple component-based manner, records thread IDs and
6
+ serializes JSON data.
7
+
8
+ It uses, but doesn't depend on, an Ecology file to specify things
9
+ like the default application name and other logging settings.
10
+
11
+ Installing
12
+ ==========
13
+
14
+ "gem install termite" works pretty well. You can also specify Termite
15
+ from a Gemfile if you're using Bundler (and you should).
16
+
17
+ Ooyalans should make sure that "gems.sv2" is listed as a gem source in
18
+ your Gemfile or on your gem command line.
19
+
20
+ Logging Dynamically
21
+ ===================
22
+
23
+ Create a logger with something like:
24
+
25
+ @logger = Termite::Logger.new
26
+
27
+ Then use it like a regular logger, possibly with options:
28
+
29
+ @logger.add(Logger::DEBUG, "so, like, this thing happened, right?", {}, :component => :ValleyGirl)
30
+ @logger.warn { "And I was like, whoah!" }
31
+
32
+ You can also pass in JSON data after your message and before your options:
33
+
34
+ @logger.fatal("Pain and misery!", { :where_it_hurts => "elbow" }, :component => "WhinyLib")
35
+ @logger.info("I ache", {:where_it_hurts => "kidney"}, :application => "NotMe", :component => "StoicLib")
36
+
37
+ Termite also supports full Ruby Logger initialize parameters for backward compatibility:
38
+
39
+ @logger = Termite::Logger.new("/var/lib/daily_termite_logs", "daily")
40
+ @logger = Termite::Logger.new("/tmp/rotatable.txt", 15, 1024000) # Up to 15 logs of size 1024000
41
+
42
+ Log Level
43
+ =========
44
+
45
+ Termite loggers, like Ruby loggers, allow you to set the logger's level. All events below that
46
+ level will be silently discarded, and will also not be sent to non-syslog loggers. You can change
47
+ the level with "logger.level = Logger::WARN" and similar.
48
+
49
+ By default, Termite will log events of all severities to standard output, and events of at least
50
+ severity :error to standard error. You can set .stdout_level and .stderr_level just like setting
51
+ .level above. Level takes precedence over the other two and stderr_level takes precedence over
52
+ stdout_level.
53
+
54
+ Non-Syslog Logging
55
+ ==================
56
+
57
+ If a filename or handle is specified in the constructor, Termite will instantiate a Ruby Logger
58
+ with the same parameters and mirror all events to it.
59
+
60
+ You can pass in objects with an :add method to a Termite logger's "add_extra_logger" method, and
61
+ from then on all events will be mirrored to that object. This can be useful for chaining loggers
62
+ if you need to. Events below the Termite logger's level (see above) won't be mirrored.
63
+
64
+ Translating to SysLog
65
+ =====================
66
+
67
+ When writing to SysLog, Termite translates Ruby Logger severities into SysLog severities. By
68
+ default, this is the mapping:
69
+
70
+ Logger => SysLog
71
+ :unknown => :alert
72
+ :fatal => :crit
73
+ :error => :err
74
+ :warn => :warning
75
+ :info => :info
76
+ :debug => :debug
77
+
78
+ Configuring with an Ecology
79
+ ===========================
80
+
81
+ Termite supports a standard Ecology file. By default, it will look at the location of the current
82
+ executable ($0) with extension .ecology. So "bob.rb" would have "bob.ecology" next to it.
83
+
84
+ An Ecology is a JSON file of roughly this structure:
85
+
86
+ {
87
+ "application": "MyApp",
88
+ "logging": {
89
+ "default_component": "SplodgingLib",
90
+ "extra_json_fields": {
91
+ "app_group": "SuperSpiffyGroup",
92
+ "precedence": 7
93
+ },
94
+ "console_print": "off",
95
+ "filename": "/tmp/bobo.txt",
96
+ "shift_age": 10,
97
+ "shift_size": 1024000,
98
+ "stderr_level": "fatal"
99
+ }
100
+ }
101
+
102
+ Absolutely every part of it is optional, including the presence of the file at all.
103
+
104
+ You can override the application name, as shown above. Other than the application name,
105
+ all Termite-specific parameters are under the "logging" field, as above.
106
+
107
+ The default_component is what application component is given for an add() call by default.
108
+ If set, it can be removed with ":component => nil" for a given add() call.
109
+
110
+ Extra JSON fields are added to the JSON data of every add() call.
111
+
112
+ Console_print can be set to "off" (or "no" or "0") to turn off Termite printing to stderr
113
+ and stdout by default at different log levels.
114
+
115
+ Filename, shift_age and shift_size are the same as Ruby Logger's normal initialize
116
+ parameters. The first is the filename to log to, the second is how many total log files
117
+ to keep, and the third is how large each log file can grow. Or the second can be set to
118
+ a value like "daily" or "monthly", and then the third is irrelevant.
119
+
120
+ You can also set level, stdout_level and stderr_level explained above. We allow numerical
121
+ (syslog) values, or standard names of Ruby Logger severities.
122
+
123
+ Releasing within Ooyala
124
+ =======================
125
+
126
+ Ooyalans, to release Termite to gems.sv2, use the following:
127
+
128
+ gem build
129
+ rake _0.8.7_ -f ../ooyala_gems.rake gem:push termite-0.0.1.gem
130
+
131
+ Change the version to the actual version you'd like to push.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require "bundler"
2
+ require "rake/testtask"
3
+
4
+ require File.join(File.dirname(__FILE__), "lib", "termite", "version")
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = Dir.glob("test/**/*test.rb")
9
+ t.verbose = true
10
+ end
11
+
12
+ desc 'Builds the gem'
13
+ task :build do
14
+ sh "gem build termite.gemspec"
15
+ end
16
+
17
+ desc 'Builds and installs the gem'
18
+ task :install => :build do
19
+ sh "gem install termite-#{Termite::VERSION}"
20
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ Ecology configuration:
2
+ * Extra loggers from Ecology
3
+ * Transport protocol from Ecology
@@ -0,0 +1,3 @@
1
+ module Termite
2
+ VERSION = "0.0.10"
3
+ end
data/lib/termite.rb ADDED
@@ -0,0 +1,259 @@
1
+ require "rubygems"
2
+ require "termite/version"
3
+
4
+ require "ecology"
5
+ require "multi_json"
6
+
7
+ require "thread"
8
+ require "syslog"
9
+ require "logger"
10
+ require "socket"
11
+
12
+ module Termite
13
+ class Logger
14
+ ##
15
+ # Maps Logger severities to syslog(3) severities.
16
+
17
+ LOGGER_MAP = {
18
+ :unknown => :alert,
19
+ :fatal => :crit,
20
+ :error => :err,
21
+ :warn => :warning,
22
+ :info => :info,
23
+ :debug => :debug,
24
+ }
25
+
26
+ ##
27
+ # Maps Ruby Logger log levels to their values so we can silence.
28
+
29
+ LOGGER_LEVEL_MAP = {}
30
+
31
+ LOGGER_MAP.each_key do |key|
32
+ LOGGER_LEVEL_MAP[key] = ::Logger.const_get key.to_s.upcase
33
+ end
34
+
35
+ ##
36
+ # Maps Logger numerical log level values to syslog log levels.
37
+
38
+ LEVEL_LOGGER_MAP = {}
39
+
40
+ LOGGER_LEVEL_MAP.invert.each do |level, severity|
41
+ LEVEL_LOGGER_MAP[level] = LOGGER_MAP[severity]
42
+ end
43
+
44
+ SYSLOG_SEVERITY_MAP = {}
45
+
46
+ LOGGER_MAP.values.each do |syslog_severity|
47
+ SYSLOG_SEVERITY_MAP[syslog_severity] = ::Syslog.const_get("LOG_" + syslog_severity.to_s.upcase)
48
+ end
49
+
50
+ ##
51
+ # Builds a methods for level +meth+.
52
+
53
+ def self.make_methods(meth)
54
+ eval <<-EOM, nil, __FILE__, __LINE__ + 1
55
+ def #{meth}(message = nil, data = {}, options = {}, &block)
56
+ return true if #{LOGGER_LEVEL_MAP[meth]} < @level
57
+ add(::Logger::#{meth.to_s.upcase}, message, data, options, &block)
58
+ end
59
+
60
+ def #{meth}?
61
+ @level <= ::Logger::#{meth.to_s.upcase}
62
+ end
63
+ EOM
64
+ end
65
+
66
+ LOGGER_MAP.each_key do |level|
67
+ make_methods level
68
+ end
69
+
70
+ ##
71
+ # Log level for Logger compatibility.
72
+
73
+ attr_accessor :level
74
+ attr_reader :stdout_level
75
+ attr_reader :stderr_level
76
+ attr_reader :file_logger
77
+
78
+ def initialize(logdev = nil, shift_age = 0, shift_size = 1048576, options = {})
79
+ options = logdev if(logdev.is_a?(Hash))
80
+
81
+ Ecology.read
82
+
83
+ read_ecology_data
84
+ @log_filename ||= logdev
85
+
86
+ @file_logger = ::Logger.new(@log_filename, @shift_age || shift_age, @shift_size || shift_size) if @log_filename
87
+ @extra_loggers << @file_logger if @file_logger
88
+
89
+ # For UDP socket
90
+ @server_addr = options[:address] || "0.0.0.0"
91
+ @server_port = options[:port] ? options[:port].to_i : 514
92
+
93
+ @@sockets ||= {}
94
+ key = "#{@server_addr}:#{@server_port}"
95
+ @@sockets[key] ||= UDPSocket.new
96
+ @socket = @@sockets[key]
97
+ end
98
+
99
+ private
100
+
101
+ def string_to_severity(str)
102
+ orig_string = str
103
+ str = str.strip.downcase
104
+ return str.to_i if str =~ /\d+/
105
+ ret = LOGGER_LEVEL_MAP[str.to_sym]
106
+ raise "Unknown logger severity #{orig_string}" unless ret
107
+ ret
108
+ end
109
+
110
+ def read_ecology_data
111
+ @application = Ecology.application
112
+
113
+ @extra_loggers = []
114
+
115
+ # @console_print defaults to "yes", but can be nil if "no", "off" or "0" is specified
116
+ @console_print = Ecology.property("logging::console_print") || "yes"
117
+ @console_print = nil if ["no", "off", "0"].include?(@console_print)
118
+
119
+ @log_filename = Ecology.property("logging::filename", :as => :path)
120
+ @shift_age = Ecology.property("logging::shift_age")
121
+ @shift_size = Ecology.property("logging::shift_size")
122
+ @default_component = Ecology.property("logging::default_component")
123
+ @level = Ecology.property("logging::level")
124
+ @level = string_to_severity(@level) if @level
125
+ @stderr_level = Ecology.property("logging::stderr_level")
126
+ @stderr_level = string_to_severity(@stderr_level) if @stderr_level
127
+ @stdout_level = Ecology.property("logging::stdout_level")
128
+ @stdout_level = string_to_severity(@stdout_level) if @stdout_level
129
+
130
+ @default_fields = Ecology.property("logging::extra_json_fields") || {}
131
+
132
+ @level ||= ::Logger::DEBUG
133
+ @stdout_level ||= ::Logger::INFO
134
+ @stderr_level ||= ::Logger::ERROR
135
+ end
136
+
137
+ public
138
+
139
+ def add_logger(logger)
140
+ @extra_loggers << logger
141
+ end
142
+
143
+ def socket
144
+ @socket
145
+ end
146
+
147
+ def raw_add(severity, message = nil, data = nil, options = {}, &block)
148
+ raw_message = message
149
+ # Severity is a numerical severity using Ruby Logger's scale
150
+ severity ||= ::Logger::UNKNOWN
151
+ return true if severity < @level
152
+
153
+ application = options[:application] || @application
154
+
155
+ component ||= @default_component
156
+ component = options[:component] if options.has_key?(:component)
157
+ if component
158
+ application += ":" + component
159
+ end
160
+
161
+ data ||= {}
162
+ if data.is_a?(Hash)
163
+ data = @default_fields.merge(data)
164
+ data = MultiJson.encode(data)
165
+ elsif data.is_a?(String)
166
+ # Can't merge a JSON string with default data
167
+ raise "Can't merge a JSON string with extra fields!" unless @default_fields.empty?
168
+ else
169
+ raise "Unknown data object passed as JSON!"
170
+ end
171
+
172
+ tid = Ecology.thread_id(Thread.current)
173
+ time = Time.now
174
+ day = time.strftime("%b %d").sub(/0(\d)/, ' \\1')
175
+ time_of_day = time.strftime("%T")
176
+ hostname = Socket.gethostname
177
+ tag = Syslog::LOG_LOCAL6 + SYSLOG_SEVERITY_MAP[LEVEL_LOGGER_MAP[severity]]
178
+
179
+ syslog_string = "<#{tag}>#{day} #{time_of_day} #{hostname} #{application} [#{Process.pid}]: [#{tid}] "
180
+ full_message = clean(message || block.call)
181
+
182
+ # ruby_severity is the Ruby Logger severity as a symbol
183
+ ruby_severity = LOGGER_LEVEL_MAP.invert[severity]
184
+
185
+ full_message.split("\n").each do |line|
186
+ message = syslog_string + "#{line} #{data}"
187
+
188
+ begin
189
+ @socket.send(message, 0, @server_addr, @server_port)
190
+ rescue Exception
191
+ # Didn't work. Try built-in Ruby syslog
192
+ require "syslog"
193
+ Syslog.open(application, Syslog::LOG_PID | Syslog::LOG_CONS) do |s|
194
+ s.error("UDP syslog failed! Falling back to libc syslog!") rescue nil
195
+ s.send(LEVEL_LOGGER_MAP[severity], "#{line} #{data}") rescue nil
196
+ end
197
+ end
198
+ end
199
+
200
+ if @console_print && severity >= @stderr_level
201
+ STDERR.puts raw_message
202
+ elsif @console_print && severity >= @stdout_level
203
+ STDOUT.puts raw_message
204
+ end
205
+
206
+ @extra_loggers.each do |logger|
207
+ logger.send(ruby_severity, raw_message) rescue nil
208
+ end
209
+
210
+ true
211
+ end
212
+
213
+ def add(*args)
214
+ begin
215
+ raw_add(*args)
216
+ rescue Exception => e
217
+ STDERR.puts("Couldn't log via Termite! Failing! Arguments:")
218
+ STDERR.puts(args.inspect)
219
+ STDERR.puts("Exception: #{e.message}")
220
+ STDERR.puts("Backtrace: #{e.backtrace.inspect}")
221
+ end
222
+ end
223
+
224
+ alias :log :add
225
+
226
+ # This isn't perfect. "Real" Ruby loggers use << to mean
227
+ # "write directly to the underlying store without adding
228
+ # headers or other cruft". That's meaningful for file
229
+ # loggers, but not for syslog.
230
+ def << (message)
231
+ add(::Logger::INFO, message)
232
+ end
233
+
234
+ ##
235
+ # Allows messages of a particular log level to be ignored temporarily.
236
+ #
237
+ # Can you say "Broken Windows"?
238
+
239
+ def silence(temporary_level = ::Logger::ERROR)
240
+ old_logger_level = @level
241
+ @level = temporary_level
242
+ yield
243
+ ensure
244
+ @level = old_logger_level
245
+ end
246
+
247
+ private
248
+
249
+ ##
250
+ # Clean up messages so they're nice and pretty.
251
+
252
+ def clean(message)
253
+ message = message.to_s.dup
254
+ message.strip!
255
+ message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes
256
+ return message
257
+ end
258
+ end
259
+ end
data/termite.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "termite/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "termite"
8
+ s.version = Termite::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Noah Gibbs"]
11
+ s.email = ["noah@ooyala.com"]
12
+ s.homepage = "http://www.ooyala.com"
13
+ s.summary = %q{Ruby logging based on Syslog}
14
+ s.description = <<EOS
15
+ Termite wraps syslog with a format for extra data, and for
16
+ what you wish it would send automatically.
17
+ EOS
18
+
19
+ s.rubyforge_project = "termite"
20
+
21
+ ignores = File.readlines(".gitignore").grep(/\S+/).map {|pattern| pattern.chomp }
22
+ dotfiles = Dir[".*"]
23
+ s.files = Dir["**/*"].reject {|f| File.directory?(f) || ignores.any? {|i| File.fnmatch(i, f) } } + dotfiles
24
+ s.test_files = s.files.grep(/^test\//)
25
+
26
+ s.require_paths = ["lib"]
27
+
28
+ s.add_dependency "multi_json"
29
+ s.add_dependency "ecology", "~>0.0.6"
30
+
31
+ s.add_development_dependency "bundler", "~> 1.0.10"
32
+ s.add_development_dependency "scope", "~> 0.2.1"
33
+ s.add_development_dependency "mocha"
34
+ s.add_development_dependency "rake"
35
+ end
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class EcologyLogTest < Scope::TestCase
4
+ context "with a custom ecology" do
5
+ setup do
6
+ Ecology.reset
7
+
8
+ set_up_ecology <<ECOLOGY_CONTENTS
9
+ {
10
+ "application": "MyApp",
11
+ "logging": {
12
+ "default_component": "SplodgingLib",
13
+ "extra_json_fields": {
14
+ "app_group": "SuperSpiffyGroup",
15
+ "precedence": 7
16
+ },
17
+ "console_print": "off",
18
+ "filename": "/tmp/bobo.txt",
19
+ "shift_age": 10,
20
+ "shift_size": 1024000
21
+ }
22
+ }
23
+ ECOLOGY_CONTENTS
24
+ end
25
+
26
+ context "with a default termite logger" do
27
+ setup do
28
+ @logger = Termite::Logger.new
29
+ end
30
+
31
+ should "send back extra JSON data and a default component when specified" do
32
+ expect_add(@logger.socket, 2, 'oh no! {"app_group":"SuperSpiffyGroup","precedence":7}', :application => "MyApp:SplodgingLib")
33
+ @logger.fatal("oh no!")
34
+ end
35
+
36
+ should "allow overriding the default component" do
37
+ expect_add(@logger.socket, 2, 'oh no! {"app_group":"SuperSpiffyGroup","precedence":7}', :application => "MyApp:SpliyingLib")
38
+ @logger.fatal("oh no!", {}, :component => "SpliyingLib")
39
+ end
40
+
41
+ should "allow overriding the default component with nothing" do
42
+ expect_add(@logger.socket, 2, 'oh no! {"app_group":"SuperSpiffyGroup","precedence":7}', :application => "MyApp")
43
+ @logger.fatal("oh no!", {}, :component => nil)
44
+ end
45
+ end
46
+
47
+ should "pass initialize parameters to Ruby Logger" do
48
+ log_mock = mock("Ruby Logger")
49
+ ::Logger.expects(:new).with("/tmp/bobo.txt", 10, 1024000).returns(log_mock)
50
+ Termite::Logger.new
51
+ end
52
+
53
+ should "override parameters passed to Termite Logger" do
54
+ log_mock = mock("Ruby Logger")
55
+ ::Logger.expects(:new).with("/tmp/bobo.txt", 10, 1024000).returns(log_mock)
56
+ Termite::Logger.new("/var/lib/sam.log", "daily")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class TermiteExtraLoggerTest < Scope::TestCase
4
+ context "with termite" do
5
+ setup do
6
+ $0 = "MyApp"
7
+ Ecology.reset
8
+ end
9
+
10
+ context "and two extra loggers added" do
11
+ setup do
12
+ @logger = Termite::Logger.new("/tmp/test_log_output.txt") # Test with output file
13
+ @logger.level = Logger::DEBUG
14
+ @logger.socket.expects(:send)
15
+ @mock_logger_1 = mock()
16
+ @logger.add_logger(@mock_logger_1)
17
+ @logger.add_logger(@mock_logger_2)
18
+ end
19
+
20
+ should "correctly send logs to Syslog" do
21
+ @mock_logger_1.expects(:fatal)
22
+ @mock_logger_2.expects(:fatal)
23
+ @logger.add(Logger::FATAL, "foo!", {})
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "termite"
4
+
5
+ logger = Termite::Logger.new
6
+ logger.fatal("Test logging from log_locally.rb!")
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+ require "syslog"
3
+
4
+ class RescueTest < Scope::TestCase
5
+ context "with termite" do
6
+ setup do
7
+ Ecology.reset
8
+ @logger = Termite::Logger.new("/tmp/termite_test.log")
9
+ end
10
+
11
+ should "continue even if all loggers raise errors" do
12
+ # First, UDP will raise an error
13
+ @logger.socket.expects(:send).raises(StandardError, "You suck!")
14
+
15
+ # Termite should fall back to trying Ruby Syslog...
16
+ syslog_mock = mock("Syslog connection")
17
+ Syslog.expects(:open).yields(syslog_mock)
18
+ syslog_mock.expects(:error).with("UDP syslog failed! Falling back to libc syslog!")
19
+ syslog_mock.expects(:crit).raises(StandardError, "You suck even more than that!")
20
+
21
+ # And it should still try to write to a file logger
22
+ @logger.file_logger.expects(:fatal).raises(StandardError, "You suck lots!")
23
+
24
+ extra_logger = mock("Additional logger")
25
+ @logger.add_logger(extra_logger)
26
+ # And it should try to write to any extra loggers
27
+ extra_logger.expects(:fatal).raises(StandardError, "You suck constantly!")
28
+
29
+ # And yet, nothing should be raised to the outside world
30
+ begin
31
+ @logger.fatal("Woe is me!")
32
+ assert true, "Nothing was raised! Yay!"
33
+ rescue Exception
34
+ flunk "Logging an event raised an assertion outside the logger!"
35
+ end
36
+ end
37
+
38
+ should "continue even if internal logic gives an error" do
39
+ #Time.expects(:now).raises(Exception.new "You suck!")
40
+ Ecology.expects(:thread_id).raises(Exception.new "Ecology thread_id dies!")
41
+ begin
42
+ @logger.fatal("Woe is me!")
43
+ assert true, "Nothing was raised! Yay!"
44
+ rescue Exception
45
+ flunk "Logging an event raised an assertion outside the logger!"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class EcologyLogTest < Scope::TestCase
4
+ context "with a custom ecology" do
5
+ setup do
6
+ Ecology.reset
7
+
8
+ set_up_ecology <<ECOLOGY_CONTENTS
9
+ {
10
+ "application": "MyApp",
11
+ "logging": {
12
+ "stdout_level": "warn",
13
+ "stderr_level": "fatal"
14
+ }
15
+ }
16
+ ECOLOGY_CONTENTS
17
+ end
18
+
19
+ context "with a default termite logger" do
20
+ setup do
21
+ @logger = Termite::Logger.new
22
+ end
23
+
24
+ should "log fatal errors to STDERR" do
25
+ expect_console_add(STDERR, 2, 'oh no!', :application => "MyApp", :method => :puts, :extra_args => [])
26
+ STDOUT.expects(:puts).never
27
+ @logger.fatal("oh no!")
28
+ end
29
+
30
+ should "log warnings to STDOUT" do
31
+ expect_console_add(STDOUT, 4, 'oh no!', :application => "MyApp", :method => :puts, :extra_args => [])
32
+ STDERR.expects(:puts).never
33
+ @logger.warn("oh no!")
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,85 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+ require "syslog"
3
+
4
+ class TermiteLoggerTest < Scope::TestCase
5
+ context "with termite ecology" do
6
+ setup do
7
+ Ecology.reset
8
+
9
+ set_up_ecology <<ECOLOGY_TEXT
10
+ {
11
+ "application": "foo_app"
12
+ }
13
+ ECOLOGY_TEXT
14
+ end
15
+
16
+ context "and fully permissive logging levels set" do
17
+ setup do
18
+ @logger = Termite::Logger.new("/tmp/test_log_output.txt") # Test with output file
19
+ @logger.level = Logger::DEBUG
20
+ end
21
+
22
+ should "correctly send logs to Syslog" do
23
+ expect_add(@logger.socket, 2, "foo! {}")
24
+ @logger.add(Logger::FATAL, "foo!", {})
25
+ end
26
+
27
+ should "correctly alias log to add" do
28
+ expect_add(@logger.socket, 2, "foo! {}")
29
+ @logger.log(Logger::FATAL, "foo!", {})
30
+ end
31
+
32
+ should "treat << as add-with-info" do
33
+ expect_add(@logger.socket, 6, "foo! {}")
34
+ @logger << "foo!"
35
+ end
36
+
37
+ should "correctly send an alert to Syslog" do
38
+ expect_add(@logger.socket, 1, "foo! {}")
39
+ @logger.add(Logger::UNKNOWN, "foo!", {})
40
+ end
41
+
42
+ should "correctly send a critical event to Syslog" do
43
+ expect_add(@logger.socket, 2, "foo! {}")
44
+ @logger.fatal("foo!")
45
+ end
46
+
47
+ should "correctly send an error event to Syslog" do
48
+ expect_add(@logger.socket, 3, "foo! {}")
49
+ @logger.error("foo!")
50
+ end
51
+
52
+ should "correctly send a warning event to Syslog" do
53
+ expect_add(@logger.socket, 4, "foo! {}")
54
+ @logger.warn("foo!")
55
+ end
56
+
57
+ should "correctly send an info event to Syslog" do
58
+ expect_add(@logger.socket, 6, "foo! {}")
59
+ @logger.info("foo!")
60
+ end
61
+
62
+ should "correctly send a debug event to Syslog" do
63
+ expect_add(@logger.socket, 7, "foo! {}")
64
+ @logger.debug("foo!")
65
+ end
66
+ end
67
+
68
+ context "and default setup for components" do
69
+ setup do
70
+ @logger = Termite::Logger.new("/tmp/test_log_output.txt") # Test with output file
71
+ end
72
+
73
+ should "allow overriding application name" do
74
+ expect_add(@logger.socket, 2, "foo! {}", :application => "bar_app")
75
+ @logger.fatal("foo!", {}, :application => "bar_app")
76
+ end
77
+
78
+ should "allow setting a component" do
79
+ expect_add(@logger.socket, 2, "foo! {}", :application => "foo_app:whatcomponent")
80
+ @logger.fatal("foo!", {}, :component => "whatcomponent")
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,29 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class SubLogger < Termite::Logger
4
+ end
5
+
6
+ class TermiteSubclassTest < Scope::TestCase
7
+ context "with subclassed termite" do
8
+ setup do
9
+ Ecology.reset
10
+
11
+ set_up_ecology <<ECOLOGY_TEXT
12
+ {
13
+ "application": "foo_app"
14
+ }
15
+ ECOLOGY_TEXT
16
+ end
17
+
18
+ context "and only default logging levels set" do
19
+ setup do
20
+ @logger = SubLogger.new
21
+ end
22
+
23
+ should "correctly send logs to Syslog" do
24
+ @logger.socket.expects(:send)
25
+ @logger.add(Logger::FATAL, "foo!", {})
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.require(:default, :development)
4
+ require "minitest/autorun"
5
+
6
+ # For testing Termite itself, use the local version *first*.
7
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
8
+
9
+ require "termite"
10
+
11
+ class Scope::TestCase
12
+ def set_up_ecology(file_contents, filename = "some.ecology")
13
+ ENV["ECOLOGY_SPEC"] = filename
14
+ File.expects(:exist?).with(filename).returns(true)
15
+ File.expects(:read).with(filename).returns(file_contents)
16
+ end
17
+
18
+ def initialize_environment
19
+ unless @initialized_test_env
20
+ Time.stubs(:now).returns(Time.at(1315433360))
21
+ Socket.stubs(:gethostname).returns("samplehost")
22
+ Process.stubs(:pid).returns("1234")
23
+ Ecology.stubs(:thread_id).returns("main")
24
+
25
+ @initialized_test_env = true
26
+ end
27
+ end
28
+
29
+ # This adds the Mocha expectation for this call. Technically it also
30
+ # returns the expectation, so you could modify it later if you wanted.
31
+ def expect_add(socket, severity_num, message, options = {})
32
+ initialize_environment
33
+
34
+ app = options[:application] || "foo_app"
35
+ string = "<#{Syslog::LOG_LOCAL6 + severity_num}>Sep 7 15:09:20 samplehost #{app} [1234]: [main] #{message}"
36
+
37
+ options[:method] ||= :send
38
+ options[:extra_args] ||= [0, "0.0.0.0", 514]
39
+ socket.expects(options[:method]).with(string, *options[:extra_args])
40
+ end
41
+
42
+ def expect_console_add(socket, severity_num, message, options = {})
43
+ initialize_environment
44
+
45
+ app = options[:application] || "foo_app"
46
+
47
+ options[:method] ||= :send
48
+ options[:extra_args] ||= [0, "0.0.0.0", 514]
49
+ socket.expects(options[:method]).with(message, *options[:extra_args])
50
+ end
51
+ end
metadata CHANGED
@@ -1,45 +1,147 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: termite
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.2
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.0.10
6
6
  platform: ruby
7
- authors:
8
- - Caleb Spare
7
+ authors:
8
+ - Noah Gibbs
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-23 00:00:00.000000000Z
13
- dependencies: []
14
- description: termite
15
- email:
16
- - caleb@ooyala.com
12
+
13
+ date: 2011-09-30 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: multi_json
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: ecology
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 0.0.6
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: bundler
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.10
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: scope
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 0.2.1
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: mocha
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id006
81
+ description: |
82
+ Termite wraps syslog with a format for extra data, and for
83
+ what you wish it would send automatically.
84
+
85
+ email:
86
+ - noah@ooyala.com
17
87
  executables: []
88
+
18
89
  extensions: []
90
+
19
91
  extra_rdoc_files: []
20
- files: []
21
- homepage: ''
92
+
93
+ files:
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - lib/termite/version.rb
97
+ - lib/termite.rb
98
+ - LICENSE
99
+ - Rakefile
100
+ - README.md
101
+ - termite.gemspec
102
+ - test/ecology_log_test.rb
103
+ - test/extra_logger_test.rb
104
+ - test/log_locally.rb
105
+ - test/rescue_test.rb
106
+ - test/stderr_log_test.rb
107
+ - test/termite_logger_test.rb
108
+ - test/termite_subclass_test.rb
109
+ - test/test_helper.rb
110
+ - TODO
111
+ - .gitignore
112
+ homepage: http://www.ooyala.com
22
113
  licenses: []
114
+
23
115
  post_install_message:
24
116
  rdoc_options: []
25
- require_paths:
117
+
118
+ require_paths:
26
119
  - lib
27
- required_ruby_version: !ruby/object:Gem::Requirement
120
+ required_ruby_version: !ruby/object:Gem::Requirement
28
121
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: "0"
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
127
  none: false
35
- requirements:
36
- - - ! '>='
37
- - !ruby/object:Gem::Version
38
- version: '0'
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: "0"
39
132
  requirements: []
133
+
40
134
  rubyforge_project: termite
41
- rubygems_version: 1.8.7
135
+ rubygems_version: 1.8.10
42
136
  signing_key:
43
137
  specification_version: 3
44
- summary: termite
45
- test_files: []
138
+ summary: Ruby logging based on Syslog
139
+ test_files:
140
+ - test/ecology_log_test.rb
141
+ - test/extra_logger_test.rb
142
+ - test/log_locally.rb
143
+ - test/rescue_test.rb
144
+ - test/stderr_log_test.rb
145
+ - test/termite_logger_test.rb
146
+ - test/termite_subclass_test.rb
147
+ - test/test_helper.rb