scrolls 0.3.7 → 0.9.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.
- checksums.yaml +5 -5
- 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 +62 -83
- data/lib/scrolls/iologger.rb +14 -0
- data/lib/scrolls/logger.rb +318 -0
- 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 +11 -2
- data/test/test_scrolls.rb +78 -41
- metadata +6 -9
- data/lib/scrolls/atomic.rb +0 -59
- data/lib/scrolls/log.rb +0 -274
- data/lib/scrolls/syslog.rb +0 -46
- data/test/test_atomic.rb +0 -33
@@ -0,0 +1,318 @@
|
|
1
|
+
require "syslog"
|
2
|
+
|
3
|
+
require "scrolls/parser"
|
4
|
+
require "scrolls/iologger"
|
5
|
+
require "scrolls/sysloglogger"
|
6
|
+
require "scrolls/utils"
|
7
|
+
|
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
|
+
|
18
|
+
class TimeUnitError < RuntimeError; end
|
19
|
+
class LogLevelError < StandardError; end
|
20
|
+
|
21
|
+
# Top level class to hold our global context
|
22
|
+
#
|
23
|
+
# Global context is defined using Scrolls#init
|
24
|
+
class GlobalContext
|
25
|
+
def initialize(ctx)
|
26
|
+
@ctx = ctx || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
@ctx
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Logger
|
35
|
+
|
36
|
+
attr_reader :logger
|
37
|
+
attr_accessor :exceptions, :timestamp
|
38
|
+
|
39
|
+
def initialize(options={})
|
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
|
50
|
+
setup!
|
51
|
+
end
|
52
|
+
|
53
|
+
def context
|
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
|
59
|
+
end
|
60
|
+
|
61
|
+
def context=(h)
|
62
|
+
Thread.current.thread_variable_set(:scrolls_context, h || {})
|
63
|
+
end
|
64
|
+
|
65
|
+
def stream
|
66
|
+
@stream
|
67
|
+
end
|
68
|
+
|
69
|
+
def stream=(s)
|
70
|
+
# Return early to avoid setup
|
71
|
+
return if s == @stream
|
72
|
+
|
73
|
+
@stream = s
|
74
|
+
setup_stream
|
75
|
+
end
|
76
|
+
|
77
|
+
def escape_keys?
|
78
|
+
@escape_keys
|
79
|
+
end
|
80
|
+
|
81
|
+
def syslog_options
|
82
|
+
@syslog_opts
|
83
|
+
end
|
84
|
+
|
85
|
+
def facility
|
86
|
+
@facility
|
87
|
+
end
|
88
|
+
|
89
|
+
def facility=(f)
|
90
|
+
if f
|
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
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def time_unit
|
104
|
+
@time_unit
|
105
|
+
end
|
106
|
+
|
107
|
+
def time_unit=(u)
|
108
|
+
@time_unit = u
|
109
|
+
setup_time_unit
|
110
|
+
end
|
111
|
+
|
112
|
+
def global_context
|
113
|
+
@global_context.to_h
|
114
|
+
end
|
115
|
+
|
116
|
+
def log(data, &blk)
|
117
|
+
# If we get a string lets bring it into our structure.
|
118
|
+
if data.kind_of? String
|
119
|
+
rawhash = { "log_message" => data }
|
120
|
+
else
|
121
|
+
rawhash = data
|
122
|
+
end
|
123
|
+
|
124
|
+
if gc = @global_context.to_h
|
125
|
+
ctx = gc.merge(context)
|
126
|
+
logdata = ctx.merge(rawhash)
|
127
|
+
end
|
128
|
+
|
129
|
+
# By merging the logdata into the timestamp, rather than vice-versa, we
|
130
|
+
# ensure that the timestamp comes first in the Hash, and is placed first
|
131
|
+
# on the output, which helps with readability.
|
132
|
+
logdata = { :now => Time.now.utc }.merge(logdata) if prepend_timestamp?
|
133
|
+
|
134
|
+
unless blk
|
135
|
+
write(logdata)
|
136
|
+
else
|
137
|
+
start = Time.now
|
138
|
+
res = nil
|
139
|
+
log(logdata.merge(:at => "start"))
|
140
|
+
begin
|
141
|
+
res = yield
|
142
|
+
rescue StandardError => e
|
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)
|
153
|
+
raise e
|
154
|
+
end
|
155
|
+
log(logdata.merge(:at => "finish", :elapsed => calculate_time(start, Time.now)))
|
156
|
+
res
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def log_exception(e, data=nil)
|
161
|
+
unless @defined
|
162
|
+
@stream = STDERR
|
163
|
+
setup_stream
|
164
|
+
end
|
165
|
+
|
166
|
+
# We check our arguments for type
|
167
|
+
case data
|
168
|
+
when String
|
169
|
+
rawhash = { "log_message" => data }
|
170
|
+
when Hash
|
171
|
+
rawhash = data
|
172
|
+
else
|
173
|
+
rawhash = {}
|
174
|
+
end
|
175
|
+
|
176
|
+
if gc = @global_context.to_h
|
177
|
+
logdata = gc.merge(rawhash)
|
178
|
+
end
|
179
|
+
|
180
|
+
excepdata = {
|
181
|
+
at: "exception",
|
182
|
+
class: e.class,
|
183
|
+
message: e.message,
|
184
|
+
exception_id: e.object_id.abs
|
185
|
+
}
|
186
|
+
|
187
|
+
excepdata.delete_if { |k,v| k if v == "" }
|
188
|
+
|
189
|
+
if e.backtrace
|
190
|
+
if single_line_exceptions?
|
191
|
+
lines = e.backtrace.map { |line| line.gsub(/[`'"]/, "") }
|
192
|
+
|
193
|
+
if lines.length > 0
|
194
|
+
excepdata[:site] = lines.join('\n')
|
195
|
+
log(logdata.merge(excepdata))
|
196
|
+
end
|
197
|
+
else
|
198
|
+
log(logdata.merge(excepdata))
|
199
|
+
|
200
|
+
e.backtrace.each do |line|
|
201
|
+
log(logdata.merge(excepdata).merge(
|
202
|
+
:at => "exception",
|
203
|
+
:class => e.class,
|
204
|
+
:exception_id => e.object_id.abs,
|
205
|
+
:site => line.gsub(/[`'"]/, "")
|
206
|
+
))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def with_context(prefix)
|
213
|
+
return unless block_given?
|
214
|
+
old = context
|
215
|
+
self.context = old.merge(prefix)
|
216
|
+
res = yield if block_given?
|
217
|
+
ensure
|
218
|
+
self.context = old
|
219
|
+
res
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def setup!
|
225
|
+
setup_global_context
|
226
|
+
prepend_timestamp?
|
227
|
+
setup_facility
|
228
|
+
setup_stream
|
229
|
+
single_line_exceptions?
|
230
|
+
setup_time_unit
|
231
|
+
end
|
232
|
+
|
233
|
+
def setup_global_context
|
234
|
+
# Builds up an immutable object for our global_context
|
235
|
+
# This is not backwards compatiable and was introduced after 0.3.7.
|
236
|
+
# Removes ability to add to global context once we initialize our
|
237
|
+
# logging object. This also deprecates #add_global_context.
|
238
|
+
@global_context = GlobalContext.new(@global_ctx)
|
239
|
+
@global_context.freeze
|
240
|
+
end
|
241
|
+
|
242
|
+
def prepend_timestamp?
|
243
|
+
@timestamp
|
244
|
+
end
|
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
|
+
|
254
|
+
def setup_stream
|
255
|
+
unless @stream == STDOUT
|
256
|
+
# Set this so we know we aren't using our default stream
|
257
|
+
@defined = true
|
258
|
+
end
|
259
|
+
|
260
|
+
if @stream == "syslog"
|
261
|
+
@logger = Scrolls::SyslogLogger.new(
|
262
|
+
progname,
|
263
|
+
syslog_options,
|
264
|
+
facility
|
265
|
+
)
|
266
|
+
else
|
267
|
+
@logger = IOLogger.new(@stream)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def single_line_exceptions?
|
272
|
+
return false if @exceptions == "multi"
|
273
|
+
true
|
274
|
+
end
|
275
|
+
|
276
|
+
def setup_time_unit
|
277
|
+
unless %w{s ms seconds milliseconds}.include? @time_unit
|
278
|
+
raise TimeUnitError, "Specify the following: s, ms, seconds, milliseconds"
|
279
|
+
end
|
280
|
+
|
281
|
+
case @time_unit
|
282
|
+
when %w{s seconds}
|
283
|
+
@t = 1.0
|
284
|
+
when %w{ms milliseconds}
|
285
|
+
@t = 1000.0
|
286
|
+
else
|
287
|
+
@t = 1.0
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# We need this for our syslog setup
|
292
|
+
def progname
|
293
|
+
File.basename($0)
|
294
|
+
end
|
295
|
+
|
296
|
+
def calculate_time(start, finish)
|
297
|
+
translate_time_unit unless @t
|
298
|
+
((finish - start).to_f * @t)
|
299
|
+
end
|
300
|
+
|
301
|
+
def log_level_ok?(level)
|
302
|
+
if level
|
303
|
+
raise LogLevelError, "Log level unknown" unless LOG_LEVEL_MAP.key?(level)
|
304
|
+
LOG_LEVEL_MAP[level.to_s] <= LOG_LEVEL
|
305
|
+
else
|
306
|
+
true
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def write(data)
|
311
|
+
if log_level_ok?(data[:level])
|
312
|
+
msg = Scrolls::Parser.unparse(data, escape_keys=escape_keys?)
|
313
|
+
@logger.log(msg)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
end
|
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