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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b78c2d850fe048ad6f192be59e56b6d04daee1c5
4
- data.tar.gz: 49934f36ea64c5fe6a8a1b44ecb5689ea4574a75
3
+ metadata.gz: 0beda1c02c56ef85e12ea9a241e4a2077d47455d
4
+ data.tar.gz: 60228ade9fab4786baafc08e186b9fb3de509a3e
5
5
  SHA512:
6
- metadata.gz: 9d05994bb1dfdd62a76ad372a56ac83270fe8853e19f5f402d86031e90b22eec8ff09f26d3208efe91e392a6b2d6391a56c8236eb3b6d29390183cbd8abf58ef
7
- data.tar.gz: c45dca818ec283f03f238de1201d5ea70198375b55a458e5a1441f2f8fa069f85198894cd11827496541084cd403ae356b6b9ce40376a499876f72be44f183fc
6
+ metadata.gz: 913819999b29cb0c5d039440340260b38ea92ff9c1877260946dc2129fc3f9d6df66643935403bc4fc3ea743f2512e7025e0231ffe7e950d51f83002d8cd377d
7
+ data.tar.gz: 7163d0841cd810a1741e2e333cf62b8cabd1c512da443cd16d10fb4f4f4b02c73f8e10248f58c5c98ac0324f573056521766b0770276f501159ebc212c22f695
data/Gemfile CHANGED
@@ -4,5 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
+ gem "minitest", require: 'minitest/autorun'
8
+ gem "minitest-reporters"
7
9
  gem "rake"
8
10
  end
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
- ## Need to know!
23
+ ## Usage
24
+
25
+ ### 0.9.0 and later
24
26
 
