termite 0.0.2 → 0.0.10

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