dlogger 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +19 -0
- data/Guardfile +15 -0
- data/LICENSE +22 -0
- data/README.md +182 -0
- data/README.md.erb +79 -0
- data/Rakefile +25 -0
- data/dlogger.gemspec +21 -0
- data/examples/basic.rb +11 -0
- data/examples/dynamic_context.rb +26 -0
- data/examples/hash_context.rb +16 -0
- data/examples/logstash.rb +22 -0
- data/examples/nested_context.rb +27 -0
- data/examples/threads.rb +29 -0
- data/lib/dlogger.rb +8 -0
- data/lib/dlogger/extension.rb +24 -0
- data/lib/dlogger/logger.rb +134 -0
- data/lib/dlogger/outputs/base.rb +7 -0
- data/lib/dlogger/outputs/em/logstash.rb +36 -0
- data/lib/dlogger/outputs/stdlib_logger.rb +27 -0
- data/lib/dlogger/outputs/stdout.rb +33 -0
- data/lib/dlogger/version.rb +3 -0
- data/specs/common.rb +52 -0
- data/specs/lib/extension_spec.rb +28 -0
- data/specs/lib/logger_spec.rb +76 -0
- data/specs/lib/outputs/stdlib_logger_spec.rb +25 -0
- data/specs/lib/outputs/stdout_spec.rb +17 -0
- metadata +111 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in dlogger.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'eventmachine', '~> 1.0.0.beta'
|
7
|
+
gem 'yajl-ruby'
|
8
|
+
|
9
|
+
group(:test) do
|
10
|
+
gem 'schmurfy-bacon', '~> 1.2.1'
|
11
|
+
gem 'mocha', '~> 0.10.0'
|
12
|
+
gem 'simplecov'
|
13
|
+
gem 'schmurfy-em-spec'
|
14
|
+
gem 'guard'
|
15
|
+
gem 'guard-bacon'
|
16
|
+
gem 'rb-fsevent'
|
17
|
+
gem 'growl'
|
18
|
+
end
|
19
|
+
|
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
# parameters:
|
5
|
+
# output => the formatted to use
|
6
|
+
# backtrace => number of lines, nil = everything
|
7
|
+
guard 'bacon', :output => "BetterOutput", :backtrace => 4 do
|
8
|
+
watch(%r{^lib/dlogger/(.+)\.rb$}) { |m| "specs/lib/#{m[1]}_spec.rb" }
|
9
|
+
watch(%r{specs/.+_spec\.rb$})
|
10
|
+
end
|
11
|
+
|
12
|
+
guard 'tilt' do
|
13
|
+
watch(%r{README.md.erb})
|
14
|
+
watch(%r{examples/.*\.rb}){ "README.md.erb" }
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Julien Ammous
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
# What is this ?
|
2
|
+
|
3
|
+
I was never happy with printf style logs, you rarely know at first what you need to put in them and how to format
|
4
|
+
them so you just drop a ruby logger and log anything. Your log messages quickly become a huge mess unless you
|
5
|
+
revise all of them when the project matures and this is a real pain...
|
6
|
+
|
7
|
+
Since I discovered logstash I started to think about what I would expect from an intelligent ruby logger allowing
|
8
|
+
me to embed the actual context of the log message WITH the message and not inside it so the informations can
|
9
|
+
easily be extracted without stupid operations like regexp matching.
|
10
|
+
|
11
|
+
I first found radar which does something similar but aimed at exception catching then I recently found ruby-cabin,
|
12
|
+
after some considerations I decided to try rolling out my own, I opened a text file and in 10min I had a basic
|
13
|
+
prototype and here we are !
|
14
|
+
|
15
|
+
# Simple example
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'rubygems'
|
19
|
+
require 'bundler/setup'
|
20
|
+
|
21
|
+
require 'dlogger'
|
22
|
+
|
23
|
+
logger = DLogger::Logger.new
|
24
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
25
|
+
|
26
|
+
logger.log("yeah it worked")
|
27
|
+
|
28
|
+
# => yeah it worked : {}
|
29
|
+
```
|
30
|
+
|
31
|
+
|
32
|
+
# Features
|
33
|
+
|
34
|
+
## Contexts
|
35
|
+
|
36
|
+
You can define hash keys and their values, they will be included in any log
|
37
|
+
sent within the block.
|
38
|
+
|
39
|
+
``` ruby
|
40
|
+
require 'rubygems'
|
41
|
+
require 'bundler/setup'
|
42
|
+
|
43
|
+
require 'dlogger'
|
44
|
+
|
45
|
+
logger = DLogger::Logger.new
|
46
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
47
|
+
|
48
|
+
logger.with_context(:user_id => 32) do
|
49
|
+
logger.log("My context is with me")
|
50
|
+
end
|
51
|
+
|
52
|
+
logger.log("but not here")
|
53
|
+
|
54
|
+
# => My context is with me : {:user_id=>32}
|
55
|
+
# => but not here : {}
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
## Nested contexts
|
60
|
+
|
61
|
+
You can use nested contexts in this case the inner contexts have a higher priority over
|
62
|
+
the ones above if they define the same keys.
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
require 'rubygems'
|
66
|
+
require 'bundler/setup'
|
67
|
+
|
68
|
+
require 'dlogger'
|
69
|
+
|
70
|
+
logger = DLogger::Logger.new
|
71
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
72
|
+
|
73
|
+
class TimeExtension < DLogger::Extension
|
74
|
+
def time
|
75
|
+
Time.now
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
logger.with_context(TimeExtension) do
|
80
|
+
logger.with_context(:user_id => 32) do
|
81
|
+
logger.log("My full context is with me")
|
82
|
+
end
|
83
|
+
|
84
|
+
logger.log("I only get the time here")
|
85
|
+
end
|
86
|
+
|
87
|
+
logger.log("but still nothing here")
|
88
|
+
|
89
|
+
# => My full context is with me : {:time=>2012-01-27 11:46:49 +0100, :user_id=>32}
|
90
|
+
# => I only get the time here : {:time=>2012-01-27 11:46:49 +0100}
|
91
|
+
# => but still nothing here : {}
|
92
|
+
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
## Dymanic context
|
97
|
+
|
98
|
+
In some cases it may be more handy to use a class rather than a static hash, for
|
99
|
+
example if use the thread local variables to store the user, session or anything.
|
100
|
+
|
101
|
+
``` ruby
|
102
|
+
require 'rubygems'
|
103
|
+
require 'bundler/setup'
|
104
|
+
|
105
|
+
require 'dlogger'
|
106
|
+
|
107
|
+
logger = DLogger::Logger.new
|
108
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
109
|
+
|
110
|
+
# any methods defined in your extension class will become
|
111
|
+
# a key in the resulting hash (except initialize).
|
112
|
+
class UserExtension < DLogger::Extension
|
113
|
+
def user_id
|
114
|
+
rand(43)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
logger.with_context(UserExtension) do
|
119
|
+
logger.log("My context is with me")
|
120
|
+
logger.log("And will change")
|
121
|
+
end
|
122
|
+
|
123
|
+
logger.log("but not here")
|
124
|
+
|
125
|
+
# => My context is with me : {:user_id=>39}
|
126
|
+
# => And will change : {:user_id=>21}
|
127
|
+
# => but not here : {}
|
128
|
+
|
129
|
+
```
|
130
|
+
|
131
|
+
## Threads and Fibers friendly
|
132
|
+
|
133
|
+
Whether you use threads or fibers for concurrency you are good to go, the contexts are
|
134
|
+
store with Thread.current which is local to the current fiber (thread's root fiber if not using
|
135
|
+
Fiber explicitly).
|
136
|
+
|
137
|
+
``` ruby
|
138
|
+
require 'rubygems'
|
139
|
+
require 'bundler/setup'
|
140
|
+
|
141
|
+
require 'dlogger'
|
142
|
+
|
143
|
+
logger = DLogger::Logger.new
|
144
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
145
|
+
|
146
|
+
th1 = Thread.new do
|
147
|
+
logger.with_context(:user_id => "alice", :thread_id => 1) do
|
148
|
+
logger.log("I did it !")
|
149
|
+
sleep 0.2
|
150
|
+
logger.log("completed")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
th2 = Thread.new do
|
155
|
+
logger.with_context(:user_id => "bob", :thread_id => 2) do
|
156
|
+
sleep 0.1
|
157
|
+
logger.log("Done !")
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
[th1, th2].each(&:join)
|
163
|
+
|
164
|
+
# => I did it ! : {:user_id=>"alice", :thread_id=>1}
|
165
|
+
# => Done ! : {:user_id=>"bob", :thread_id=>2}
|
166
|
+
# => completed : {:user_id=>"alice", :thread_id=>1}
|
167
|
+
|
168
|
+
```
|
169
|
+
|
170
|
+
|
171
|
+
# Want to contribute ?
|
172
|
+
|
173
|
+
Fork and clone the repository then:
|
174
|
+
|
175
|
+
``` bash
|
176
|
+
$ bundle
|
177
|
+
$ bundle exec guard
|
178
|
+
```
|
179
|
+
|
180
|
+
After this any change to a file will automatically run the associated spec file, any change
|
181
|
+
to the README.md.erb or any of the examples will trigger a rebuild of the README.md file.
|
182
|
+
|
data/README.md.erb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# What is this ?
|
2
|
+
|
3
|
+
I was never happy with printf style logs, you rarely know at first what you need to put in them and how to format
|
4
|
+
them so you just drop a ruby logger and log anything. Your log messages quickly become a huge mess unless you
|
5
|
+
revise all of them when the project matures and this is a real pain...
|
6
|
+
|
7
|
+
Since I discovered logstash I started to think about what I would expect from an intelligent ruby logger allowing
|
8
|
+
me to embed the actual context of the log message WITH the message and not inside it so the informations can
|
9
|
+
easily be extracted without stupid operations like regexp matching.
|
10
|
+
|
11
|
+
I first found radar which does something similar but aimed at exception catching then I recently found ruby-cabin,
|
12
|
+
after some considerations I decided to try rolling out my own, I opened a text file and in 10min I had a basic
|
13
|
+
prototype and here we are !
|
14
|
+
|
15
|
+
# Simple example
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
<%= File.read('examples/basic.rb') %>
|
19
|
+
|
20
|
+
```
|
21
|
+
|
22
|
+
|
23
|
+
# Features
|
24
|
+
|
25
|
+
## Contexts
|
26
|
+
|
27
|
+
You can define hash keys and their values, they will be included in any log
|
28
|
+
sent within the block.
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
<%= File.read('examples/hash_context.rb') %>
|
32
|
+
|
33
|
+
```
|
34
|
+
|
35
|
+
## Nested contexts
|
36
|
+
|
37
|
+
You can use nested contexts in this case the inner contexts have a higher priority over
|
38
|
+
the ones above if they define the same keys.
|
39
|
+
|
40
|
+
``` ruby
|
41
|
+
<%= File.read('examples/nested_context.rb') %>
|
42
|
+
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
## Dymanic context
|
47
|
+
|
48
|
+
In some cases it may be more handy to use a class rather than a static hash, for
|
49
|
+
example if use the thread local variables to store the user, session or anything.
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
<%= File.read('examples/dynamic_context.rb') %>
|
53
|
+
|
54
|
+
```
|
55
|
+
|
56
|
+
## Threads and Fibers friendly
|
57
|
+
|
58
|
+
Whether you use threads or fibers for concurrency you are good to go, the contexts are
|
59
|
+
store with Thread.current which is local to the current fiber (thread's root fiber if not using
|
60
|
+
Fiber explicitly).
|
61
|
+
|
62
|
+
``` ruby
|
63
|
+
<%= File.read('examples/threads.rb') %>
|
64
|
+
|
65
|
+
```
|
66
|
+
|
67
|
+
|
68
|
+
# Want to contribute ?
|
69
|
+
|
70
|
+
Fork and clone the repository then:
|
71
|
+
|
72
|
+
``` bash
|
73
|
+
$ bundle
|
74
|
+
$ bundle exec guard
|
75
|
+
```
|
76
|
+
|
77
|
+
After this any change to a file will automatically run the associated spec file, any change
|
78
|
+
to the README.md.erb or any of the examples will trigger a rebuild of the README.md file.
|
79
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
task :readme do
|
7
|
+
require 'tilt'
|
8
|
+
require 'erb'
|
9
|
+
|
10
|
+
Dir.chdir( File.dirname(__FILE__) ) do
|
11
|
+
template = Tilt.new('README.md.erb')
|
12
|
+
File.write('README.md', template.render)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
task :test do
|
18
|
+
require 'bacon'
|
19
|
+
ENV['COVERAGE'] = "1"
|
20
|
+
Dir[File.expand_path('../specs/**/*_spec.rb', __FILE__)].each do |file|
|
21
|
+
load(file)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
data/dlogger.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/dlogger/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Julien Ammous"]
|
6
|
+
gem.email = ["schmurfy@gmail.com"]
|
7
|
+
gem.description = %q{Advanced logger allowing you to include metadata with your messages}
|
8
|
+
gem.summary = %q{Dynamic logger: add a context to your log messages}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "dlogger"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Dlogger::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency('rake')
|
19
|
+
gem.add_development_dependency('tilt')
|
20
|
+
gem.add_development_dependency('guard-tilt')
|
21
|
+
end
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'dlogger'
|
5
|
+
|
6
|
+
logger = DLogger::Logger.new
|
7
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
8
|
+
|
9
|
+
# any methods defined in your extension class will become
|
10
|
+
# a key in the resulting hash (except initialize).
|
11
|
+
class UserExtension < DLogger::Extension
|
12
|
+
def user_id
|
13
|
+
rand(43)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
logger.with_context(UserExtension) do
|
18
|
+
logger.log("My context is with me")
|
19
|
+
logger.log("And will change")
|
20
|
+
end
|
21
|
+
|
22
|
+
logger.log("but not here")
|
23
|
+
|
24
|
+
# => My context is with me : {:user_id=>39}
|
25
|
+
# => And will change : {:user_id=>21}
|
26
|
+
# => but not here : {}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'dlogger'
|
5
|
+
|
6
|
+
logger = DLogger::Logger.new
|
7
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
8
|
+
|
9
|
+
logger.with_context(:user_id => 32) do
|
10
|
+
logger.log("My context is with me")
|
11
|
+
end
|
12
|
+
|
13
|
+
logger.log("but not here")
|
14
|
+
|
15
|
+
# => My context is with me : {:user_id=>32}
|
16
|
+
# => but not here : {}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'yajl'
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
|
7
|
+
require 'dlogger'
|
8
|
+
require 'dlogger/outputs/em/logstash'
|
9
|
+
|
10
|
+
logger = DLogger::Logger.new
|
11
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
12
|
+
|
13
|
+
EM::run do
|
14
|
+
|
15
|
+
logger.add_output( DLogger::Output::LogStash.new('127.0.0.1', 10000) )
|
16
|
+
|
17
|
+
n = 0
|
18
|
+
EM::add_periodic_timer(2) do
|
19
|
+
logger.log("yeah it worked", :n => n+= 1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'dlogger'
|
5
|
+
|
6
|
+
logger = DLogger::Logger.new
|
7
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
8
|
+
|
9
|
+
class TimeExtension < DLogger::Extension
|
10
|
+
def time
|
11
|
+
Time.now
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
logger.with_context(TimeExtension) do
|
16
|
+
logger.with_context(:user_id => 32) do
|
17
|
+
logger.log("My full context is with me")
|
18
|
+
end
|
19
|
+
|
20
|
+
logger.log("I only get the time here")
|
21
|
+
end
|
22
|
+
|
23
|
+
logger.log("but still nothing here")
|
24
|
+
|
25
|
+
# => My full context is with me : {:time=>2012-01-27 11:46:49 +0100, :user_id=>32}
|
26
|
+
# => I only get the time here : {:time=>2012-01-27 11:46:49 +0100}
|
27
|
+
# => but still nothing here : {}
|
data/examples/threads.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'dlogger'
|
5
|
+
|
6
|
+
logger = DLogger::Logger.new
|
7
|
+
logger.add_output( DLogger::Output::Stdout.new )
|
8
|
+
|
9
|
+
th1 = Thread.new do
|
10
|
+
logger.with_context(:user_id => "alice", :thread_id => 1) do
|
11
|
+
logger.log("I did it !")
|
12
|
+
sleep 0.2
|
13
|
+
logger.log("completed")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
th2 = Thread.new do
|
18
|
+
logger.with_context(:user_id => "bob", :thread_id => 2) do
|
19
|
+
sleep 0.1
|
20
|
+
logger.log("Done !")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
[th1, th2].each(&:join)
|
26
|
+
|
27
|
+
# => I did it ! : {:user_id=>"alice", :thread_id=>1}
|
28
|
+
# => Done ! : {:user_id=>"bob", :thread_id=>2}
|
29
|
+
# => completed : {:user_id=>"alice", :thread_id=>1}
|
data/lib/dlogger.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module DLogger
|
2
|
+
|
3
|
+
class Extension
|
4
|
+
##
|
5
|
+
# Class attribute, each child class
|
6
|
+
# with have its own.
|
7
|
+
def self.properties
|
8
|
+
@properties ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Called by ruby each time a new method is added
|
13
|
+
# to this class or any of its children
|
14
|
+
#
|
15
|
+
# @param [Symbol] m method name
|
16
|
+
#
|
17
|
+
def self.method_added(m)
|
18
|
+
unless m == :initialize
|
19
|
+
self.properties << m
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module DLogger
|
4
|
+
class Logger
|
5
|
+
def initialize(name = "_default")
|
6
|
+
@name = name
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@outputs = []
|
9
|
+
|
10
|
+
# initialize context
|
11
|
+
context
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Main entry point, log a message with
|
16
|
+
# its metadata.
|
17
|
+
#
|
18
|
+
# @param [String] msg the message
|
19
|
+
# @param [Hash] metadata Additional data
|
20
|
+
#
|
21
|
+
def log(msg, metadata = {})
|
22
|
+
@mutex.synchronize do
|
23
|
+
# clearing a small hash is slightly faster than creating a new
|
24
|
+
# one each time.
|
25
|
+
merged_metadata.clear
|
26
|
+
|
27
|
+
# first load context data
|
28
|
+
context.each do |ctx_data|
|
29
|
+
case ctx_data
|
30
|
+
when Hash
|
31
|
+
merged_metadata.merge!(ctx_data)
|
32
|
+
|
33
|
+
when Extension
|
34
|
+
ctx_data.class.properties.each do |attr_name|
|
35
|
+
merged_metadata[attr_name] = ctx_data.send(attr_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# then add our data
|
42
|
+
merged_metadata.merge!(metadata)
|
43
|
+
|
44
|
+
# and dispatch the result
|
45
|
+
dispatch(msg, merged_metadata)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Helper methods to mimic the standard ruby logger interface.
|
50
|
+
%w(debug info error warn).each do |level|
|
51
|
+
define_method(level) do |msg, metadata = {}|
|
52
|
+
log(msg, metadata.merge(:severity => level.to_sym))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Register a new output, the only requirement is that
|
58
|
+
# the object passed reponds to the "dispatch" method.
|
59
|
+
#
|
60
|
+
# @param [Object] handler the handler
|
61
|
+
#
|
62
|
+
def add_output(handler)
|
63
|
+
@outputs << handler
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Usually called by #with_context method but you can use
|
68
|
+
# it directly to push a context on the stack, it may be useful
|
69
|
+
# for asynchronous handling or just to push a global context.
|
70
|
+
#
|
71
|
+
# @param [Hash, Dlogger::Extension] context_data additional informations
|
72
|
+
# to include in logs
|
73
|
+
#
|
74
|
+
def push_context(context_data)
|
75
|
+
if context_data.is_a?(Class)
|
76
|
+
context << context_data.new
|
77
|
+
else
|
78
|
+
context << context_data
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# The exact opposite of #push_context, if you called it by hand
|
84
|
+
# you can remove the context for the stack by calling this method.
|
85
|
+
def pop_context
|
86
|
+
context.pop
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# add context data for any log sent within the given block.
|
91
|
+
#
|
92
|
+
# @param [Hash, Dlogger::Extension] context_data additional informations
|
93
|
+
# to include in logs
|
94
|
+
#
|
95
|
+
def with_context(context_data)
|
96
|
+
push_context(context_data)
|
97
|
+
yield
|
98
|
+
ensure
|
99
|
+
# remove context
|
100
|
+
pop_context
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
##
|
107
|
+
# Store the context in fiber local variables, each
|
108
|
+
# Thread/Fiber gets its own.
|
109
|
+
def context
|
110
|
+
Thread.current["#{@name}_dlogger_contexts"] ||= []
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Store the temporary hash used to merge contexts.
|
115
|
+
#
|
116
|
+
def merged_metadata
|
117
|
+
Thread.current["#{@name}_dlogger_merged_metadata"] ||= {}
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Dispatch messages to all registered outputs.
|
122
|
+
#
|
123
|
+
# @param [String] msg the log message
|
124
|
+
# @param [Hash] metadata a hash including all the
|
125
|
+
# additional informations you want to make available
|
126
|
+
#
|
127
|
+
def dispatch(msg, metadata)
|
128
|
+
@outputs.each do |out|
|
129
|
+
out.dispatch(msg, metadata)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
gem 'eventmachine'; require 'eventmachine'
|
2
|
+
gem 'yajl-ruby'; require 'yajl'
|
3
|
+
|
4
|
+
module DLogger
|
5
|
+
module Output
|
6
|
+
|
7
|
+
module LogStashHandler
|
8
|
+
def initialize(master)
|
9
|
+
@master = master
|
10
|
+
end
|
11
|
+
|
12
|
+
def unbind
|
13
|
+
EM::add_timer(0.2){ @master.connect }
|
14
|
+
@master = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class LogStash < Base
|
19
|
+
def initialize(host, port)
|
20
|
+
@host = host
|
21
|
+
@port = port
|
22
|
+
connect
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect
|
26
|
+
@socket = EM::connect(@host, @port, LogStashHandler, self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def dispatch(msg, metadata)
|
30
|
+
metadata = metadata.merge(:message => msg)
|
31
|
+
@socket.send_data( Yajl::Encoder.encode(metadata) + "\n" )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DLogger
|
2
|
+
module Output
|
3
|
+
|
4
|
+
##
|
5
|
+
# Output data to a standard ruby logger.
|
6
|
+
class StdlibLogger < Base
|
7
|
+
|
8
|
+
##
|
9
|
+
# @param [Logger] logger a ruby logger
|
10
|
+
#
|
11
|
+
def initialize(logger, dump_metadata = true)
|
12
|
+
@logger = logger
|
13
|
+
@dump_metadata = dump_metadata
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# @see Logger::dispatch
|
18
|
+
#
|
19
|
+
def dispatch(msg, metadata)
|
20
|
+
severity = metadata.delete(:severity) || :debug
|
21
|
+
msg = @dump_metadata ? "#{msg} : #{metadata.inspect}" : msg
|
22
|
+
@logger.send(severity, msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module DLogger
|
4
|
+
module Output
|
5
|
+
|
6
|
+
##
|
7
|
+
# Outputs data to stdout.
|
8
|
+
#
|
9
|
+
class Stdout < Base
|
10
|
+
|
11
|
+
##
|
12
|
+
# @param [Boolean] dump_metadata if true the output will
|
13
|
+
# include the metadata dumped with inspect
|
14
|
+
#
|
15
|
+
def initialize(dump_metadata = true)
|
16
|
+
@dump_metadata = dump_metadata
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# @see Logger::dispatch
|
21
|
+
#
|
22
|
+
def dispatch(msg, metadata)
|
23
|
+
if @dump_metadata
|
24
|
+
Kernel.puts("#{msg} : #{metadata.inspect}")
|
25
|
+
else
|
26
|
+
Kernel.puts(msg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/specs/common.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
if (RUBY_VERSION >= "1.9") && ENV['COVERAGE']
|
6
|
+
require 'simplecov'
|
7
|
+
|
8
|
+
puts "[[ SimpleCov enabled ]]"
|
9
|
+
|
10
|
+
SimpleCov.start do
|
11
|
+
add_filter '/specs'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'bacon'
|
16
|
+
require 'mocha'
|
17
|
+
|
18
|
+
$LOAD_PATH.unshift( File.expand_path('../../lib', __FILE__) )
|
19
|
+
require 'dlogger'
|
20
|
+
|
21
|
+
|
22
|
+
Bacon.summary_at_exit
|
23
|
+
|
24
|
+
module Bacon
|
25
|
+
module MochaRequirementsCounter
|
26
|
+
def self.increment
|
27
|
+
Counter[:requirements] += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Context
|
32
|
+
include Mocha::API
|
33
|
+
|
34
|
+
alias_method :it_before_mocha, :it
|
35
|
+
|
36
|
+
def it(description)
|
37
|
+
it_before_mocha(description) do
|
38
|
+
# TODO: find better than that...
|
39
|
+
1.should == 1
|
40
|
+
begin
|
41
|
+
mocha_setup
|
42
|
+
yield
|
43
|
+
mocha_verify(MochaRequirementsCounter)
|
44
|
+
rescue Mocha::ExpectationError => e
|
45
|
+
raise Error.new(:failed, "#{e.message}\n#{e.backtrace[0...10].join("\n")}")
|
46
|
+
ensure
|
47
|
+
mocha_teardown
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../common'
|
2
|
+
|
3
|
+
describe 'Extension' do
|
4
|
+
should 'record methods added' do
|
5
|
+
ext_class = Class.new(DLogger::Extension) do
|
6
|
+
def user; "user"; end
|
7
|
+
def var; 34; end
|
8
|
+
end
|
9
|
+
|
10
|
+
ext_class.properties.should == [:user, :var]
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'keep separate lists for each subclass' do
|
14
|
+
ext_class1 = Class.new(DLogger::Extension) do
|
15
|
+
def var1; 1; end
|
16
|
+
def var2; 2; end
|
17
|
+
end
|
18
|
+
|
19
|
+
ext_class2 = Class.new(DLogger::Extension) do
|
20
|
+
def var45; 45; end
|
21
|
+
end
|
22
|
+
|
23
|
+
ext_class1.properties.should == [:var1, :var2]
|
24
|
+
ext_class2.properties.should == [:var45]
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "../common"
|
2
|
+
|
3
|
+
describe "Logger" do
|
4
|
+
before do
|
5
|
+
@logger = DLogger::Logger.new
|
6
|
+
end
|
7
|
+
|
8
|
+
should "dispatch msg with its to registered outputs" do
|
9
|
+
|
10
|
+
handler = mock('Handler')
|
11
|
+
handler.expects(:dispatch).with("the message", :id => 43, :user => 'bob')
|
12
|
+
|
13
|
+
@logger.add_output(handler)
|
14
|
+
|
15
|
+
@logger.log("the message", :id => 43, :user => "bob")
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'mimic standard ruby logger interface' do
|
19
|
+
@logger.expects(:dispatch).with('the message', :severity => :debug)
|
20
|
+
@logger.debug("the message")
|
21
|
+
|
22
|
+
@logger.expects(:dispatch).with('the message', :severity => :info)
|
23
|
+
@logger.info("the message")
|
24
|
+
|
25
|
+
@logger.expects(:dispatch).with('the message', :severity => :warn)
|
26
|
+
@logger.warn("the message")
|
27
|
+
|
28
|
+
@logger.expects(:dispatch).with('the message', :severity => :error)
|
29
|
+
@logger.error("the message")
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'with hash context' do
|
33
|
+
should 'dispatch msg and merged data' do
|
34
|
+
@logger.expects(:dispatch).with('msg', :id => 43, :user => 'bob')
|
35
|
+
|
36
|
+
@logger.with_context(:user => 'bob') do
|
37
|
+
@logger.log('msg', :id => 43)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'with extension context' do
|
44
|
+
should 'dispatch msg and merged data' do
|
45
|
+
ext = Class.new(DLogger::Extension) do
|
46
|
+
def user
|
47
|
+
'alice'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@logger.expects(:dispatch).with('msg', :id => 56, :user => 'alice')
|
52
|
+
|
53
|
+
@logger.with_context(ext) do
|
54
|
+
@logger.log('msg', :id => 56)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'with multiple contexts' do
|
61
|
+
should 'merge the contexts in defined order (last defined has greater priority)' do
|
62
|
+
@logger.expects(:dispatch).with('msg', :operation => 'destroy', :user => 'billy')
|
63
|
+
|
64
|
+
@logger.with_context(:user => 'bob') do
|
65
|
+
@logger.with_context(:user => 'billy') do
|
66
|
+
@logger.log('msg', :operation => "destroy")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@logger.expects(:dispatch).with('some text', {})
|
71
|
+
@logger.log('some text')
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "../../common"
|
2
|
+
|
3
|
+
describe "Stdlib Logger" do
|
4
|
+
before do
|
5
|
+
@out = DLogger::Output::StdlibLogger.new( Logger.new($stdout) )
|
6
|
+
end
|
7
|
+
|
8
|
+
should 'honor severity level' do
|
9
|
+
Logger.any_instance.expects(:info).with('message : {}')
|
10
|
+
@out.dispatch("message", :severity => :info)
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'use debug as default for severity level' do
|
14
|
+
# default = debug
|
15
|
+
Logger.any_instance.expects(:debug).with('message : {}')
|
16
|
+
@out.dispatch("message", {})
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'not log metadata when asked' do
|
20
|
+
out = DLogger::Output::StdlibLogger.new( Logger.new($stdout), false )
|
21
|
+
Logger.any_instance.expects(:debug).with('message')
|
22
|
+
out.dispatch("message", {})
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "../../common"
|
2
|
+
|
3
|
+
describe "Logger" do
|
4
|
+
|
5
|
+
should 'write messages to stdout' do
|
6
|
+
out = DLogger::Output::Stdout.new
|
7
|
+
Kernel.expects(:puts).with('message : {}')
|
8
|
+
out.dispatch("message", {})
|
9
|
+
end
|
10
|
+
|
11
|
+
should 'write messages to stdout without metadata' do
|
12
|
+
out = DLogger::Output::Stdout.new(false)
|
13
|
+
Kernel.expects(:puts).with('message')
|
14
|
+
out.dispatch("message", {})
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dlogger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Julien Ammous
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70201269940540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70201269940540
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: tilt
|
27
|
+
requirement: &70201269939560 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70201269939560
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: guard-tilt
|
38
|
+
requirement: &70201269938640 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70201269938640
|
47
|
+
description: Advanced logger allowing you to include metadata with your messages
|
48
|
+
email:
|
49
|
+
- schmurfy@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- Guardfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- README.md.erb
|
60
|
+
- Rakefile
|
61
|
+
- dlogger.gemspec
|
62
|
+
- examples/basic.rb
|
63
|
+
- examples/dynamic_context.rb
|
64
|
+
- examples/hash_context.rb
|
65
|
+
- examples/logstash.rb
|
66
|
+
- examples/nested_context.rb
|
67
|
+
- examples/threads.rb
|
68
|
+
- lib/dlogger.rb
|
69
|
+
- lib/dlogger/extension.rb
|
70
|
+
- lib/dlogger/logger.rb
|
71
|
+
- lib/dlogger/outputs/base.rb
|
72
|
+
- lib/dlogger/outputs/em/logstash.rb
|
73
|
+
- lib/dlogger/outputs/stdlib_logger.rb
|
74
|
+
- lib/dlogger/outputs/stdout.rb
|
75
|
+
- lib/dlogger/version.rb
|
76
|
+
- specs/common.rb
|
77
|
+
- specs/lib/extension_spec.rb
|
78
|
+
- specs/lib/logger_spec.rb
|
79
|
+
- specs/lib/outputs/stdlib_logger_spec.rb
|
80
|
+
- specs/lib/outputs/stdout_spec.rb
|
81
|
+
homepage: ''
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
hash: -1333174357779250362
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
hash: -1333174357779250362
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.15
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: ! 'Dynamic logger: add a context to your log messages'
|
111
|
+
test_files: []
|