25
- The way Scrolls handles "global_context" is changing after v0.3.8. Please see the [release notes](https://github.com/asenchi/scrolls/releases/tag/v0.3.8) and [this documentation](https://github.com/asenchi/scrolls/tree/master/docs/global-context.md) for more information. I apologize for any trouble this may cause.
27
+ ```ruby
28
+ require 'scrolls'
26
29
 
27
- ## Documentation:
30
+ Scrolls.init(
31
+ timestamp: true,
32
+ global_context: {app: "scrolls", deploy: "production"},
33
+ exceptions: "multi"
34
+ )
28
35
 
29
- I apologize, some of these are a WIP.
36
+ Scrolls.log(at: "test")
30
37
 
31
- * [Sending logs to syslog using Scrolls](https://github.com/asenchi/scrolls/tree/master/docs/syslog.md)
32
- * Logging contexts
33
- * Adding timestamps by default
34
- * Misc Features
38
+ Scrolls.context(context: "block") do
39
+ Scrolls.log(at: "exec")
40
+ end
35
41
 
36
- ## Usage
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 => ENV["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="2014-01-17T16:11:39Z" app=scrolls deploy=nil at=test
61
- now="2014-01-17T16:11:39Z" app=scrolls deploy=nil context=block at=exec
62
- now="2014-01-17T16:11:39Z" app=scrolls deploy=nil at=exception class=RuntimeError message= exception_id=70312608019740
63
- now="2014-01-17T16:11:39Z" app=scrolls deploy=nil at=exception class= exception_id=70312608019740 site="./test.rb:16:in <main>"
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.pattern = "test/test_*.rb"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ t.verbose = true
9
11
  end
10
12
 
11
13
  task :default => :test
@@ -38,7 +38,7 @@ Scrolls.init(
38
38
  Scrolls.log(:t => "t")
39
39
  ```
40
40
 
41
- Is the same as this currently:
41
+ Is the same as this in versions prior to 0.9.0:
42
42
 
43
43
  ```ruby
44
44
  Scrolls.global_context(:g => "g")
@@ -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`. This allows this particular feature to be rather fluid throughout your application.
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
+ ```
@@ -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
- @log = Log.new(options)
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
 
@@ -1,5 +1,5 @@
1
1
  module Scrolls
2
- class IOLog
2
+ class IOLogger
3
3
  def initialize(stream)
4
4
  if stream.respond_to?(:sync)
5
5
  stream.sync = true
@@ -1,100 +1,102 @@
1
+ require "syslog"
2
+
1
3
  require "scrolls/parser"
2
- require "scrolls/iolog"
3
- require "scrolls/syslog"
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
- attr_reader :context
10
- def initialize(context)
11
- @context = context || {}
25
+ def initialize(ctx)
26
+ @ctx = ctx || {}
12
27
  end
13
28
 
14
29
  def to_h
15
- @context
30
+ @ctx
16
31
  end
17
32
  end
18
33
 
19
- class Log
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 = options.fetch(:stream, sync_stream)
60
- @facility = options.fetch(:facility, LOG_FACILITY)
61
- @time_unit = options.fetch(:time_unit, "seconds")
62
- @timestamp = options.fetch(:timestamp, false)
63
- @exceptions = options.fetch(:exceptions, "single")
64
- @global_ctx = options.fetch(:global_context, {})
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[:scrolls_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
71
59
  end
72
60
 
73
61
  def context=(h)
74
- Thread.current[:scrolls_context] = h
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=(stream)
69
+ def stream=(s)
82
70
  # Return early to avoid setup
83
- return if stream == @stream
71
+ return if s == @stream
84
72
 
85
- @stream = 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 ||= LOG_FACILITY
86
+ @facility
91
87
  end
92
88
 
93
89
  def facility=(f)
94
90
  if f
95
- @facility = LOG_FACILITY_MAP[f]
96
- # Assume we are using syslog and set it up again
97
- @logger = Scrolls::SyslogLogger.new(progname, facility)
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
- translate_time_unit
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
- log(logdata.merge(
142
- :at => "exception",
143
- :reraise => true,
144
- :class => e.class,
145
- :message => e.message,
146
- :exception_id => e.object_id.abs,
147
- :elapsed => calculate_time(start, Time.now)
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(data, e)
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
- # If we get a string lets bring it into our structure.
163
- if data.kind_of? String
166
+ # We check our arguments for type
167
+ case data
168
+ when String
164
169
  rawhash = { "log_message" => data }
165
- else
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
- :at => "exception",
175
- :class => e.class,
176
- :message => e.message,
177
- :exception_id => e.object_id.abs
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
- build_global_context
225
+ setup_global_context
217
226
  prepend_timestamp?
227
+ setup_facility
218
228
  setup_stream
219
229
  single_line_exceptions?
220
- translate_time_unit
230
+ setup_time_unit
221
231
  end
222
232
 
223
- def build_global_context
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(progname, facility)
261
+ @logger = Scrolls::SyslogLogger.new(
262
+ progname,
263
+ syslog_options,
264
+ facility
265
+ )
243
266
  else
244
- @logger = sync_stream(@stream)
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 translate_time_unit
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
 
@@ -4,8 +4,10 @@ module Scrolls
4
4
  module Parser
5
5
  extend self
6
6
 
7
- def unparse(data)
7
+ def unparse(data, escape_keys=false)
8
8
  data.map do |(k,v)|
9
+ k = Scrolls::Utils.escape_chars(k) if escape_keys
10
+
9
11
  if (v == true)
10
12
  "#{k}=true"
11
13
  elsif (v == false)
@@ -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
@@ -1,19 +1,61 @@
1
1
  module Scrolls
2
- module Utils
3
2
 
4
- def hashify(d)
5
- last = d.pop
6
- return {} unless last
7
- return hashified_list(d).merge(last) if last.is_a?(Hash)
8
- d.push(last)
9
- hashified_list(d)
10
- end
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
+ "&" => "&amp;",
43
+ "<" => "&lt;",
44
+ ">" => "&gt;",
45
+ "'" => "&#x27;",
46
+ '"' => "&quot;",
47
+ "/" => "&#x2F;"
48
+ }
49
+
50
+ ESCAPE_CHAR_PATTERN = Regexp.union(*ESCAPE_CHAR.keys)
51
+
52
+ module Utils
11
53
 
12
- def hashified_list(l)
13
- return {} if l.empty?
14
- l.inject({}) do |h, i|
15
- h[i.to_sym] = true
16
- h
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
 
@@ -1,3 +1,3 @@
1
1
  module Scrolls
2
- VERSION = "0.9.0.pre"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -1,6 +1,9 @@
1
- require "test/unit"
1
+ require "minitest/autorun"
2
+ require "minitest/reporters"
2
3
  require "stringio"
3
4
 
5
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
6
+
4
7
  $: << File.expand_path("../../lib", __FILE__)
5
8
 
6
9
  require "scrolls"
@@ -1,6 +1,6 @@
1
1
  require File.expand_path("../test_helper", __FILE__)
2
2
 
3
- class TestScrollsParser < Test::Unit::TestCase
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 '&lt;p&gt;p&lt;&#x2F;p&gt;=d p&#x2F;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"
@@ -1,6 +1,6 @@
1
1
  require File.expand_path("../test_helper", __FILE__)
2
2
 
3
- class TestScrolls < Test::Unit::TestCase
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::IOLog, Scrolls.stream.class
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
- assert_raise Scrolls::TimeUnitError do
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"}, e)
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"}, e)
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.pre
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-08-23 00:00:00.000000000 Z
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/iolog.rb
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/syslog.rb
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: 1.3.1
55
+ version: '0'
57
56
  requirements: []
58
57
  rubyforge_project:
59
- rubygems_version: 2.6.11
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.
@@ -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
@@ -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