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