scrolls 0.9.0.pre → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +52 -14
- data/Rakefile +4 -2
- data/docs/global-context.md +1 -1
- data/docs/syslog.md +14 -1
- data/lib/scrolls.rb +23 -2
- data/lib/scrolls/{iolog.rb → iologger.rb} +1 -1
- data/lib/scrolls/logger.rb +107 -86
- data/lib/scrolls/parser.rb +3 -1
- data/lib/scrolls/sysloglogger.rb +17 -0
- data/lib/scrolls/utils.rb +55 -13
- data/lib/scrolls/version.rb +1 -1
- data/test/test_helper.rb +4 -1
- data/test/test_parser.rb +10 -1
- data/test/test_scrolls.rb +11 -8
- metadata +7 -8
- data/lib/scrolls/log.rb +0 -262
- data/lib/scrolls/syslog.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0beda1c02c56ef85e12ea9a241e4a2077d47455d
|
4
|
+
data.tar.gz: 60228ade9fab4786baafc08e186b9fb3de509a3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 913819999b29cb0c5d039440340260b38ea92ff9c1877260946dc2129fc3f9d6df66643935403bc4fc3ea743f2512e7025e0231ffe7e950d51f83002d8cd377d
|
7
|
+
data.tar.gz: 7163d0841cd810a1741e2e333cf62b8cabd1c512da443cd16d10fb4f4f4b02c73f8e10248f58c5c98ac0324f573056521766b0770276f501159ebc212c22f695
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,26 +20,64 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
Scrolls follows the belief that logs should be treated as data. One way to think of them is the blood of your infrastructure. Logs are a realtime view of what is happening on your systems.
|
22
22
|
|
23
|
-
##
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### 0.9.0 and later
|
24
26
|
|
25
|
-
|
27
|
+
```ruby
|
28
|
+
require 'scrolls'
|
26
29
|
|
27
|
-
|
30
|
+
Scrolls.init(
|
31
|
+
timestamp: true,
|
32
|
+
global_context: {app: "scrolls", deploy: "production"},
|
33
|
+
exceptions: "multi"
|
34
|
+
)
|
28
35
|
|
29
|
-
|
36
|
+
Scrolls.log(at: "test")
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
* Misc Features
|
38
|
+
Scrolls.context(context: "block") do
|
39
|
+
Scrolls.log(at: "exec")
|
40
|
+
end
|
35
41
|
|
36
|
-
|
42
|
+
begin
|
43
|
+
raise
|
44
|
+
rescue Exception => e
|
45
|
+
Scrolls.log_exception(e, at: "raise")
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
You can also use `Scrolls#log` and `Scrolls#log_exception` without initalizing:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'scrolls'
|
53
|
+
|
54
|
+
Scrolls.log(test: "test")
|
55
|
+
```
|
56
|
+
|
57
|
+
### Defaults
|
58
|
+
|
59
|
+
Here are the defaults `Scrolls#init`:
|
60
|
+
|
61
|
+
```
|
62
|
+
stream: STDOUT
|
63
|
+
facility: Syslog::LOG_USER
|
64
|
+
time_unit: "seconds"
|
65
|
+
timestamp: false
|
66
|
+
exceptions: "single"
|
67
|
+
global_context: {}
|
68
|
+
syslog_options: Syslog::LOG_PID|Syslog::LOG_CONS
|
69
|
+
escape_keys: false
|
70
|
+
```
|
71
|
+
|
72
|
+
## Older Versions
|
73
|
+
|
74
|
+
### Pre 0.9.0
|
37
75
|
|
38
76
|
```ruby
|
39
77
|
require 'scrolls'
|
40
78
|
|
41
79
|
Scrolls.add_timestamp = true
|
42
|
-
Scrolls.global_context(:app => "scrolls", :deploy =>
|
80
|
+
Scrolls.global_context(:app => "scrolls", :deploy => "production")
|
43
81
|
|
44
82
|
Scrolls.log(:at => "test")
|
45
83
|
|
@@ -57,10 +95,10 @@ end
|
|
57
95
|
Produces:
|
58
96
|
|
59
97
|
```
|
60
|
-
now="
|
61
|
-
now="
|
62
|
-
now="
|
63
|
-
now="
|
98
|
+
now="2017-09-01T00:37:13Z" app=scrolls deploy=production at=test
|
99
|
+
now="2017-09-01T00:37:13Z" app=scrolls deploy=production context=block at=exec
|
100
|
+
now="2017-09-01T00:37:13Z" app=scrolls deploy=production at=exception class=RuntimeError exception_id=70149797587080
|
101
|
+
now="2017-09-01T00:37:13Z" app=scrolls deploy=production at=exception class=RuntimeError exception_id=70149797587080 site="./test-scrolls.rb:16:in <main>"
|
64
102
|
```
|
65
103
|
|
66
104
|
## History
|
data/Rakefile
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
+
|
2
3
|
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
3
5
|
|
4
6
|
ENV['TESTOPTS'] = "-v"
|
5
7
|
|
6
|
-
require "rake/testtask"
|
7
8
|
Rake::TestTask.new do |t|
|
8
|
-
t.
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
t.verbose = true
|
9
11
|
end
|
10
12
|
|
11
13
|
task :default => :test
|
data/docs/global-context.md
CHANGED
data/docs/syslog.md
CHANGED
@@ -6,6 +6,14 @@ By default Scrolls writes log messages to `STDOUT`. With the release of [v0.2.8]
|
|
6
6
|
Scrolls.stream = "syslog"
|
7
7
|
```
|
8
8
|
|
9
|
+
Or using `Scrolls#init` in versions 0.9.0 and after:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Scrolls.init(
|
13
|
+
stream: "syslog"
|
14
|
+
)
|
15
|
+
```
|
16
|
+
|
9
17
|
This defaults to syslog facility USER and log level ERROR. You can adjust the log facility like so:
|
10
18
|
|
11
19
|
```ruby
|
@@ -14,4 +22,9 @@ Scrolls.facility = "local7"
|
|
14
22
|
|
15
23
|
Scrolls generally doesn't care about log levels. The library defaults to ERROR (or 3), but ultimately is of the opinion that levels are useless. The reasoning behind this is that applications should log useful data, all of the time. Debugging data is great for development, but should never be deployed. The richness of structured logging allows exceptions and error messages to sit along side the context of the data in which the error was thrown, there is no need to send to an "emergency" level.
|
16
24
|
|
17
|
-
With that said, if one wanted to adjust the log level, you can set an environment variable `LOG_LEVEL
|
25
|
+
With that said, if one wanted to adjust the log level, you can set an environment variable `LOG_LEVEL` or use one of the level methods. This allows this particular feature to be rather fluid throughout your application.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Scrolls.info(d: "data")
|
29
|
+
Scrolls.warn(d: "data")
|
30
|
+
```
|
data/lib/scrolls.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "thread"
|
2
1
|
require "scrolls/logger"
|
3
2
|
require "scrolls/version"
|
4
3
|
|
@@ -8,9 +7,25 @@ module Scrolls
|
|
8
7
|
# Public: Initialize a Scrolls logger
|
9
8
|
#
|
10
9
|
# options - A hash of key/values for configuring Scrolls
|
10
|
+
# stream - Stream to output data (default: STDOUT)
|
11
|
+
# log_facility - Syslog facility (default: Syslog::LOG_USER)
|
12
|
+
# time_unit - Unit of time (default: seconds)
|
13
|
+
# timestamp - Prepend logs with a timestamp (default: false)
|
14
|
+
# exceptions - Method for outputting exceptions (default: single line)
|
15
|
+
# global_context - Immutable context to prepend all messages with
|
16
|
+
# syslog_options - Syslog options (default: Syslog::LOG_PID|Syslog::LOG_CONS)
|
17
|
+
# escape_keys - Escape chars in keys
|
11
18
|
#
|
12
19
|
def init(options={})
|
13
|
-
|
20
|
+
# Set a hint whether #init was called.
|
21
|
+
@initialized = true
|
22
|
+
@log = Logger.new(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Get the primary logger
|
26
|
+
#
|
27
|
+
def logger
|
28
|
+
@log.logger
|
14
29
|
end
|
15
30
|
|
16
31
|
# Public: Set a context in a block for logs
|
@@ -48,6 +63,9 @@ module Scrolls
|
|
48
63
|
# => nil
|
49
64
|
#
|
50
65
|
def log(data, &blk)
|
66
|
+
# Allows us to call #log directly and initialize defaults
|
67
|
+
@log = Logger.new({}) unless @initialized
|
68
|
+
|
51
69
|
@log.log(data, &blk)
|
52
70
|
end
|
53
71
|
|
@@ -67,6 +85,9 @@ module Scrolls
|
|
67
85
|
# ...
|
68
86
|
#
|
69
87
|
def log_exception(data, e)
|
88
|
+
# Allows us to call #log directly and initialize defaults
|
89
|
+
@log = Logger.new({}) unless @initialized
|
90
|
+
|
70
91
|
@log.log_exception(data, e)
|
71
92
|
end
|
72
93
|
|
data/lib/scrolls/logger.rb
CHANGED
@@ -1,100 +1,102 @@
|
|
1
|
+
require "syslog"
|
2
|
+
|
1
3
|
require "scrolls/parser"
|
2
|
-
require "scrolls/
|
3
|
-
require "scrolls/
|
4
|
+
require "scrolls/iologger"
|
5
|
+
require "scrolls/sysloglogger"
|
6
|
+
require "scrolls/utils"
|
4
7
|
|
5
8
|
module Scrolls
|
9
|
+
# Default log facility
|
10
|
+
LOG_FACILITY = ENV['LOG_FACILITY'] || Syslog::LOG_USER
|
11
|
+
|
12
|
+
# Default log level
|
13
|
+
LOG_LEVEL = (ENV['LOG_LEVEL'] || 6).to_i
|
14
|
+
|
15
|
+
# Default syslog options
|
16
|
+
SYSLOG_OPTIONS = Syslog::LOG_PID|Syslog::LOG_CONS
|
17
|
+
|
6
18
|
class TimeUnitError < RuntimeError; end
|
19
|
+
class LogLevelError < StandardError; end
|
7
20
|
|
21
|
+
# Top level class to hold our global context
|
22
|
+
#
|
23
|
+
# Global context is defined using Scrolls#init
|
8
24
|
class GlobalContext
|
9
|
-
|
10
|
-
|
11
|
-
@context = context || {}
|
25
|
+
def initialize(ctx)
|
26
|
+
@ctx = ctx || {}
|
12
27
|
end
|
13
28
|
|
14
29
|
def to_h
|
15
|
-
@
|
30
|
+
@ctx
|
16
31
|
end
|
17
32
|
end
|
18
33
|
|
19
|
-
class
|
20
|
-
LOG_FACILITY = ENV['LOG_FACILITY'] || Syslog::LOG_USER
|
21
|
-
LOG_FACILITY_MAP = {
|
22
|
-
"auth" => Syslog::LOG_AUTH,
|
23
|
-
"authpriv" => Syslog::LOG_AUTHPRIV,
|
24
|
-
"cron" => Syslog::LOG_CRON,
|
25
|
-
"daemon" => Syslog::LOG_DAEMON,
|
26
|
-
"ftp" => Syslog::LOG_FTP,
|
27
|
-
"kern" => Syslog::LOG_KERN,
|
28
|
-
"mail" => Syslog::LOG_MAIL,
|
29
|
-
"news" => Syslog::LOG_NEWS,
|
30
|
-
"syslog" => Syslog::LOG_SYSLOG,
|
31
|
-
"user" => Syslog::LOG_USER,
|
32
|
-
"uucp" => Syslog::LOG_UUCP,
|
33
|
-
"local0" => Syslog::LOG_LOCAL0,
|
34
|
-
"local1" => Syslog::LOG_LOCAL1,
|
35
|
-
"local2" => Syslog::LOG_LOCAL2,
|
36
|
-
"local3" => Syslog::LOG_LOCAL3,
|
37
|
-
"local4" => Syslog::LOG_LOCAL4,
|
38
|
-
"local5" => Syslog::LOG_LOCAL5,
|
39
|
-
"local6" => Syslog::LOG_LOCAL6,
|
40
|
-
"local7" => Syslog::LOG_LOCAL7,
|
41
|
-
}
|
42
|
-
|
43
|
-
LOG_LEVEL = (ENV['LOG_LEVEL'] || 6).to_i
|
44
|
-
LOG_LEVEL_MAP = {
|
45
|
-
"emergency" => 0,
|
46
|
-
"alert" => 1,
|
47
|
-
"critical" => 2,
|
48
|
-
"error" => 3,
|
49
|
-
"warning" => 4,
|
50
|
-
"notice" => 5,
|
51
|
-
"info" => 6,
|
52
|
-
"debug" => 7
|
53
|
-
}
|
34
|
+
class Logger
|
54
35
|
|
55
36
|
attr_reader :logger
|
56
37
|
attr_accessor :exceptions, :timestamp
|
57
38
|
|
58
39
|
def initialize(options={})
|
59
|
-
@stream
|
60
|
-
@
|
61
|
-
@time_unit
|
62
|
-
@timestamp
|
63
|
-
@exceptions
|
64
|
-
@global_ctx
|
65
|
-
|
40
|
+
@stream = options.fetch(:stream, STDOUT)
|
41
|
+
@log_facility = options.fetch(:facility, LOG_FACILITY)
|
42
|
+
@time_unit = options.fetch(:time_unit, "seconds")
|
43
|
+
@timestamp = options.fetch(:timestamp, false)
|
44
|
+
@exceptions = options.fetch(:exceptions, "single")
|
45
|
+
@global_ctx = options.fetch(:global_context, {})
|
46
|
+
@syslog_opts = options.fetch(:syslog_options, SYSLOG_OPTIONS)
|
47
|
+
@escape_keys = options.fetch(:escape_keys, false)
|
48
|
+
|
49
|
+
# Our main entry point to ensure our options are setup properly
|
66
50
|
setup!
|
67
51
|
end
|
68
52
|
|
69
53
|
def context
|
70
|
-
Thread.current
|
54
|
+
if Thread.current.thread_variables.include?(:scrolls_context)
|
55
|
+
Thread.current.thread_variable_get(:scrolls_context)
|
56
|
+
else
|
57
|
+
Thread.current.thread_variable_set(:scrolls_context, {})
|
58
|
+
end
|
71
59
|
end
|
72
60
|
|
73
61
|
def context=(h)
|
74
|
-
Thread.current
|
62
|
+
Thread.current.thread_variable_set(:scrolls_context, h || {})
|
75
63
|
end
|
76
64
|
|
77
65
|
def stream
|
78
66
|
@stream
|
79
67
|
end
|
80
68
|
|
81
|
-
def stream=(
|
69
|
+
def stream=(s)
|
82
70
|
# Return early to avoid setup
|
83
|
-
return if
|
71
|
+
return if s == @stream
|
84
72
|
|
85
|
-
@stream =
|
73
|
+
@stream = s
|
86
74
|
setup_stream
|
87
75
|
end
|
88
76
|
|
77
|
+
def escape_keys?
|
78
|
+
@escape_keys
|
79
|
+
end
|
80
|
+
|
81
|
+
def syslog_options
|
82
|
+
@syslog_opts
|
83
|
+
end
|
84
|
+
|
89
85
|
def facility
|
90
|
-
@facility
|
86
|
+
@facility
|
91
87
|
end
|
92
88
|
|
93
89
|
def facility=(f)
|
94
90
|
if f
|
95
|
-
|
96
|
-
#
|
97
|
-
|
91
|
+
setup_facility(f)
|
92
|
+
# If we are using syslog, we need to setup our connection again
|
93
|
+
if stream == "syslog"
|
94
|
+
@logger = Scrolls::SyslogLogger.new(
|
95
|
+
progname,
|
96
|
+
syslog_options,
|
97
|
+
facility
|
98
|
+
)
|
99
|
+
end
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
@@ -104,7 +106,7 @@ module Scrolls
|
|
104
106
|
|
105
107
|
def time_unit=(u)
|
106
108
|
@time_unit = u
|
107
|
-
|
109
|
+
setup_time_unit
|
108
110
|
end
|
109
111
|
|
110
112
|
def global_context
|
@@ -138,14 +140,16 @@ module Scrolls
|
|
138
140
|
begin
|
139
141
|
res = yield
|
140
142
|
rescue StandardError => e
|
141
|
-
|
142
|
-
:
|
143
|
-
:
|
144
|
-
:
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
148
|
-
)
|
143
|
+
logdata.merge({
|
144
|
+
at: "exception",
|
145
|
+
reraise: true,
|
146
|
+
class: e.class,
|
147
|
+
message: e.message,
|
148
|
+
exception_id: e.object_id.abs,
|
149
|
+
elapsed: calculate_time(start, Time.now)
|
150
|
+
})
|
151
|
+
logdata.delete_if { |k,v| k if v == "" }
|
152
|
+
log(logdata)
|
149
153
|
raise e
|
150
154
|
end
|
151
155
|
log(logdata.merge(:at => "finish", :elapsed => calculate_time(start, Time.now)))
|
@@ -153,17 +157,20 @@ module Scrolls
|
|
153
157
|
end
|
154
158
|
end
|
155
159
|
|
156
|
-
def log_exception(
|
160
|
+
def log_exception(e, data=nil)
|
157
161
|
unless @defined
|
158
162
|
@stream = STDERR
|
159
163
|
setup_stream
|
160
164
|
end
|
161
165
|
|
162
|
-
#
|
163
|
-
|
166
|
+
# We check our arguments for type
|
167
|
+
case data
|
168
|
+
when String
|
164
169
|
rawhash = { "log_message" => data }
|
165
|
-
|
170
|
+
when Hash
|
166
171
|
rawhash = data
|
172
|
+
else
|
173
|
+
rawhash = {}
|
167
174
|
end
|
168
175
|
|
169
176
|
if gc = @global_context.to_h
|
@@ -171,12 +178,14 @@ module Scrolls
|
|
171
178
|
end
|
172
179
|
|
173
180
|
excepdata = {
|
174
|
-
:
|
175
|
-
:
|
176
|
-
:
|
177
|
-
:
|
181
|
+
at: "exception",
|
182
|
+
class: e.class,
|
183
|
+
message: e.message,
|
184
|
+
exception_id: e.object_id.abs
|
178
185
|
}
|
179
186
|
|
187
|
+
excepdata.delete_if { |k,v| k if v == "" }
|
188
|
+
|
180
189
|
if e.backtrace
|
181
190
|
if single_line_exceptions?
|
182
191
|
lines = e.backtrace.map { |line| line.gsub(/[`'"]/, "") }
|
@@ -213,14 +222,15 @@ module Scrolls
|
|
213
222
|
private
|
214
223
|
|
215
224
|
def setup!
|
216
|
-
|
225
|
+
setup_global_context
|
217
226
|
prepend_timestamp?
|
227
|
+
setup_facility
|
218
228
|
setup_stream
|
219
229
|
single_line_exceptions?
|
220
|
-
|
230
|
+
setup_time_unit
|
221
231
|
end
|
222
232
|
|
223
|
-
def
|
233
|
+
def setup_global_context
|
224
234
|
# Builds up an immutable object for our global_context
|
225
235
|
# This is not backwards compatiable and was introduced after 0.3.7.
|
226
236
|
# Removes ability to add to global context once we initialize our
|
@@ -233,15 +243,28 @@ module Scrolls
|
|
233
243
|
@timestamp
|
234
244
|
end
|
235
245
|
|
246
|
+
def setup_facility(f=nil)
|
247
|
+
if f
|
248
|
+
@facility = LOG_FACILITY_MAP.fetch(f, LOG_FACILITY)
|
249
|
+
else
|
250
|
+
@facility = LOG_FACILITY_MAP.fetch(@log_facility, LOG_FACILITY)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
236
254
|
def setup_stream
|
237
255
|
unless @stream == STDOUT
|
256
|
+
# Set this so we know we aren't using our default stream
|
238
257
|
@defined = true
|
239
258
|
end
|
240
259
|
|
241
260
|
if @stream == "syslog"
|
242
|
-
@logger = Scrolls::SyslogLogger.new(
|
261
|
+
@logger = Scrolls::SyslogLogger.new(
|
262
|
+
progname,
|
263
|
+
syslog_options,
|
264
|
+
facility
|
265
|
+
)
|
243
266
|
else
|
244
|
-
@logger =
|
267
|
+
@logger = IOLogger.new(@stream)
|
245
268
|
end
|
246
269
|
end
|
247
270
|
|
@@ -250,7 +273,7 @@ module Scrolls
|
|
250
273
|
true
|
251
274
|
end
|
252
275
|
|
253
|
-
def
|
276
|
+
def setup_time_unit
|
254
277
|
unless %w{s ms seconds milliseconds}.include? @time_unit
|
255
278
|
raise TimeUnitError, "Specify the following: s, ms, seconds, milliseconds"
|
256
279
|
end
|
@@ -264,11 +287,8 @@ module Scrolls
|
|
264
287
|
@t = 1.0
|
265
288
|
end
|
266
289
|
end
|
267
|
-
|
268
|
-
def sync_stream(out = STDOUT)
|
269
|
-
IOLog.new(out)
|
270
|
-
end
|
271
290
|
|
291
|
+
# We need this for our syslog setup
|
272
292
|
def progname
|
273
293
|
File.basename($0)
|
274
294
|
end
|
@@ -280,6 +300,7 @@ module Scrolls
|
|
280
300
|
|
281
301
|
def log_level_ok?(level)
|
282
302
|
if level
|
303
|
+
raise LogLevelError, "Log level unknown" unless LOG_LEVEL_MAP.key?(level)
|
283
304
|
LOG_LEVEL_MAP[level.to_s] <= LOG_LEVEL
|
284
305
|
else
|
285
306
|
true
|
@@ -288,8 +309,8 @@ module Scrolls
|
|
288
309
|
|
289
310
|
def write(data)
|
290
311
|
if log_level_ok?(data[:level])
|
291
|
-
msg = Scrolls::Parser.unparse(data)
|
292
|
-
logger.log(msg)
|
312
|
+
msg = Scrolls::Parser.unparse(data, escape_keys=escape_keys?)
|
313
|
+
@logger.log(msg)
|
293
314
|
end
|
294
315
|
end
|
295
316
|
|
data/lib/scrolls/parser.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Scrolls
|
2
|
+
class SyslogLogger
|
3
|
+
def initialize(ident = 'scrolls',
|
4
|
+
options = Scrolls::SYSLOG_OPTIONS,
|
5
|
+
facility = Scrolls::LOG_FACILITY)
|
6
|
+
if Syslog.opened?
|
7
|
+
@syslog = Syslog.reopen(ident, options, facility)
|
8
|
+
else
|
9
|
+
@syslog = Syslog.open(ident, options, facility)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def log(data)
|
14
|
+
@syslog.log(Syslog::LOG_INFO, "%s", data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/scrolls/utils.rb
CHANGED
@@ -1,19 +1,61 @@
|
|
1
1
|
module Scrolls
|
2
|
-
module Utils
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# Helpful map of syslog facilities
|
4
|
+
LOG_FACILITY_MAP = {
|
5
|
+
"auth" => Syslog::LOG_AUTH,
|
6
|
+
"authpriv" => Syslog::LOG_AUTHPRIV,
|
7
|
+
"cron" => Syslog::LOG_CRON,
|
8
|
+
"daemon" => Syslog::LOG_DAEMON,
|
9
|
+
"ftp" => Syslog::LOG_FTP,
|
10
|
+
"kern" => Syslog::LOG_KERN,
|
11
|
+
"mail" => Syslog::LOG_MAIL,
|
12
|
+
"news" => Syslog::LOG_NEWS,
|
13
|
+
"syslog" => Syslog::LOG_SYSLOG,
|
14
|
+
"user" => Syslog::LOG_USER,
|
15
|
+
"uucp" => Syslog::LOG_UUCP,
|
16
|
+
"local0" => Syslog::LOG_LOCAL0,
|
17
|
+
"local1" => Syslog::LOG_LOCAL1,
|
18
|
+
"local2" => Syslog::LOG_LOCAL2,
|
19
|
+
"local3" => Syslog::LOG_LOCAL3,
|
20
|
+
"local4" => Syslog::LOG_LOCAL4,
|
21
|
+
"local5" => Syslog::LOG_LOCAL5,
|
22
|
+
"local6" => Syslog::LOG_LOCAL6,
|
23
|
+
"local7" => Syslog::LOG_LOCAL7,
|
24
|
+
}
|
25
|
+
|
26
|
+
# Helpful map of syslog log levels
|
27
|
+
LOG_LEVEL_MAP = {
|
28
|
+
"emerg" => 0, # Syslog::LOG_EMERG
|
29
|
+
"emergency" => 0, # Syslog::LOG_EMERG
|
30
|
+
"alert" => 1, # Syslog::LOG_ALERT
|
31
|
+
"crit" => 2, # Syslog::LOG_CRIT
|
32
|
+
"critical" => 2, # Syslog::LOG_CRIT
|
33
|
+
"error" => 3, # Syslog::LOG_ERR
|
34
|
+
"warn" => 4, # Syslog::LOG_WARNING
|
35
|
+
"warning" => 4, # Syslog::LOG_WARNING
|
36
|
+
"notice" => 5, # Syslog::LOG_NOTICE
|
37
|
+
"info" => 6, # Syslog::LOG_INFO
|
38
|
+
"debug" => 7 # Syslog::LOG_DEBUG
|
39
|
+
}
|
40
|
+
|
41
|
+
ESCAPE_CHAR = {
|
42
|
+
"&" => "&",
|
43
|
+
"<" => "<",
|
44
|
+
">" => ">",
|
45
|
+
"'" => "'",
|
46
|
+
'"' => """,
|
47
|
+
"/" => "/"
|
48
|
+
}
|
49
|
+
|
50
|
+
ESCAPE_CHAR_PATTERN = Regexp.union(*ESCAPE_CHAR.keys)
|
51
|
+
|
52
|
+
module Utils
|
11
53
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
54
|
+
def self.escape_chars(d)
|
55
|
+
if d.is_a?(String) and d =~ ESCAPE_CHAR_PATTERN
|
56
|
+
esc = d.to_s.gsub(ESCAPE_CHAR_PATTERN) {|c| ESCAPE_CHAR[c] }
|
57
|
+
else
|
58
|
+
esc = d
|
17
59
|
end
|
18
60
|
end
|
19
61
|
|
data/lib/scrolls/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/test_parser.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path("../test_helper", __FILE__)
|
2
2
|
|
3
|
-
class TestScrollsParser < Test
|
3
|
+
class TestScrollsParser < Minitest::Test
|
4
4
|
include Scrolls::Parser
|
5
5
|
|
6
6
|
def test_parse_bool
|
@@ -79,6 +79,15 @@ class TestScrollsParser < Test::Unit::TestCase
|
|
79
79
|
assert_equal 't="2012-06-19T16:02:35+01:00"', unparse(data)
|
80
80
|
end
|
81
81
|
|
82
|
+
def test_unparse_escape_keys
|
83
|
+
html = "<p>p</p>"
|
84
|
+
slash = "p/p"
|
85
|
+
|
86
|
+
data = { html => "d", slash => "d" }
|
87
|
+
assert_equal '<p>p</p>=d p/p=d',
|
88
|
+
unparse(data, escape_keys=true)
|
89
|
+
end
|
90
|
+
|
82
91
|
def test_parse_time
|
83
92
|
time = Time.new(2012, 06, 19, 16, 02, 35, "+01:00")
|
84
93
|
string = "t=2012-06-19T16:02:35+01:00"
|
data/test/test_scrolls.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path("../test_helper", __FILE__)
|
2
2
|
|
3
|
-
class TestScrolls < Test
|
3
|
+
class TestScrolls < Minitest::Test
|
4
4
|
def setup
|
5
5
|
@out = StringIO.new
|
6
6
|
Scrolls.init(
|
@@ -8,12 +8,9 @@ class TestScrolls < Test::Unit::TestCase
|
|
8
8
|
)
|
9
9
|
end
|
10
10
|
|
11
|
-
def teardown
|
12
|
-
end
|
13
|
-
|
14
11
|
def test_default_construct
|
15
12
|
Scrolls.init
|
16
|
-
assert_equal Scrolls::
|
13
|
+
assert_equal Scrolls::IOLogger, Scrolls.logger.class
|
17
14
|
end
|
18
15
|
|
19
16
|
def test_default_global_context
|
@@ -103,12 +100,18 @@ class TestScrolls < Test::Unit::TestCase
|
|
103
100
|
end
|
104
101
|
|
105
102
|
def test_setting_incorrect_time_unit
|
106
|
-
|
103
|
+
assert_raises Scrolls::TimeUnitError do
|
107
104
|
Scrolls.time_unit = "years"
|
108
105
|
Scrolls.log(:tu => "yrs")
|
109
106
|
end
|
110
107
|
end
|
111
108
|
|
109
|
+
def test_unknown_log_level
|
110
|
+
assert_raises Scrolls::LogLevelError do
|
111
|
+
Scrolls.log(:level => "nope")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
112
115
|
def test_logging
|
113
116
|
Scrolls.log(:test => "basic")
|
114
117
|
assert_equal "test=basic\n", @out.string
|
@@ -124,7 +127,7 @@ class TestScrolls < Test::Unit::TestCase
|
|
124
127
|
begin
|
125
128
|
raise Exception
|
126
129
|
rescue Exception => e
|
127
|
-
Scrolls.log_exception({:test => "exception"}
|
130
|
+
Scrolls.log_exception(e, {:test => "exception"})
|
128
131
|
end
|
129
132
|
|
130
133
|
oneline_bt = @out.string.gsub("\n", 'XX')
|
@@ -136,7 +139,7 @@ class TestScrolls < Test::Unit::TestCase
|
|
136
139
|
begin
|
137
140
|
raise Exception
|
138
141
|
rescue Exception => e
|
139
|
-
Scrolls.log_exception({:o => "o"}
|
142
|
+
Scrolls.log_exception(e, {:o => "o"})
|
140
143
|
end
|
141
144
|
|
142
145
|
oneline_bt = @out.string.gsub("\n", 'XX')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scrolls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.0
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Curt Micol
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Logging, easier, more consistent.
|
14
14
|
email:
|
@@ -25,11 +25,10 @@ files:
|
|
25
25
|
- docs/global-context.md
|
26
26
|
- docs/syslog.md
|
27
27
|
- lib/scrolls.rb
|
28
|
-
- lib/scrolls/
|
29
|
-
- lib/scrolls/log.rb
|
28
|
+
- lib/scrolls/iologger.rb
|
30
29
|
- lib/scrolls/logger.rb
|
31
30
|
- lib/scrolls/parser.rb
|
32
|
-
- lib/scrolls/
|
31
|
+
- lib/scrolls/sysloglogger.rb
|
33
32
|
- lib/scrolls/utils.rb
|
34
33
|
- lib/scrolls/version.rb
|
35
34
|
- scrolls.gemspec
|
@@ -51,12 +50,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
50
|
version: '0'
|
52
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
52
|
requirements:
|
54
|
-
- - "
|
53
|
+
- - ">="
|
55
54
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
55
|
+
version: '0'
|
57
56
|
requirements: []
|
58
57
|
rubyforge_project:
|
59
|
-
rubygems_version: 2.6.
|
58
|
+
rubygems_version: 2.6.13
|
60
59
|
signing_key:
|
61
60
|
specification_version: 4
|
62
61
|
summary: When do we log? All the time.
|
data/lib/scrolls/log.rb
DELETED
@@ -1,262 +0,0 @@
|
|
1
|
-
require "scrolls/parser"
|
2
|
-
require "scrolls/utils"
|
3
|
-
require "scrolls/iolog"
|
4
|
-
require "scrolls/syslog"
|
5
|
-
|
6
|
-
module Scrolls
|
7
|
-
|
8
|
-
class TimeUnitError < RuntimeError; end
|
9
|
-
|
10
|
-
module Log
|
11
|
-
extend self
|
12
|
-
|
13
|
-
extend Parser
|
14
|
-
extend Utils
|
15
|
-
|
16
|
-
LOG_LEVEL = (ENV['LOG_LEVEL'] || 6).to_i
|
17
|
-
LOG_LEVEL_MAP = {
|
18
|
-
"emergency" => 0,
|
19
|
-
"alert" => 1,
|
20
|
-
"critical" => 2,
|
21
|
-
"error" => 3,
|
22
|
-
"warning" => 4,
|
23
|
-
"notice" => 5,
|
24
|
-
"info" => 6,
|
25
|
-
"debug" => 7
|
26
|
-
}
|
27
|
-
|
28
|
-
def context
|
29
|
-
Thread.current[:scrolls_context] ||= {}
|
30
|
-
end
|
31
|
-
|
32
|
-
def context=(h)
|
33
|
-
Thread.current[:scrolls_context] = h
|
34
|
-
end
|
35
|
-
|
36
|
-
def global_context
|
37
|
-
get_global_context
|
38
|
-
end
|
39
|
-
|
40
|
-
def global_context=(data)
|
41
|
-
set_global_context(data)
|
42
|
-
end
|
43
|
-
|
44
|
-
def add_global_context(new_data)
|
45
|
-
default_global_context unless @global_context
|
46
|
-
@global_context.update { |previous_data| previous_data.merge(new_data) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def facility=(f)
|
50
|
-
@facility = LOG_FACILITY_MAP[f] if f
|
51
|
-
if Scrolls::SyslogLogger.opened?
|
52
|
-
Scrolls::SyslogLogger.new(progname, facility)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def facility
|
57
|
-
@facility ||= default_log_facility
|
58
|
-
end
|
59
|
-
|
60
|
-
def stream=(out=nil)
|
61
|
-
@defined = out.nil? ? false : true
|
62
|
-
if out == 'syslog'
|
63
|
-
@stream = Scrolls::SyslogLogger.new(progname, facility)
|
64
|
-
else
|
65
|
-
@stream = sync_stream(out)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def stream
|
70
|
-
@stream ||= sync_stream
|
71
|
-
end
|
72
|
-
|
73
|
-
def time_unit=(u)
|
74
|
-
set_time_unit(u)
|
75
|
-
end
|
76
|
-
|
77
|
-
def time_unit
|
78
|
-
@tunit ||= default_time_unit
|
79
|
-
end
|
80
|
-
|
81
|
-
def add_timestamp=(b)
|
82
|
-
@add_timestamp = !!b
|
83
|
-
end
|
84
|
-
|
85
|
-
def add_timestamp
|
86
|
-
@add_timestamp || false
|
87
|
-
end
|
88
|
-
|
89
|
-
def single_line_exceptions=(b)
|
90
|
-
@single_line_exceptions = !!b
|
91
|
-
end
|
92
|
-
|
93
|
-
def single_line_exceptions?
|
94
|
-
@single_line_exceptions || false
|
95
|
-
end
|
96
|
-
|
97
|
-
def log(data, &blk)
|
98
|
-
# If we get a string lets bring it into our structure.
|
99
|
-
if data.kind_of? String
|
100
|
-
rawhash = { "log_message" => data }
|
101
|
-
else
|
102
|
-
rawhash = data
|
103
|
-
end
|
104
|
-
|
105
|
-
if gc = get_global_context
|
106
|
-
ctx = gc.merge(context)
|
107
|
-
logdata = ctx.merge(rawhash)
|
108
|
-
end
|
109
|
-
|
110
|
-
# By merging the logdata into the timestamp, rather than vice-versa, we
|
111
|
-
# ensure that the timestamp comes first in the Hash, and is placed first
|
112
|
-
# on the output, which helps with readability.
|
113
|
-
logdata = { :now => Time.now.utc }.merge(logdata) if add_timestamp
|
114
|
-
|
115
|
-
unless blk
|
116
|
-
write(logdata)
|
117
|
-
else
|
118
|
-
start = Time.now
|
119
|
-
res = nil
|
120
|
-
log(logdata.merge(:at => "start"))
|
121
|
-
begin
|
122
|
-
res = yield
|
123
|
-
rescue StandardError => e
|
124
|
-
log(logdata.merge(
|
125
|
-
:at => "exception",
|
126
|
-
:reraise => true,
|
127
|
-
:class => e.class,
|
128
|
-
:message => e.message,
|
129
|
-
:exception_id => e.object_id.abs,
|
130
|
-
:elapsed => calc_time(start, Time.now)
|
131
|
-
))
|
132
|
-
raise e
|
133
|
-
end
|
134
|
-
log(logdata.merge(:at => "finish", :elapsed => calc_time(start, Time.now)))
|
135
|
-
res
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def log_exception(data, e)
|
140
|
-
sync_stream(STDERR) unless @defined
|
141
|
-
|
142
|
-
# If we get a string lets bring it into our structure.
|
143
|
-
if data.kind_of? String
|
144
|
-
rawhash = { "log_message" => data }
|
145
|
-
else
|
146
|
-
rawhash = data
|
147
|
-
end
|
148
|
-
|
149
|
-
if gc = get_global_context
|
150
|
-
logdata = gc.merge(rawhash)
|
151
|
-
end
|
152
|
-
|
153
|
-
excepdata = {
|
154
|
-
:at => "exception",
|
155
|
-
:class => e.class,
|
156
|
-
:message => e.message,
|
157
|
-
:exception_id => e.object_id.abs
|
158
|
-
}
|
159
|
-
|
160
|
-
if e.backtrace
|
161
|
-
if single_line_exceptions?
|
162
|
-
btlines = []
|
163
|
-
e.backtrace.each do |line|
|
164
|
-
btlines << line.gsub(/[`'"]/, "")
|
165
|
-
end
|
166
|
-
|
167
|
-
if btlines.length > 0
|
168
|
-
squish = { :site => btlines.join('\n') }
|
169
|
-
log(logdata.merge(excepdata.merge(squish)))
|
170
|
-
end
|
171
|
-
else
|
172
|
-
log(logdata.merge(excepdata))
|
173
|
-
|
174
|
-
e.backtrace.each do |line|
|
175
|
-
log(logdata.merge(excepdata).merge(
|
176
|
-
:at => "exception",
|
177
|
-
:class => e.class,
|
178
|
-
:exception_id => e.object_id.abs,
|
179
|
-
:site => line.gsub(/[`'"]/, "")
|
180
|
-
))
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
def with_context(prefix)
|
187
|
-
return unless block_given?
|
188
|
-
old = context
|
189
|
-
self.context = old.merge(prefix)
|
190
|
-
res = yield if block_given?
|
191
|
-
ensure
|
192
|
-
self.context = old
|
193
|
-
res
|
194
|
-
end
|
195
|
-
|
196
|
-
private
|
197
|
-
|
198
|
-
def get_global_context
|
199
|
-
default_global_context unless @global_context
|
200
|
-
@global_context.value
|
201
|
-
end
|
202
|
-
|
203
|
-
def set_global_context(data=nil)
|
204
|
-
default_global_context unless @global_context
|
205
|
-
@global_context.update { |_| data }
|
206
|
-
end
|
207
|
-
|
208
|
-
def default_global_context
|
209
|
-
@global_context = Atomic.new({})
|
210
|
-
end
|
211
|
-
|
212
|
-
def set_time_unit(u=nil)
|
213
|
-
unless ["ms","milli","milliseconds","s","seconds"].include?(u)
|
214
|
-
raise TimeUnitError, "Specify only 'seconds' or 'milliseconds'"
|
215
|
-
end
|
216
|
-
|
217
|
-
if ["ms", "milli", "milliseconds", 1000].include?(u)
|
218
|
-
@tunit = "milliseconds"
|
219
|
-
@t = 1000.0
|
220
|
-
else
|
221
|
-
default_time_unit
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def default_time_unit
|
226
|
-
@t = 1.0
|
227
|
-
@tunit = "seconds"
|
228
|
-
end
|
229
|
-
|
230
|
-
def calc_time(start, finish)
|
231
|
-
default_time_unit unless @t
|
232
|
-
((finish - start).to_f * @t)
|
233
|
-
end
|
234
|
-
|
235
|
-
def sync_stream(out = STDOUT)
|
236
|
-
IOLog.new(out)
|
237
|
-
end
|
238
|
-
|
239
|
-
def write(data)
|
240
|
-
if log_level_ok?(data[:level])
|
241
|
-
msg = unparse(data)
|
242
|
-
stream.log(msg)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def log_level_ok?(level)
|
247
|
-
if level
|
248
|
-
LOG_LEVEL_MAP[level.to_s] <= LOG_LEVEL
|
249
|
-
else
|
250
|
-
true
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def progname
|
255
|
-
File.basename($0)
|
256
|
-
end
|
257
|
-
|
258
|
-
def default_log_facility
|
259
|
-
LOG_FACILITY
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
data/lib/scrolls/syslog.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'syslog'
|
2
|
-
|
3
|
-
module Scrolls
|
4
|
-
|
5
|
-
LOG_FACILITY = ENV['LOG_FACILITY'] || Syslog::LOG_USER
|
6
|
-
LOG_FACILITY_MAP = {
|
7
|
-
"auth" => Syslog::LOG_AUTH,
|
8
|
-
"authpriv" => Syslog::LOG_AUTHPRIV,
|
9
|
-
"cron" => Syslog::LOG_CRON,
|
10
|
-
"daemon" => Syslog::LOG_DAEMON,
|
11
|
-
"ftp" => Syslog::LOG_FTP,
|
12
|
-
"kern" => Syslog::LOG_KERN,
|
13
|
-
"mail" => Syslog::LOG_MAIL,
|
14
|
-
"news" => Syslog::LOG_NEWS,
|
15
|
-
"syslog" => Syslog::LOG_SYSLOG,
|
16
|
-
"user" => Syslog::LOG_USER,
|
17
|
-
"uucp" => Syslog::LOG_UUCP,
|
18
|
-
"local0" => Syslog::LOG_LOCAL0,
|
19
|
-
"local1" => Syslog::LOG_LOCAL1,
|
20
|
-
"local2" => Syslog::LOG_LOCAL2,
|
21
|
-
"local3" => Syslog::LOG_LOCAL3,
|
22
|
-
"local4" => Syslog::LOG_LOCAL4,
|
23
|
-
"local5" => Syslog::LOG_LOCAL5,
|
24
|
-
"local6" => Syslog::LOG_LOCAL6,
|
25
|
-
"local7" => Syslog::LOG_LOCAL7,
|
26
|
-
}
|
27
|
-
|
28
|
-
class SyslogLogger
|
29
|
-
def initialize(ident = 'scrolls', facility = Syslog::LOG_USER)
|
30
|
-
options = Syslog::LOG_PID|Syslog::LOG_CONS
|
31
|
-
if Syslog.opened?
|
32
|
-
@syslog = Syslog.reopen(ident, options, facility)
|
33
|
-
else
|
34
|
-
@syslog = Syslog.open(ident, options, facility)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def log(data)
|
39
|
-
@syslog.log(Syslog::LOG_INFO, "%s", data)
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.opened?
|
43
|
-
Syslog.opened?
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|