notarius 0.1.0
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/LICENSE.md +20 -0
- data/README.md +149 -0
- data/lib/notarius.rb +82 -0
- data/lib/notarius/config.rb +6 -0
- data/lib/notarius/exception.rb +4 -0
- data/lib/notarius/formatter.rb +72 -0
- data/lib/notarius/secretary.rb +88 -0
- data/lib/notarius/version.rb +6 -0
- data/notarius.gemspec +30 -0
- data/rakefile.rb +43 -0
- data/spec/config_spec.rb +21 -0
- data/spec/formatter_spec.rb +106 -0
- data/spec/notarius_spec.rb +116 -0
- data/spec/secretary_spec.rb +128 -0
- data/spec/simplecov_helper.rb +2 -0
- metadata +119 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Frank Mitchell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files
|
5
|
+
(the "Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included
|
12
|
+
in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
Notarius
|
2
|
+
========
|
3
|
+
|
4
|
+
Notarius is a logging library with opinions. The word "notarius" is
|
5
|
+
Latin for "shorthand writer". To this end, Notarius does everything
|
6
|
+
possible to encourage you to write short useful log messages.
|
7
|
+
|
8
|
+
* Whitespace is converted to spaces.
|
9
|
+
* Lines are truncated to 140 characters.
|
10
|
+
* Timestamps are formatted as [ISO 8601][].
|
11
|
+
* Call stacks are easy to grep.
|
12
|
+
* Duplicate messages are discarded.
|
13
|
+
|
14
|
+
Notarius does not want you to be happy now, while you're writing code.
|
15
|
+
It wants the person reading your logs at 3am trying to figure out why
|
16
|
+
your code doesn't work to be happy. If those happen to be the same
|
17
|
+
person, Notarius would rather future you be happier.
|
18
|
+
|
19
|
+
There is infinitely more future than there is now.
|
20
|
+
|
21
|
+
Installation
|
22
|
+
------------
|
23
|
+
|
24
|
+
Notarius is packaged as a Ruby gem. You do `gem install notarius` from
|
25
|
+
the command line to get it.
|
26
|
+
|
27
|
+
Configuration
|
28
|
+
-------------
|
29
|
+
|
30
|
+
Notarius is namespaced. This lets multiple libraries use Notarius
|
31
|
+
without clobbering each others logs.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'notarius'
|
35
|
+
|
36
|
+
Notarius.configure 'BIG' do |log|
|
37
|
+
log.file = '/var/log/notarius/notebook.log'
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
If you try to log to the same file as another library, Notarius will
|
42
|
+
yell at you and throw an exception. Notarius does not want you to
|
43
|
+
obliterate someone else's carefully crafted logs. However, it will tell
|
44
|
+
you what they've namespaced their logs so you can redirect them
|
45
|
+
elsewhere.
|
46
|
+
|
47
|
+
By default, Notarius doesn't log to anything, not even the console. If
|
48
|
+
you want console logging, you have to enable it.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
Notarius.configure 'BIG' do |log|
|
52
|
+
log.console = true
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Notarius is fine with you calling the `configure` method multiple times.
|
57
|
+
This means you can set up defaults when your program starts and change
|
58
|
+
them later.
|
59
|
+
|
60
|
+
Usage
|
61
|
+
-----
|
62
|
+
|
63
|
+
Using Notarius is simple. Include the `Notarius::NAME` module (where
|
64
|
+
NAME is the string you passed to the `configure` method) and call
|
65
|
+
`log.info`, `log.warn`, or `log.error`. You can log anything that
|
66
|
+
responds to `:message`, `:backtrace`, or `:inspect`.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class Player
|
70
|
+
include Notarius::BIG
|
71
|
+
|
72
|
+
def move direction
|
73
|
+
log.info "You head off down the #{direction} path."
|
74
|
+
end
|
75
|
+
|
76
|
+
def poke object
|
77
|
+
log.warn "Gingerly, you nudge the #{object}."
|
78
|
+
end
|
79
|
+
|
80
|
+
def attack monster
|
81
|
+
if monster.nil?
|
82
|
+
log.error "You attack the darkness."
|
83
|
+
else
|
84
|
+
log.info "You whack the #{monster} with a stick."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Opinions
|
91
|
+
--------
|
92
|
+
|
93
|
+
Notarius only has three logging levels: info, warning, and error. You
|
94
|
+
don't need more than that. If think you do, your logs are too verbose.
|
95
|
+
|
96
|
+
There is one line of output per message. If you insert carriage returns
|
97
|
+
and newlines in a message, they will be converted into spaces. The same
|
98
|
+
goes for tabs. Log statements shouldn't read like poetry. Exceptions are
|
99
|
+
the exceptional exception to this rule.
|
100
|
+
|
101
|
+
Messages are truncated so the total length of a line is less than 140
|
102
|
+
characters. This makes them easy to [tweet][]. Tweetablility is
|
103
|
+
important, because it's what the people reading logs at 3am do when they
|
104
|
+
find something hilarious. If you're writing non-tweetable messages, your
|
105
|
+
logs are too verbose.
|
106
|
+
|
107
|
+
To make it easy to grep for warnings and errors, messages are output as
|
108
|
+
"level [timestamp] message". You can do `cat notarius.log | grep ^WARN`
|
109
|
+
and find all the warnings in a log.
|
110
|
+
|
111
|
+
If the object you pass to the `log.info`, `log.warn`, and `log.error`
|
112
|
+
methods responds to `:message` and `:backtrace`, you'll get output like
|
113
|
+
this:
|
114
|
+
|
115
|
+
```bash
|
116
|
+
level [timestamp] message
|
117
|
+
! backtrace
|
118
|
+
! backtrace
|
119
|
+
! backtrace
|
120
|
+
```
|
121
|
+
|
122
|
+
This makes it easy to log exceptions. More importantly, it makes it easy
|
123
|
+
to find exceptions: `cat notarius.log | grep -B 1 "^\!"`
|
124
|
+
|
125
|
+
If you try to write the same message multiple times, only the first one
|
126
|
+
will show up. This is true even if you change the logging level, or the
|
127
|
+
timestamps of the messages are different. Multiple identical lines in
|
128
|
+
the log are a sign that your code is broken.
|
129
|
+
|
130
|
+
Inspiration
|
131
|
+
-----------
|
132
|
+
|
133
|
+
At lot of the Notarius philosophy was copied from [Logula][]. The
|
134
|
+
rest of it comes from several years watching sysadmins work, and
|
135
|
+
realizing that programmers often make their lives more difficult than we
|
136
|
+
intend.
|
137
|
+
|
138
|
+
Notarius tries to make things easier.
|
139
|
+
|
140
|
+
License
|
141
|
+
-------
|
142
|
+
|
143
|
+
Notarius is available under an [MIT-style][] license. See the
|
144
|
+
{file:LICENSE.md} document for more information.
|
145
|
+
|
146
|
+
[Logula]: https://github.com/codahale/logula "Logula is a Scala library which provides a sane log output format and easy-to-use mixin for adding logging to your code."
|
147
|
+
[MIT-style]: http://opensource.org/license/MIT "Open Source Initiative OSI - The MIT License"
|
148
|
+
[ISO 8601]: http://en.wikipedia.org/wiki/ISO_8601 "An international standard covering the exchange of date and time related data."
|
149
|
+
[tweet]: https://twitter.com/ "Twitter"
|
data/lib/notarius.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'notarius/secretary'
|
2
|
+
require 'notarius/config'
|
3
|
+
require 'notarius/exception'
|
4
|
+
require 'notarius/version'
|
5
|
+
|
6
|
+
module Notarius
|
7
|
+
@configs = {}
|
8
|
+
|
9
|
+
##
|
10
|
+
# Configure logging for the named module.
|
11
|
+
#
|
12
|
+
# @yieldparam log [Config]
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# Notarius.configure 'BIG' do |log|
|
16
|
+
# log.console = true
|
17
|
+
# log.file = '/var/log/notarius/big.log'
|
18
|
+
# end
|
19
|
+
|
20
|
+
def self.configure name, &block
|
21
|
+
name = namespace name
|
22
|
+
@configs[name] = Config.new if @configs[name].nil?
|
23
|
+
@configs[name].instance_eval(&block) if block_given?
|
24
|
+
return if self.const_defined? name
|
25
|
+
|
26
|
+
mod = Module.new do
|
27
|
+
define_method :log do
|
28
|
+
@secretary = Secretary.new if @secretary.nil?
|
29
|
+
@secretary.configure Notarius.config(name)
|
30
|
+
@secretary
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self.const_set name, mod
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Return a config with the given name. Validates the config first.
|
38
|
+
# @return [Config, nil] matching config or +nil+ if none found
|
39
|
+
# @see Notarius.validate
|
40
|
+
|
41
|
+
def self.config name
|
42
|
+
validate name
|
43
|
+
@configs[name]
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Validate a config with the given name.
|
48
|
+
# @param [String] name name of config to validate
|
49
|
+
# @raise [Exception] when file is already being logged to
|
50
|
+
|
51
|
+
def self.validate name
|
52
|
+
config = @configs[name]
|
53
|
+
if config
|
54
|
+
@configs.each do |n, c|
|
55
|
+
if n != name && c.file && c.file == config.file
|
56
|
+
message = <<EOF
|
57
|
+
Notarius::#{name} logs to the same file as Notarius::#{n}.
|
58
|
+
EOF
|
59
|
+
raise Notarius::Exception.new message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
private_class_method :validate
|
65
|
+
|
66
|
+
##
|
67
|
+
# Convert an +Object+ to a +String+ that can be used as a namespace.
|
68
|
+
# This has to generate something that matches Ruby's idea of a
|
69
|
+
# constant.
|
70
|
+
# @raise [Exception] when +name+ is empty
|
71
|
+
# @param [#to_s] name name of the namespace
|
72
|
+
# @return [String] converted namespace
|
73
|
+
|
74
|
+
def self.namespace name
|
75
|
+
name = name.to_s
|
76
|
+
if name.empty?
|
77
|
+
raise Notarius::Exception.new "namespaces can't be empty"
|
78
|
+
end
|
79
|
+
name[0, 1].upcase + name[1, name.size]
|
80
|
+
end
|
81
|
+
private_class_method :namespace
|
82
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Notarius
|
4
|
+
##
|
5
|
+
# Handles formatting of log messages. It's compatable with Ruby's
|
6
|
+
# +Logger::Formatter+ class, but has its own opinions:
|
7
|
+
#
|
8
|
+
# * Whitespace in the message is converted to spaces.
|
9
|
+
# * Output is truncated to 140 characters.
|
10
|
+
# * Timestamps are formatted as ISO 8601 in UTC.
|
11
|
+
# * Lines in call stacks are prefixed with !'s.
|
12
|
+
# * Any of the arguments to #call can be nil.
|
13
|
+
|
14
|
+
class Formatter
|
15
|
+
##
|
16
|
+
# This is the interface Ruby's +Logger+ class expects.
|
17
|
+
# @param [String] severity the severity level of the message
|
18
|
+
# @param [Date] timestamp the timestamp for the message
|
19
|
+
# @param [Object] application unused
|
20
|
+
# @param [String, #message, #backtrace, #inspect] message
|
21
|
+
# the message to log
|
22
|
+
# @return [String] formatted as "SEVERITY [timestamp] message\\n"
|
23
|
+
|
24
|
+
def call severity, timestamp, application, message
|
25
|
+
result = []
|
26
|
+
result << format_severity(severity) if severity
|
27
|
+
result << '[' + format_timestamp(timestamp) + ']' if timestamp
|
28
|
+
result << format_message(message) if message
|
29
|
+
make_tweetable(result.join(' ')) + "\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_severity severity
|
33
|
+
severity.strip.upcase
|
34
|
+
end
|
35
|
+
private :format_severity
|
36
|
+
|
37
|
+
def format_message message
|
38
|
+
result = []
|
39
|
+
if message.respond_to?(:message)
|
40
|
+
result << message.message
|
41
|
+
else
|
42
|
+
result << message
|
43
|
+
end
|
44
|
+
if message.respond_to?(:backtrace)
|
45
|
+
backtrace = [message.backtrace]
|
46
|
+
backtrace.flatten!
|
47
|
+
backtrace.compact!
|
48
|
+
result << backtrace.map { |line| "! %s" % clean_message(line) }
|
49
|
+
end
|
50
|
+
result.flatten!
|
51
|
+
result.map! { |line| clean_message(line) }
|
52
|
+
result.join("\n")
|
53
|
+
end
|
54
|
+
private :format_message
|
55
|
+
|
56
|
+
def clean_message message
|
57
|
+
message = message.inspect unless message.kind_of?(String)
|
58
|
+
message.gsub(/[\t\r\n\s]+/, ' ').strip
|
59
|
+
end
|
60
|
+
private :clean_message
|
61
|
+
|
62
|
+
def format_timestamp timestamp
|
63
|
+
timestamp.utc.iso8601
|
64
|
+
end
|
65
|
+
private :format_timestamp
|
66
|
+
|
67
|
+
def make_tweetable message
|
68
|
+
message.length > 140 ? message[0, 137] + '...' : message
|
69
|
+
end
|
70
|
+
private :make_tweetable
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'notarius/formatter'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Notarius
|
5
|
+
class Secretary
|
6
|
+
##
|
7
|
+
# Create a new instance of Secretary.
|
8
|
+
# @return [Secretary]
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@loggers = {}
|
12
|
+
@messages = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Configure a Secretary.
|
17
|
+
# @param [Config] config the configuration for this Secretary
|
18
|
+
|
19
|
+
def configure config
|
20
|
+
if config.console
|
21
|
+
add :console, logger(config.console, $stdout)
|
22
|
+
else
|
23
|
+
delete :console
|
24
|
+
end
|
25
|
+
if config.file
|
26
|
+
add :file, config.file
|
27
|
+
else
|
28
|
+
delete :file
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Log an informative message. Informative messages show up the log
|
34
|
+
# with "INFO" at the start of the line.
|
35
|
+
|
36
|
+
def info message
|
37
|
+
log Logger::INFO, message
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Log a warning message. Warning messages show up in the log with
|
42
|
+
# "WARN" at the start of the line.
|
43
|
+
|
44
|
+
def warn message
|
45
|
+
log Logger::WARN, message
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Log an error message. Error messages show up in the log with
|
50
|
+
# "ERROR" at the start of the line.
|
51
|
+
|
52
|
+
def error message
|
53
|
+
log Logger::ERROR, message
|
54
|
+
end
|
55
|
+
|
56
|
+
def log severity, message
|
57
|
+
@loggers.each do |key, logger|
|
58
|
+
if message != @messages[key]
|
59
|
+
@messages[key] = message
|
60
|
+
logger.add(severity) { message }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
private :log
|
65
|
+
|
66
|
+
def add key, stream
|
67
|
+
@loggers[key] = Logger.new stream
|
68
|
+
@loggers[key].formatter = Formatter.new
|
69
|
+
end
|
70
|
+
private :add
|
71
|
+
|
72
|
+
def delete key
|
73
|
+
logger = @loggers.delete(key)
|
74
|
+
logger.close rescue nil
|
75
|
+
end
|
76
|
+
private :delete
|
77
|
+
|
78
|
+
def logger *args
|
79
|
+
args.find { |arg| loggable?(arg) }
|
80
|
+
end
|
81
|
+
private :logger
|
82
|
+
|
83
|
+
def loggable? stream
|
84
|
+
stream.respond_to?(:write) && stream.respond_to?(:close)
|
85
|
+
end
|
86
|
+
private :loggable?
|
87
|
+
end
|
88
|
+
end
|
data/notarius.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
|
3
|
+
require 'notarius/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'notarius'
|
7
|
+
s.version = Notarius::VERSION
|
8
|
+
s.required_ruby_version = '~> 1.9'
|
9
|
+
|
10
|
+
s.summary = 'Notarius is a logging library with opinions.'
|
11
|
+
s.description = <<EOF
|
12
|
+
Notarius is a logging library with opinions. The word "notarius" is
|
13
|
+
Latin for "shorthand writer". To this end, Notarius does everything
|
14
|
+
possible to encourage you to write short useful log messages.
|
15
|
+
EOF
|
16
|
+
|
17
|
+
s.author = 'Frank Mitchell'
|
18
|
+
s.email = 'me@frankmitchell.org'
|
19
|
+
s.homepage = 'https://github.com/elimossinary/notarius'
|
20
|
+
s.license = 'MIT'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake', '~> 0.9.2'
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.10'
|
24
|
+
s.add_development_dependency 'simplecov', '~> 0.6'
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.files.reject! { |file| file =~ /^\./ }
|
28
|
+
|
29
|
+
s.test_files = `git ls-files -- spec/*_spec.rb`.split("\n")
|
30
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
CLEAN.include 'doc'
|
5
|
+
CLEAN.include 'coverage'
|
6
|
+
CLEAN.include '*.gem'
|
7
|
+
CLEAN.include '.yardoc'
|
8
|
+
|
9
|
+
desc 'Run specs.'
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
desc 'Run specs.'
|
13
|
+
RSpec::Core::RakeTask.new :spec
|
14
|
+
|
15
|
+
desc 'Generate SimpleCov spec coverage.'
|
16
|
+
RSpec::Core::RakeTask.new :coverage do |t|
|
17
|
+
t.rspec_opts = ['--require simplecov_helper']
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Build the gem.'
|
21
|
+
task :build do
|
22
|
+
sh "gem build #{name}.gemspec"
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Install the gem'
|
26
|
+
task :install do
|
27
|
+
sh "gem install ./#{name}-#{version}.gem"
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Uninstall the gem'
|
31
|
+
task :uninstall do
|
32
|
+
sh "gem uninstall #{name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
37
|
+
end
|
38
|
+
|
39
|
+
def version
|
40
|
+
text = File.read("lib/#{name}/version.rb")
|
41
|
+
text = text[/^\s*VERSION\s*=\s*.*/]
|
42
|
+
@version ||= text.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
43
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'notarius/config'
|
2
|
+
|
3
|
+
describe Notarius::Config do
|
4
|
+
let(:config) { Notarius::Config.new }
|
5
|
+
|
6
|
+
it 'defaults to no console' do
|
7
|
+
config.console.should be_false
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'defaults to no file' do
|
11
|
+
config.file.should be_false
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'lets you configure a console' do
|
15
|
+
config.should respond_to(:console=)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'lets you configure a file' do
|
19
|
+
config.should respond_to(:file=)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'notarius/formatter'
|
2
|
+
|
3
|
+
describe Notarius::Formatter do
|
4
|
+
describe 'call' do
|
5
|
+
let(:formatter) { Notarius::Formatter.new }
|
6
|
+
|
7
|
+
it 'converts whitespace to spaces' do
|
8
|
+
message = "\sMessage\r\nwith\twhitespace "
|
9
|
+
message = formatter.call(nil, nil, nil, message)
|
10
|
+
message.should == "Message with whitespace\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'formats timestamps as ISO 8601' do
|
14
|
+
timestamp = Time.parse('2012-06-25 20:41:30 -0400')
|
15
|
+
message = formatter.call(nil, timestamp, nil, nil)
|
16
|
+
message.should == "[2012-06-26T00:41:30Z]\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'makes severity all upper case' do
|
20
|
+
message = formatter.call('info', nil, nil, nil)
|
21
|
+
message.should == "INFO\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'removes extra whitespace from severity' do
|
25
|
+
message = formatter.call("\swarn\s", nil, nil, nil)
|
26
|
+
message.should == "WARN\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'ignores program name field' do
|
30
|
+
message = formatter.call(nil, nil, 'noodles', nil)
|
31
|
+
message.should == "\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'makes messages tweetable' do
|
35
|
+
message = <<-EOF
|
36
|
+
This is a really, really long message that needs to
|
37
|
+
be more than 140 characters so that Notarius can trim it
|
38
|
+
down to something more reasonable in length.
|
39
|
+
EOF
|
40
|
+
message.length.should > 140
|
41
|
+
message = formatter.call(nil, nil, nil, message)
|
42
|
+
message.strip.length.should == 140
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'formats messages as "LEVEL [timestamp] message\n"' do
|
46
|
+
timestamp = Time.parse('2012-06-25 21:17:44 -0400')
|
47
|
+
message = formatter.call('level', timestamp, nil, 'message')
|
48
|
+
message.should == "LEVEL [2012-06-26T01:17:44Z] message\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'appends newlines to messages' do
|
52
|
+
message = formatter.call(nil, nil, nil, 'message')
|
53
|
+
message.should == "message\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'formats exceptions nicely' do
|
57
|
+
exception = Exception.new('message')
|
58
|
+
exception.set_backtrace ['trace this line', 'back to here']
|
59
|
+
lines = formatter.call(nil, nil, nil, exception).split("\n")
|
60
|
+
lines[0].should == 'message'
|
61
|
+
lines[1].should == '! trace this line'
|
62
|
+
lines[2].should == '! back to here'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'formats objects nicely' do
|
66
|
+
object = Class.new do
|
67
|
+
def inspect
|
68
|
+
'details'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
message = formatter.call(nil, nil, nil, object.new)
|
72
|
+
message.should == "details\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'formats exceptions without backtraces nicely' do
|
76
|
+
exception = Exception.new('message')
|
77
|
+
message = formatter.call(nil, nil, nil, exception)
|
78
|
+
message.should == "message\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'formats objects that look like exceptions nicely' do
|
82
|
+
exception = Class.new do
|
83
|
+
def message
|
84
|
+
message = Class.new do
|
85
|
+
def inspect
|
86
|
+
'message'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
message.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def backtrace
|
93
|
+
backtrace = Class.new do
|
94
|
+
def inspect
|
95
|
+
'backtrace'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
backtrace.new
|
99
|
+
end
|
100
|
+
end
|
101
|
+
lines = formatter.call(nil, nil, nil, exception.new).split("\n")
|
102
|
+
lines[0].should == 'message'
|
103
|
+
lines[1].should == '! backtrace'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'notarius'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Notarius do
|
5
|
+
def tempfiles *prefixes
|
6
|
+
files = prefixes.map { |prefix| Tempfile.new prefix }
|
7
|
+
begin
|
8
|
+
paths = files.map { |file| file.path }
|
9
|
+
paths = paths.first if paths.size == 1
|
10
|
+
yield paths if block_given?
|
11
|
+
ensure
|
12
|
+
files.each { |file| file.close! }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a semantic version number' do
|
17
|
+
Notarius::VERSION.should match(/^\d+\.\d+\.\d+$/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can log to a file' do
|
21
|
+
tempfiles 'player' do |player_log|
|
22
|
+
Notarius.configure('BIG') { |l| l.file = player_log }
|
23
|
+
player = Class.new do
|
24
|
+
include Notarius::BIG
|
25
|
+
def initialize
|
26
|
+
log.info 'New player created!'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
player.new
|
30
|
+
lines = File.read(player_log).split("\n")
|
31
|
+
lines[0].should end_with('New player created!')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows namespaces to be overwritten' do
|
36
|
+
tempfiles('player', 'monster') do |player_log, monster_log|
|
37
|
+
Notarius.configure('BIG') { |l| l.file = player_log }
|
38
|
+
|
39
|
+
player = Class.new do
|
40
|
+
include Notarius::BIG
|
41
|
+
def run message
|
42
|
+
log.info message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
p = player.new
|
46
|
+
|
47
|
+
p.run 'Player is running.'
|
48
|
+
lines = File.read(player_log).split("\n")
|
49
|
+
lines[0].should end_with('Player is running.')
|
50
|
+
|
51
|
+
Notarius.configure('BIG') { |l| l.file = monster_log }
|
52
|
+
p.run 'Player is still running.'
|
53
|
+
lines = File.read(monster_log).split("\n")
|
54
|
+
lines[0].should end_with('Player is still running.')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'allows for unique namespaces' do
|
59
|
+
tempfiles('player', 'monster') do |player_log, monster_log|
|
60
|
+
Notarius.configure('Player') { |l| l.file = player_log }
|
61
|
+
player = Class.new do
|
62
|
+
include Notarius::Player
|
63
|
+
def initialize
|
64
|
+
log.info 'New player created!'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Notarius.configure('Monster') { |l| l.file = monster_log }
|
69
|
+
monster = Class.new do
|
70
|
+
include Notarius::Monster
|
71
|
+
def initialize
|
72
|
+
log.info 'New monster created!'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
monster.new
|
77
|
+
player.new
|
78
|
+
File.read(monster_log).should_not include('New player created!')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'throws an error if separate namespaces log to the same file' do
|
83
|
+
tempfiles 'log' do |log|
|
84
|
+
Notarius.configure('Player') { |l| l.file = log }
|
85
|
+
Notarius.configure('Monster') { |l| l.file = log }
|
86
|
+
|
87
|
+
monster = Class.new do
|
88
|
+
include Notarius::Monster
|
89
|
+
def initialize
|
90
|
+
log.info 'New monster created!'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
expect { monster.new }.to raise_error(Notarius::Exception)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#configure' do
|
99
|
+
it 'handles lowercase namespaces gracefully' do
|
100
|
+
Notarius.configure 'little'
|
101
|
+
Notarius.const_defined?(:Little).should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'throws an error if a namespace is empty' do
|
105
|
+
expect do
|
106
|
+
Notarius.configure ''
|
107
|
+
end.to raise_error(Notarius::Exception)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'throws an error if a namespace is nil' do
|
111
|
+
expect do
|
112
|
+
Notarius.configure nil
|
113
|
+
end.to raise_error(Notarius::Exception)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'notarius/secretary'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Notarius::Secretary do
|
5
|
+
describe 'logging' do
|
6
|
+
let(:logger) { StringIO.new }
|
7
|
+
let(:secretary) do
|
8
|
+
c = Notarius::Config.new
|
9
|
+
c.file = logger
|
10
|
+
s = Notarius::Secretary.new
|
11
|
+
s.configure c
|
12
|
+
s
|
13
|
+
end
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
logger.truncate 0
|
17
|
+
logger.rewind
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can log info messages' do
|
21
|
+
secretary.info 'info message'
|
22
|
+
logger.string.should match(/^INFO \[[^\]]+\] info message\n$/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can log warning messages' do
|
26
|
+
secretary.warn 'warning message'
|
27
|
+
logger.string.should match(/^WARN \[[^\]]+\] warning message\n$/)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can log error messages' do
|
31
|
+
secretary.error 'error message'
|
32
|
+
logger.string.should match(/^ERROR \[[^\]]+\] error message\n$/)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#configure' do
|
37
|
+
it 'logs to multiple outputs' do
|
38
|
+
io1 = StringIO.new
|
39
|
+
io2 = StringIO.new
|
40
|
+
|
41
|
+
secretary = Notarius::Secretary.new
|
42
|
+
|
43
|
+
config = Notarius::Config.new
|
44
|
+
config.console = io1
|
45
|
+
config.file = io2
|
46
|
+
secretary.configure config
|
47
|
+
|
48
|
+
secretary.info 'info message'
|
49
|
+
io1.string.should match(/^INFO \[[^\]]+\] info message\n$/)
|
50
|
+
io2.string.should match(/^INFO \[[^\]]+\] info message\n$/)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'defaults console to stdout' do
|
54
|
+
config = Notarius::Config.new
|
55
|
+
config.console = true
|
56
|
+
secretary = Notarius::Secretary.new
|
57
|
+
|
58
|
+
output = StringIO.new
|
59
|
+
stdout = $stdout
|
60
|
+
begin
|
61
|
+
$stdout = output
|
62
|
+
secretary.configure config
|
63
|
+
secretary.info 'message'
|
64
|
+
ensure
|
65
|
+
$stdout = stdout
|
66
|
+
end
|
67
|
+
|
68
|
+
output.string.should match(/^INFO \[[^\]]+\] message\n$/)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'removes a logger when no longer referenced' do
|
72
|
+
io1 = StringIO.new
|
73
|
+
io2 = StringIO.new
|
74
|
+
|
75
|
+
config1 = Notarius::Config.new
|
76
|
+
config1.console = io1
|
77
|
+
config1.file = io2
|
78
|
+
|
79
|
+
secretary = Notarius::Secretary.new
|
80
|
+
secretary.configure config1
|
81
|
+
secretary.info 'noodles'
|
82
|
+
io1.string.should match(/^INFO \[[^\]]+\] noodles\n$/)
|
83
|
+
io2.string.should match(/^INFO \[[^\]]+\] noodles\n$/)
|
84
|
+
|
85
|
+
secretary.configure Notarius::Config.new
|
86
|
+
secretary.info 'pasta'
|
87
|
+
io1.string.should_not match(/^INFO \[[^\]]+\] pasta\n$/)
|
88
|
+
io2.string.should_not match(/^INFO \[[^\]]+\] pasta\n$/)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'closes a logger when it removes it' do
|
92
|
+
config = Notarius::Config.new
|
93
|
+
config.console = StringIO.new
|
94
|
+
|
95
|
+
secretary = Notarius::Secretary.new
|
96
|
+
secretary.configure config
|
97
|
+
secretary.info 'noodles'
|
98
|
+
config.console.string.should match(/^INFO \[[^\]]+\] noodles\n$/)
|
99
|
+
|
100
|
+
secretary.configure Notarius::Config.new
|
101
|
+
secretary.info 'pasta'
|
102
|
+
config.console.should be_closed
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'skips duplicate messages per logger' do
|
107
|
+
io1 = StringIO.new
|
108
|
+
io2 = StringIO.new
|
109
|
+
|
110
|
+
secretary = Notarius::Secretary.new
|
111
|
+
|
112
|
+
config1 = Notarius::Config.new
|
113
|
+
config1.console = io1
|
114
|
+
secretary.configure config1
|
115
|
+
|
116
|
+
secretary.error 'same message'
|
117
|
+
|
118
|
+
config2 = Notarius::Config.new
|
119
|
+
config2.console = io1
|
120
|
+
config2.file = io2
|
121
|
+
secretary.configure config2
|
122
|
+
|
123
|
+
secretary.info 'same message'
|
124
|
+
|
125
|
+
io1.string.should_not match(/^INFO \[[^\]]+\] same message\n$/)
|
126
|
+
io2.string.should match(/^INFO \[[^\]]+\] same message\n$/)
|
127
|
+
end
|
128
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: notarius
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Frank Mitchell
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.9.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.10'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.10'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: simplecov
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.6'
|
62
|
+
description: ! 'Notarius is a logging library with opinions. The word "notarius" is
|
63
|
+
|
64
|
+
Latin for "shorthand writer". To this end, Notarius does everything
|
65
|
+
|
66
|
+
possible to encourage you to write short useful log messages.
|
67
|
+
|
68
|
+
'
|
69
|
+
email: me@frankmitchell.org
|
70
|
+
executables: []
|
71
|
+
extensions: []
|
72
|
+
extra_rdoc_files: []
|
73
|
+
files:
|
74
|
+
- LICENSE.md
|
75
|
+
- README.md
|
76
|
+
- lib/notarius.rb
|
77
|
+
- lib/notarius/config.rb
|
78
|
+
- lib/notarius/exception.rb
|
79
|
+
- lib/notarius/formatter.rb
|
80
|
+
- lib/notarius/secretary.rb
|
81
|
+
- lib/notarius/version.rb
|
82
|
+
- notarius.gemspec
|
83
|
+
- rakefile.rb
|
84
|
+
- spec/config_spec.rb
|
85
|
+
- spec/formatter_spec.rb
|
86
|
+
- spec/notarius_spec.rb
|
87
|
+
- spec/secretary_spec.rb
|
88
|
+
- spec/simplecov_helper.rb
|
89
|
+
homepage: https://github.com/elimossinary/notarius
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.9'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.8.24
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: Notarius is a logging library with opinions.
|
114
|
+
test_files:
|
115
|
+
- spec/config_spec.rb
|
116
|
+
- spec/formatter_spec.rb
|
117
|
+
- spec/notarius_spec.rb
|
118
|
+
- spec/secretary_spec.rb
|
119
|
+
has_rdoc:
|