lines 0.1.27 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +17 -0
- data/Gemfile +2 -2
- data/README.md +77 -2
- data/lib/lines.rb +118 -67
- data/lib/lines/active_record.rb +1 -0
- data/lib/lines/loader.rb +212 -149
- data/lib/lines/version.rb +1 -1
- data/spec/bench.rb +29 -39
- data/spec/lines_loader_spec.rb +49 -55
- data/spec/lines_spec.rb +24 -19
- data/spec/parse-bench +26 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZGM0YmFkODI1ZDgwOTA1NTQ1Y2JmZjM5ZDE2ZjIxMzAxM2JhNWZhZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YzY3M2FhMGEyNmExZGEyODZiMDIyMDgzOWQ4YTQ4NThiMzM2NDI3NQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTI3YzJhY2M5YjMzZDE2NWI3NjdjYmE0MDZjOWU0MDg1MjY0ZTY2NmYzYThh
|
10
|
+
ZjkxNTQ4ZjZjMzQ0OGRiMTZmNTc5ZDhmYjI3ODIwMDg4MDg0NTFmYWE4OWU3
|
11
|
+
MmUyYzgxOTU0MjQ0N2NhNDFmNThkZmU3Y2NjOWMyZWUxMWIwMmU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Njc3MjM5ZjJmMmY0MWE4OWM1ZmFlYmUyNzdlMmQ0OWFmZTVmZTllZjc0YWRk
|
14
|
+
ZDczMjM4YmEwYTMwYTU1OTU5Y2E1OWIwNjAzMGNiYTc1NTdiNGJhNWUzZjE5
|
15
|
+
ZjhiNDlhYWU1ZGM4MTBmZTQ3YmY4ZmQyNjMxOWU5NDMyZjZiMWU=
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,21 @@
|
|
1
1
|
|
2
|
+
0.2.0 / 2013-07-15
|
3
|
+
==================
|
4
|
+
|
5
|
+
* Use benchmark-ips for benchs
|
6
|
+
* Fixes serialization of Date objects
|
7
|
+
* Lines now outputs to $stderr by default.
|
8
|
+
* Lines.use resets the global context.
|
9
|
+
* Improved the doc
|
10
|
+
* Lines.log now returns nil instead of the logged object.
|
11
|
+
* Support parsing lines that end with \r\n or spaces
|
12
|
+
* Add Lines.load and Lines.dump for JSON-like functionality
|
13
|
+
* Introduced a hand-written parser that performs 200x faster
|
14
|
+
* Differentiate units with a : sign to ensure their parsability
|
15
|
+
* Escape strings that contain an equal sign
|
16
|
+
* Change the default max_depth from 3 to 4
|
17
|
+
* Make sure ActiveRecord's log subscriber is loaded
|
18
|
+
|
2
19
|
0.1.27 / 2013-07-10
|
3
20
|
===================
|
4
21
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -6,10 +6,85 @@ Status](https://travis-ci.org/zimbatm/lines-ruby.png)](https://travis-ci.org/zim
|
|
6
6
|
An oppinionated logging library that implement the
|
7
7
|
[lines](https://github.com/zimbatm/lines) format.
|
8
8
|
|
9
|
-
|
9
|
+
* Log everything in development AND production.
|
10
|
+
* Logs should be easy to read, grep and parse.
|
11
|
+
* Logging something should never fail.
|
12
|
+
* Let the system handle the storage. Write to syslog or STDERR.
|
13
|
+
* No log levels necessary. Just log whatever you want.
|
10
14
|
|
11
|
-
|
15
|
+
STATUS: WORK IN PROGRESS
|
16
|
+
========================
|
17
|
+
|
18
|
+
Doc is still scarce so it's quite hard to get started. I think reading the
|
19
|
+
lib/lines.rb should give a good idea of the capabilities.
|
20
|
+
|
21
|
+
Lines.id is a unique ID generator that seems quite handy but I'm not sure if
|
22
|
+
it should be part of the lib or not.
|
23
|
+
|
24
|
+
It would be nice to expose a method that resolves a context into a hash. It's
|
25
|
+
useful to share the context with other tools like an error reporter. Btw,
|
26
|
+
Sentry/Raven is great.
|
27
|
+
|
28
|
+
There is a parser in the lib but no credible direct consumption path.
|
29
|
+
|
30
|
+
Quick intro
|
31
|
+
-----------
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'lines'
|
35
|
+
|
36
|
+
# Setups the outputs. IO and Syslog are supported.
|
37
|
+
Lines.use($stdout, Syslog)
|
38
|
+
|
39
|
+
# All lines will be prefixed by the global context
|
40
|
+
Lines.global['at'] = proc{ Time.now }
|
41
|
+
|
42
|
+
# First example
|
43
|
+
Lines.log(foo: 'bar') # logs: at=2013-07-14T14:19:28Z foo=bar
|
44
|
+
|
45
|
+
# If not a hash, the argument is transformed. A second argument is accepted as
|
46
|
+
# a hash
|
47
|
+
Lines.log("Hey", count: 3) # logs: at=2013-07-14T14:19:28Z msg=Hey count=3
|
48
|
+
|
49
|
+
# You can also keep a context
|
50
|
+
class MyClass < ActiveRecord::Base
|
51
|
+
attr_reader :lines
|
52
|
+
def initialize
|
53
|
+
@lines = Lines.context(my_class_id: self.id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_something
|
57
|
+
lines.log("Something happened")
|
58
|
+
# logs: at=2013-07-14T14:19:28Z msg='Something happeend' my_class_id: 2324
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
Features
|
12
64
|
--------
|
13
65
|
|
66
|
+
* Simple to use
|
67
|
+
* Thread safe (if the IO#write is)
|
68
|
+
* Designed to not raise exceptions (unless it's an IO issue)
|
69
|
+
* Lines.logger is a backward-compatible Logger in case you want to retrofit
|
70
|
+
* require "lines/active_record" for sane ActiveRecord logs
|
71
|
+
* "lines/rack_logger" is a logging middleware for Rack
|
72
|
+
* Lines.load and Lines.dump to parse and generate 'lines'
|
73
|
+
|
74
|
+
There's also a fork of lograge that you can use with Rails. See
|
75
|
+
https://github.com/zimbatm/lograge/tree/lines-output
|
76
|
+
|
77
|
+
Known issues
|
78
|
+
------------
|
79
|
+
|
80
|
+
Syslog seems to truncate lines longer than 2056 chars and Lines makes if very
|
81
|
+
easy to put too much data.
|
82
|
+
|
83
|
+
Lines logging speed is reasonable but it could be faster. It writes at around
|
84
|
+
5000 lines per second to Syslog on my machine.
|
85
|
+
|
86
|
+
Inspired by
|
87
|
+
-----------
|
88
|
+
|
14
89
|
* Scrolls : https://github.com/asenchi/scrolls
|
15
90
|
* Lograge : https://github.com/roidrage/lograge
|
data/lib/lines.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
require 'date'
|
2
2
|
require 'time'
|
3
|
-
require 'forwardable'
|
4
3
|
|
5
|
-
# Lines is an opinionated structured log format and a library
|
6
|
-
# inspired by Slogger.
|
4
|
+
# Lines is an opinionated structured log format and a library.
|
7
5
|
#
|
8
|
-
# Don't use log levels. They limit the reasoning of the developer.
|
9
6
|
# Log everything in development AND production.
|
10
7
|
# Logs should be easy to read, grep and parse.
|
11
8
|
# Logging something should never fail.
|
12
|
-
#
|
9
|
+
# Let the system handle the storage. Write to syslog or STDERR.
|
10
|
+
# No log levels necessary. Just log whatever you want.
|
13
11
|
#
|
14
12
|
# Example:
|
15
13
|
#
|
16
|
-
# log(
|
14
|
+
# log("Oops !", foo: {}, g: [])
|
17
15
|
# #outputs:
|
18
16
|
# # at=2013-03-07T09:21:39+00:00 pid=3242 app=some-process msg="Oops !" foo={} g=[]
|
19
17
|
#
|
@@ -25,40 +23,57 @@ require 'forwardable'
|
|
25
23
|
# ctx = Lines.context(encoding_id: Log.id)
|
26
24
|
# ctx.log({})
|
27
25
|
#
|
28
|
-
# Lines.context(:
|
29
|
-
# l.log(:
|
26
|
+
# Lines.context(foo: 'bar') do |l|
|
27
|
+
# l.log(items_count: 3)
|
30
28
|
# end
|
31
29
|
module Lines
|
32
|
-
|
33
|
-
|
30
|
+
class << self
|
31
|
+
attr_reader :global
|
32
|
+
attr_writer :loader, :dumper
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
# Parsing object. Responds to #load(string)
|
35
|
+
def loader
|
36
|
+
@loader ||= (
|
37
|
+
require 'lines/loader'
|
38
|
+
Loader
|
39
|
+
)
|
40
|
+
end
|
37
41
|
|
38
|
-
|
42
|
+
# Serializing object. Responds to #dump(hash)
|
39
43
|
def dumper; @dumper ||= Dumper.new end
|
40
|
-
attr_reader :global
|
41
|
-
attr_reader :outputters
|
42
44
|
|
43
|
-
#
|
45
|
+
# Returns a backward-compatibile Logger
|
46
|
+
def logger
|
47
|
+
@logger ||= (
|
48
|
+
require 'lines/logger'
|
49
|
+
Logger.new(self)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Used to configure lines.
|
44
54
|
#
|
45
55
|
# outputs - allows any kind of IO or Syslog
|
46
56
|
#
|
47
57
|
# Usage:
|
48
58
|
#
|
49
|
-
# Lines.use(Syslog, $stderr)
|
59
|
+
# Lines.use(Syslog, $stderr, at: proc{ Time.now })
|
50
60
|
def use(*outputs)
|
51
|
-
|
61
|
+
if outputs.last.kind_of?(Hash)
|
62
|
+
@global = outputs.pop
|
63
|
+
else
|
64
|
+
@global = {}
|
65
|
+
end
|
66
|
+
@outputters = outputs.flatten.map{|o| to_outputter o}
|
52
67
|
end
|
53
68
|
|
54
69
|
# The main function. Used to record objects in the logs as lines.
|
55
70
|
#
|
56
|
-
# obj - a ruby hash
|
57
|
-
# args -
|
71
|
+
# obj - a ruby hash. coerced to +{"msg"=>obj}+ otherwise
|
72
|
+
# args - complementary values to put in the line
|
58
73
|
def log(obj, args={})
|
59
74
|
obj = prepare_obj(obj, args)
|
60
|
-
outputters.each{|out| out.output(dumper, obj) }
|
61
|
-
|
75
|
+
@outputters.each{|out| out.output(dumper, obj) }
|
76
|
+
nil
|
62
77
|
end
|
63
78
|
|
64
79
|
# Add data to the logs
|
@@ -72,19 +87,21 @@ module Lines
|
|
72
87
|
new_context
|
73
88
|
end
|
74
89
|
|
75
|
-
# Returns a backward-compatibile logger
|
76
|
-
def logger
|
77
|
-
@logger ||= (
|
78
|
-
require 'lines/logger'
|
79
|
-
Logger.new(self)
|
80
|
-
)
|
81
|
-
end
|
82
|
-
|
83
90
|
def ensure_hash!(obj) # :nodoc:
|
84
91
|
return {} unless obj
|
85
92
|
return obj if obj.kind_of?(Hash)
|
86
93
|
return obj.to_h if obj.respond_to?(:to_h)
|
87
|
-
|
94
|
+
{msg: obj}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Parses a lines-formatted string
|
98
|
+
def load(string)
|
99
|
+
loader.load(string)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Generates a lines-formatted string from the given object
|
103
|
+
def dump(obj)
|
104
|
+
dumper.dump ensure_hash!(obj)
|
88
105
|
end
|
89
106
|
|
90
107
|
protected
|
@@ -118,7 +135,7 @@ module Lines
|
|
118
135
|
end
|
119
136
|
end
|
120
137
|
|
121
|
-
# Wrapper object that holds a given context. Emitted by Lines.
|
138
|
+
# Wrapper object that holds a given context. Emitted by Lines.context
|
122
139
|
class Context
|
123
140
|
attr_reader :data
|
124
141
|
|
@@ -126,12 +143,16 @@ module Lines
|
|
126
143
|
@data = data
|
127
144
|
end
|
128
145
|
|
146
|
+
# Works like the Lines.log method.
|
129
147
|
def log(obj, args={})
|
130
148
|
Lines.log obj, Lines.ensure_hash!(args).merge(data)
|
131
149
|
end
|
132
150
|
end
|
133
151
|
|
152
|
+
# Handles output to any kind of IO
|
134
153
|
class StreamOutputter
|
154
|
+
NL = "\n".freeze
|
155
|
+
|
135
156
|
# stream must accept a #write(str) message
|
136
157
|
def initialize(stream = $stderr)
|
137
158
|
@stream = stream
|
@@ -141,28 +162,25 @@ module Lines
|
|
141
162
|
|
142
163
|
def output(dumper, obj)
|
143
164
|
str = dumper.dump(obj) + NL
|
144
|
-
stream.write str
|
165
|
+
@stream.write str
|
145
166
|
end
|
146
|
-
|
147
|
-
protected
|
148
|
-
|
149
|
-
attr_reader :stream
|
150
167
|
end
|
151
168
|
|
152
169
|
require 'syslog'
|
170
|
+
# Handles output to syslog
|
153
171
|
class SyslogOutputter
|
154
172
|
PRI2SYSLOG = {
|
155
|
-
'debug' => Syslog::LOG_DEBUG,
|
156
|
-
'info' => Syslog::LOG_INFO,
|
157
|
-
'warn' => Syslog::LOG_WARNING,
|
158
|
-
'warning' => Syslog::LOG_WARNING,
|
159
|
-
'err' => Syslog::LOG_ERR,
|
160
|
-
'error' => Syslog::LOG_ERR,
|
161
|
-
'crit' => Syslog::LOG_CRIT,
|
162
|
-
'critical' => Syslog::LOG_CRIT,
|
163
|
-
}
|
164
|
-
|
165
|
-
def initialize(syslog = Syslog)
|
173
|
+
'debug' => ::Syslog::LOG_DEBUG,
|
174
|
+
'info' => ::Syslog::LOG_INFO,
|
175
|
+
'warn' => ::Syslog::LOG_WARNING,
|
176
|
+
'warning' => ::Syslog::LOG_WARNING,
|
177
|
+
'err' => ::Syslog::LOG_ERR,
|
178
|
+
'error' => ::Syslog::LOG_ERR,
|
179
|
+
'crit' => ::Syslog::LOG_CRIT,
|
180
|
+
'critical' => ::Syslog::LOG_CRIT,
|
181
|
+
}.freeze
|
182
|
+
|
183
|
+
def initialize(syslog = ::Syslog)
|
166
184
|
@syslog = syslog
|
167
185
|
end
|
168
186
|
|
@@ -175,9 +193,8 @@ module Lines
|
|
175
193
|
obj.delete(:app) # And again
|
176
194
|
|
177
195
|
level = extract_pri(obj)
|
178
|
-
str = dumper.dump(obj)
|
179
196
|
|
180
|
-
@syslog.log(level, "%s",
|
197
|
+
@syslog.log(level, "%s", dumper.dump(obj))
|
181
198
|
end
|
182
199
|
|
183
200
|
protected
|
@@ -186,13 +203,13 @@ module Lines
|
|
186
203
|
return if @syslog.opened?
|
187
204
|
app_name ||= File.basename($0)
|
188
205
|
@syslog.open(app_name,
|
189
|
-
Syslog::LOG_PID | Syslog::LOG_CONS | Syslog::LOG_NDELAY,
|
190
|
-
Syslog::LOG_USER)
|
206
|
+
::Syslog::LOG_PID | ::Syslog::LOG_CONS | ::Syslog::LOG_NDELAY,
|
207
|
+
::Syslog::LOG_USER)
|
191
208
|
end
|
192
209
|
|
193
210
|
def extract_pri(h)
|
194
211
|
pri = h.delete(:pri).to_s.downcase
|
195
|
-
PRI2SYSLOG[pri] || Syslog::LOG_INFO
|
212
|
+
PRI2SYSLOG[pri] || ::Syslog::LOG_INFO
|
196
213
|
end
|
197
214
|
end
|
198
215
|
|
@@ -230,15 +247,41 @@ module Lines
|
|
230
247
|
# This dumper has been inspired by the OkJSON gem (both formats look alike
|
231
248
|
# after all).
|
232
249
|
class Dumper
|
250
|
+
SPACE = ' '
|
251
|
+
LIT_TRUE = '#t'
|
252
|
+
LIT_FALSE = '#f'
|
253
|
+
LIT_NIL = 'nil'
|
254
|
+
OPEN_BRACE = '{'
|
255
|
+
SHUT_BRACE = '}'
|
256
|
+
OPEN_BRACKET = '['
|
257
|
+
SHUT_BRACKET = ']'
|
258
|
+
SINGLE_QUOTE = "'"
|
259
|
+
DOUBLE_QUOTE = '"'
|
260
|
+
|
261
|
+
constants.each(&:freeze)
|
262
|
+
|
233
263
|
def dump(obj) #=> String
|
234
264
|
objenc_internal(obj)
|
235
265
|
end
|
236
266
|
|
237
267
|
# Used to introduce new ruby litterals.
|
268
|
+
#
|
269
|
+
# Usage:
|
270
|
+
#
|
271
|
+
# Point = Struct.new(:x, :y)
|
272
|
+
# Lines.dumper.map(Point) do |p|
|
273
|
+
# "#{p.x}x#{p.y}"
|
274
|
+
# end
|
275
|
+
#
|
276
|
+
# Lines.log msg: Point.new(3, 5)
|
277
|
+
# # logs: msg=3x5
|
278
|
+
#
|
238
279
|
def map(klass, &rule)
|
239
280
|
@mapping[klass] = rule
|
240
281
|
end
|
241
282
|
|
283
|
+
# After a certain depth, arrays are replaced with [...] and objects with
|
284
|
+
# {...}. Default is 4.
|
242
285
|
attr_accessor :max_depth
|
243
286
|
|
244
287
|
protected
|
@@ -247,7 +290,7 @@ module Lines
|
|
247
290
|
|
248
291
|
def initialize
|
249
292
|
@mapping = {}
|
250
|
-
@max_depth =
|
293
|
+
@max_depth = 4
|
251
294
|
end
|
252
295
|
|
253
296
|
def objenc_internal(x, depth=0)
|
@@ -255,7 +298,7 @@ module Lines
|
|
255
298
|
if depth > max_depth
|
256
299
|
'...'
|
257
300
|
else
|
258
|
-
x.map{|k,v| "#{keyenc(k)}=#{valenc(v, depth)}" }.join(
|
301
|
+
x.map{|k,v| "#{keyenc(k)}=#{valenc(v, depth)}" }.join(SPACE)
|
259
302
|
end
|
260
303
|
end
|
261
304
|
|
@@ -273,39 +316,40 @@ module Lines
|
|
273
316
|
when Array then arrenc(x, depth)
|
274
317
|
when String, Symbol then strenc(x)
|
275
318
|
when Numeric then numenc(x)
|
276
|
-
when Time
|
277
|
-
when
|
278
|
-
when
|
279
|
-
when
|
319
|
+
when Time then timeenc(x)
|
320
|
+
when Date then dateenc(x)
|
321
|
+
when true then LIT_TRUE
|
322
|
+
when false then LIT_FALSE
|
323
|
+
when nil then LIT_NIL
|
280
324
|
else
|
281
325
|
litenc(x)
|
282
326
|
end
|
283
327
|
end
|
284
328
|
|
285
329
|
def objenc(x, depth)
|
286
|
-
|
330
|
+
OPEN_BRACE + objenc_internal(x, depth) + SHUT_BRACE
|
287
331
|
end
|
288
332
|
|
289
333
|
def arrenc(a, depth)
|
290
334
|
depth += 1
|
291
335
|
# num + unit. Eg: 3ms
|
292
336
|
if a.size == 2 && a.first.kind_of?(Numeric) && is_literal?(a.last.to_s)
|
293
|
-
numenc(a.first)
|
337
|
+
"#{numenc(a.first)}:#{strenc(a.last)}"
|
294
338
|
elsif depth > max_depth
|
295
339
|
'[...]'
|
296
340
|
else
|
297
|
-
|
341
|
+
OPEN_BRACKET + a.map{|x| valenc(x, depth)}.join(' ') + SHUT_BRACKET
|
298
342
|
end
|
299
343
|
end
|
300
344
|
|
301
|
-
# TODO: Single-quote espace if possible
|
302
345
|
def strenc(s)
|
303
346
|
s = s.to_s
|
304
347
|
unless is_literal?(s)
|
305
348
|
s = s.inspect
|
306
|
-
unless s[1..-2].include?(
|
307
|
-
s
|
308
|
-
s.gsub!('\"',
|
349
|
+
unless s[1..-2].include?(SINGLE_QUOTE)
|
350
|
+
s.gsub!(SINGLE_QUOTE, "\\'")
|
351
|
+
s.gsub!('\"', DOUBLE_QUOTE)
|
352
|
+
s[0] = s[-1] = SINGLE_QUOTE
|
309
353
|
end
|
310
354
|
end
|
311
355
|
s
|
@@ -336,8 +380,12 @@ module Lines
|
|
336
380
|
t.utc.iso8601
|
337
381
|
end
|
338
382
|
|
383
|
+
def dateenc(d)
|
384
|
+
d.iso8601
|
385
|
+
end
|
386
|
+
|
339
387
|
def is_literal?(s)
|
340
|
-
!s.index(/[\s'"]/)
|
388
|
+
!s.index(/[\s'"=:{}\[\]]/)
|
341
389
|
end
|
342
390
|
|
343
391
|
end
|
@@ -360,3 +408,6 @@ module Lines
|
|
360
408
|
end
|
361
409
|
extend UniqueIDs
|
362
410
|
end
|
411
|
+
|
412
|
+
# default config
|
413
|
+
Lines.use($stderr)
|
data/lib/lines/active_record.rb
CHANGED
data/lib/lines/loader.rb
CHANGED
@@ -1,166 +1,229 @@
|
|
1
|
-
begin
|
2
|
-
require 'parslet'
|
3
|
-
rescue LoadError
|
4
|
-
warn "lines/loader depends on parslet"
|
5
|
-
raise
|
6
|
-
end
|
7
|
-
|
8
|
-
# http://zerowidth.com/2013/02/24/parsing-toml-in-ruby-with-parslet.html
|
9
1
|
module Lines
|
10
2
|
module Error; end
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
3
|
+
|
4
|
+
class Loader
|
5
|
+
class ParseError < StandardError; include Error; end
|
6
|
+
|
7
|
+
DOT = '.'
|
8
|
+
EQUAL = '='
|
9
|
+
SPACE = ' '
|
10
|
+
OPEN_BRACKET = '['
|
11
|
+
SHUT_BRACKET = ']'
|
12
|
+
OPEN_BRACE = '{'
|
13
|
+
SHUT_BRACE = '}'
|
14
|
+
SINGLE_QUOTE = "'"
|
15
|
+
DOUBLE_QUOTE = '"'
|
16
|
+
BACKSLASH = '\\'
|
17
|
+
|
18
|
+
ESCAPED_SINGLE_QUOTE = "\\'"
|
19
|
+
ESCAPED_DOUBLE_QUOTE = '\"'
|
20
|
+
|
21
|
+
LITERAL_MATCH = /[^=\s}\]]+/
|
22
|
+
SINGLE_QUOTE_MATCH = /(?:\\.|[^'])*/
|
23
|
+
DOUBLE_QUOTE_MATCH = /(?:\\.|[^"])*/
|
24
|
+
|
25
|
+
NUM_MATCH = /-?(?:0|[1-9])\d*(?:\.\d+)?(?:[eE][+-]\d+)?/
|
26
|
+
ISO8601_ZULU_CAPTURE = /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/
|
27
|
+
NUM_CAPTURE = /^(#{NUM_MATCH})$/
|
28
|
+
UNIT_CAPTURE = /^(#{NUM_MATCH}):(.+)/
|
29
|
+
|
30
|
+
# Speeds parsing up a bit
|
31
|
+
constants.each(&:freeze)
|
32
|
+
|
33
|
+
EOF = nil
|
34
|
+
|
35
|
+
|
36
|
+
def self.load(string)
|
37
|
+
new.parse(string)
|
24
38
|
end
|
25
|
-
end
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
}
|
95
|
-
|
96
|
-
rule(:value) {
|
97
|
-
str('#t').as(:true) | str('#f').as(:false) |
|
98
|
-
str('nil').as(:nil) |
|
99
|
-
object | array |
|
100
|
-
number | time |
|
101
|
-
string
|
102
|
-
}
|
103
|
-
|
104
|
-
rule(:entry) {
|
105
|
-
(
|
106
|
-
key.as(:key) >>
|
107
|
-
str('=') >>
|
108
|
-
value.as(:val)
|
109
|
-
).as(:entry)
|
110
|
-
}
|
111
|
-
|
112
|
-
rule(:top) { spaces? >> (entry >> (spaces >> entry).repeat).maybe.as(:object) >> spaces? }
|
113
|
-
#rule(:top) { (digit >> digit).as(:digit) }
|
114
|
-
#rule(:top) { time }
|
115
|
-
|
116
|
-
root(:top)
|
117
|
-
end
|
40
|
+
def parse(string)
|
41
|
+
init(string.rstrip)
|
42
|
+
inner_obj
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def init(string)
|
48
|
+
@string = string
|
49
|
+
@pos = 0
|
50
|
+
@c = @string[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
def getc
|
54
|
+
@pos += 1
|
55
|
+
@c = @string[@pos]
|
56
|
+
end
|
57
|
+
|
58
|
+
def accept(char)
|
59
|
+
if @c == char
|
60
|
+
getc
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def peek(num)
|
67
|
+
@string[@pos+num]
|
68
|
+
end
|
69
|
+
|
70
|
+
def skip(num)
|
71
|
+
@pos += num
|
72
|
+
@c = @string[@pos]
|
73
|
+
end
|
74
|
+
|
75
|
+
def match(reg)
|
76
|
+
@string.match(reg, @pos)
|
77
|
+
end
|
78
|
+
|
79
|
+
def expect(char)
|
80
|
+
if !accept(char)
|
81
|
+
fail "Expected '#{char}' but got '#{@c}'"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def fail(msg)
|
86
|
+
raise ParseError, "At #{@pos}, #{msg}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def dbg(*x)
|
90
|
+
#p [@pos, @c, @string[0..@pos]] + x
|
91
|
+
end
|
92
|
+
|
93
|
+
# Structures
|
94
|
+
|
95
|
+
|
96
|
+
def inner_obj
|
97
|
+
dbg :inner_obj
|
98
|
+
# Shortcut for the '...' max_depth notation
|
99
|
+
if @c == DOT && peek(1) == DOT && peek(2) == DOT
|
100
|
+
expect DOT
|
101
|
+
expect DOT
|
102
|
+
expect DOT
|
103
|
+
return {'...' => ''}
|
104
|
+
end
|
105
|
+
|
106
|
+
return {} if @c == EOF || @c == SHUT_BRACE
|
118
107
|
|
119
|
-
|
108
|
+
# First pair
|
109
|
+
k = key()
|
110
|
+
expect EQUAL
|
111
|
+
obj = {
|
112
|
+
k => value()
|
113
|
+
}
|
114
|
+
|
115
|
+
while accept(SPACE)
|
116
|
+
k = key()
|
117
|
+
expect EQUAL
|
118
|
+
obj[k] = value()
|
119
|
+
end
|
120
|
+
|
121
|
+
obj
|
122
|
+
end
|
120
123
|
|
121
|
-
|
124
|
+
def key
|
125
|
+
dbg :key
|
122
126
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
when Array
|
128
|
-
ar
|
127
|
+
if @c == SINGLE_QUOTE
|
128
|
+
single_quoted_string
|
129
|
+
elsif @c == DOUBLE_QUOTE
|
130
|
+
double_quoted_string
|
129
131
|
else
|
130
|
-
|
132
|
+
literal(false)
|
131
133
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
end
|
135
|
+
|
136
|
+
def single_quoted_string
|
137
|
+
dbg :single_quoted_string
|
138
|
+
|
139
|
+
expect SINGLE_QUOTE
|
140
|
+
md = match SINGLE_QUOTE_MATCH
|
141
|
+
str = md[0].gsub ESCAPED_SINGLE_QUOTE, SINGLE_QUOTE
|
142
|
+
skip md[0].size
|
143
|
+
|
144
|
+
expect SINGLE_QUOTE
|
145
|
+
str
|
146
|
+
end
|
147
|
+
|
148
|
+
def double_quoted_string
|
149
|
+
dbg :double_quoted_string
|
150
|
+
|
151
|
+
expect DOUBLE_QUOTE
|
152
|
+
md = match DOUBLE_QUOTE_MATCH
|
153
|
+
str = md[0].gsub ESCAPED_DOUBLE_QUOTE, DOUBLE_QUOTE
|
154
|
+
skip md[0].size
|
155
|
+
|
156
|
+
expect DOUBLE_QUOTE
|
157
|
+
str
|
158
|
+
end
|
159
|
+
|
160
|
+
def literal(sub_parse)
|
161
|
+
dbg :literal, sub_parse
|
162
|
+
|
163
|
+
return "" unless ((md = match LITERAL_MATCH))
|
164
|
+
|
165
|
+
literal = md[0]
|
166
|
+
skip literal.size
|
167
|
+
|
168
|
+
return literal unless sub_parse
|
169
|
+
|
170
|
+
case literal
|
171
|
+
when 'nil'
|
172
|
+
nil
|
173
|
+
when '#t'
|
174
|
+
true
|
175
|
+
when '#f'
|
176
|
+
false
|
177
|
+
when ISO8601_ZULU_CAPTURE
|
178
|
+
Time.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, '+00:00').utc
|
179
|
+
when NUM_CAPTURE
|
180
|
+
literal.index('.') ? Float(literal) : Integer(literal)
|
181
|
+
when UNIT_CAPTURE
|
182
|
+
num = $1.index('.') ? Float($1) : Integer($1)
|
183
|
+
unit = $2
|
184
|
+
[num, unit]
|
139
185
|
else
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
}
|
144
|
-
}
|
186
|
+
literal
|
187
|
+
end
|
188
|
+
end
|
145
189
|
|
146
|
-
|
147
|
-
|
148
|
-
|
190
|
+
def value
|
191
|
+
dbg :value
|
192
|
+
|
193
|
+
case @c
|
194
|
+
when OPEN_BRACKET
|
195
|
+
list
|
196
|
+
when OPEN_BRACE
|
197
|
+
object
|
198
|
+
when DOUBLE_QUOTE
|
199
|
+
double_quoted_string
|
200
|
+
when SINGLE_QUOTE
|
201
|
+
single_quoted_string
|
202
|
+
else
|
203
|
+
literal(:sub_parse)
|
204
|
+
end
|
205
|
+
end
|
149
206
|
|
150
|
-
|
151
|
-
|
152
|
-
}
|
207
|
+
def list
|
208
|
+
dbg :list
|
153
209
|
|
154
|
-
|
155
|
-
|
156
|
-
|
210
|
+
list = []
|
211
|
+
expect(OPEN_BRACKET)
|
212
|
+
list.push value
|
213
|
+
while accept(SPACE)
|
214
|
+
list.push value
|
215
|
+
end
|
216
|
+
expect(SHUT_BRACKET)
|
217
|
+
list
|
218
|
+
end
|
157
219
|
|
158
|
-
|
159
|
-
|
160
|
-
}
|
220
|
+
def object
|
221
|
+
dbg :object
|
161
222
|
|
162
|
-
|
163
|
-
|
164
|
-
|
223
|
+
expect(OPEN_BRACE)
|
224
|
+
obj = inner_obj
|
225
|
+
expect(SHUT_BRACE)
|
226
|
+
obj
|
227
|
+
end
|
165
228
|
end
|
166
229
|
end
|
data/lib/lines/version.rb
CHANGED
data/spec/bench.rb
CHANGED
@@ -1,56 +1,46 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
|
1
3
|
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
4
|
require 'lines'
|
3
5
|
|
4
|
-
module Kernel
|
5
|
-
def bm(name = nil, &what)
|
6
|
-
start = Time.now.to_f
|
7
|
-
count = 0
|
8
|
-
max = 0.5
|
9
|
-
name ||= what.source_location.join(':')
|
10
|
-
$stdout.write "#{name} : "
|
11
|
-
while Time.now.to_f - start < max
|
12
|
-
yield
|
13
|
-
count += 1
|
14
|
-
end
|
15
|
-
$stdout.puts "%0.3f fps" % (count / max)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
6
|
class FakeIO
|
20
7
|
def write(*a)
|
21
8
|
end
|
22
9
|
alias syswrite write
|
23
10
|
end
|
24
11
|
|
25
|
-
|
26
|
-
|
27
|
-
|
12
|
+
globals = {
|
13
|
+
app: 'benchmark',
|
14
|
+
at: proc{ Time.now },
|
15
|
+
pid: Process.pid,
|
16
|
+
}
|
28
17
|
|
29
18
|
EX = (raise "FOO" rescue $!)
|
30
19
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
20
|
+
Benchmark.ips do |x|
|
21
|
+
x.report "FakeIO write" do |n|
|
22
|
+
Lines.use(FakeIO.new, globals)
|
23
|
+
n.times{ Lines.log EX }
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
26
|
+
x.report "/dev/null write" do |n|
|
27
|
+
dev_null = File.open('/dev/null', 'w')
|
28
|
+
Lines.use(dev_null, globals)
|
29
|
+
n.times{ Lines.log EX }
|
30
|
+
end
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
32
|
+
x.report "syslog write" do |n|
|
33
|
+
Lines.use(Syslog, globals)
|
34
|
+
n.times{ Lines.log EX }
|
35
|
+
end
|
46
36
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
37
|
+
x.report "real file" do |n|
|
38
|
+
real_file = File.open('real_file.log', 'w')
|
39
|
+
Lines.use(real_file, globals)
|
40
|
+
n.times{ Lines.log EX }
|
41
|
+
end
|
52
42
|
|
53
|
-
|
54
|
-
|
43
|
+
x.report "real file logger" do |n|
|
44
|
+
n.times{ Lines.logger.info "Ahoi this is a really cool option" }
|
45
|
+
end
|
55
46
|
end
|
56
|
-
|
data/spec/lines_loader_spec.rb
CHANGED
@@ -1,61 +1,55 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'lines/loader'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
subject { Loader }
|
7
|
-
|
8
|
-
it "can load stuff" do
|
9
|
-
expect(Loader.load 'foo=bar').to eq("foo" => "bar")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
describe Parser do
|
14
|
-
let(:parser) { Lines::Parser.new }
|
15
|
-
|
16
|
-
context "value parsing" do
|
17
|
-
let(:value_parser) { parser.value }
|
18
|
-
|
19
|
-
it "parses integers" do
|
20
|
-
pending
|
21
|
-
expect(value_parser).to parse("1")
|
22
|
-
expect(value_parser).to parse("-123")
|
23
|
-
expect(value_parser).to parse("120381")
|
24
|
-
expect(value_parser).to parse("181")
|
25
|
-
end
|
26
|
-
|
27
|
-
it "parses floats" do
|
28
|
-
pending
|
29
|
-
expect(value_parser).to parse("0.1")
|
30
|
-
expect(value_parser).to parse("3.14159")
|
31
|
-
expect(value_parser).to parse("-0.00001")
|
32
|
-
end
|
33
|
-
|
34
|
-
it "parses booleans" do
|
35
|
-
pending
|
36
|
-
expect(value_parser).to parse("#t")
|
37
|
-
expect(value_parser).to parse("#f")
|
38
|
-
end
|
39
|
-
|
40
|
-
it "parses datetimes" do
|
41
|
-
pending
|
42
|
-
expect(value_parser).to parse("1979-05-27T07:32:00Z")
|
43
|
-
expect(value_parser).to parse("2013-02-24T17:26:21Z")
|
44
|
-
expect(value_parser).to_not parse("1979l05-27 07:32:00")
|
45
|
-
end
|
46
|
-
|
47
|
-
it "parses strings" do
|
48
|
-
pending
|
49
|
-
expect(value_parser).to parse('""')
|
50
|
-
expect(value_parser).to parse('"hello world"')
|
51
|
-
expect(value_parser).to parse('"hello\\nworld"')
|
52
|
-
expect(value_parser).to parse('"hello\\t\\n\\\\\\0world\\n"')
|
53
|
-
expect(value_parser).to_not parse("\"hello\nworld\"")
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe Transformer do
|
4
|
+
describe Lines::Loader do
|
5
|
+
subject { Lines::Loader.new }
|
59
6
|
|
7
|
+
def expect_load(str)
|
8
|
+
expect(subject.parse str)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can load stuff" do
|
12
|
+
expect_load('foo=bar bar=33').to eq("foo" => "bar", "bar" => 33)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "handles max_depth items" do
|
16
|
+
expect_load('x=[...]').to eq("x" => ["..."])
|
17
|
+
expect_load('x={...}').to eq("x" => {"..." => ""})
|
18
|
+
end
|
19
|
+
|
20
|
+
it "treats missing value in a pair as an empty string" do
|
21
|
+
expect_load('x=').to eq("x" => "")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has non-greedy string parsing" do
|
25
|
+
expect_load('x="foo" bar="baz"').to eq("x" => "foo", "bar" => "baz")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "unscapes quotes in quoted strings" do
|
29
|
+
expect_load("x='foo\\'bar'").to eq("x" => "foo'bar")
|
30
|
+
expect_load('x="foo\"bar"').to eq("x" => 'foo"bar')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "doesn't parse literals when they are keys" do
|
34
|
+
expect_load("3=4").to eq("3" => 4)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "handles some random stuff" do
|
38
|
+
expect_load("=").to eq("" => "")
|
39
|
+
expect_load('"\""=zzz').to eq('"' => "zzz")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "parses sample log lines" do
|
43
|
+
expect_load("commit=716f337").to eq("commit" => "716f337")
|
44
|
+
|
45
|
+
line = <<LINE
|
46
|
+
at=2013-07-12T21:33:47Z commit=716f337 sql="SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(created_at) - UNIX_TIMESTAMP(created_at)%(300)) as timestamp FROM `job_queue_logs` WHERE `job_queue_logs`.`account_id` = 'effe376baf553c590c02090abe512278' AND (created_at >= '2013-06-28 16:56:12') GROUP BY timestamp" elapsed=31.9:ms
|
47
|
+
LINE
|
48
|
+
expect_load(line).to eq(
|
49
|
+
"at" => Time.at(1373664827).utc,
|
50
|
+
"commit" => "716f337",
|
51
|
+
"sql" => "SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(created_at) - UNIX_TIMESTAMP(created_at)%(300)) as timestamp FROM `job_queue_logs` WHERE `job_queue_logs`.`account_id` = 'effe376baf553c590c02090abe512278' AND (created_at >= '2013-06-28 16:56:12') GROUP BY timestamp",
|
52
|
+
"elapsed" => [31.9, "ms"],
|
53
|
+
)
|
60
54
|
end
|
61
55
|
end
|
data/spec/lines_spec.rb
CHANGED
@@ -2,28 +2,29 @@ require 'spec_helper'
|
|
2
2
|
require 'lines'
|
3
3
|
require 'stringio'
|
4
4
|
|
5
|
+
NL = "\n"
|
6
|
+
|
5
7
|
describe Lines do
|
6
8
|
let(:outputter) { StringIO.new }
|
7
9
|
let(:output) { outputter.string }
|
8
10
|
before do
|
9
|
-
Lines.global.replace({})
|
10
11
|
Lines.use(outputter)
|
11
12
|
end
|
12
13
|
|
13
14
|
context ".log" do
|
14
15
|
it "logs stuff" do
|
15
16
|
Lines.log(foo: 'bar')
|
16
|
-
expect(output).to eq('foo=bar' +
|
17
|
+
expect(output).to eq('foo=bar' + NL)
|
17
18
|
end
|
18
19
|
|
19
20
|
it "supports a first msg argument" do
|
20
21
|
Lines.log("this user is annoying", user: 'bob')
|
21
|
-
expect(output).to eq("msg='this user is annoying' user=bob" +
|
22
|
+
expect(output).to eq("msg='this user is annoying' user=bob" + NL)
|
22
23
|
end
|
23
24
|
|
24
25
|
it "logs exceptions" do
|
25
26
|
Lines.log(StandardError.new("error time!"), user: 'bob')
|
26
|
-
expect(output).to eq("ex=StandardError msg='error time!' user=bob" +
|
27
|
+
expect(output).to eq("ex=StandardError msg='error time!' user=bob" + NL)
|
27
28
|
end
|
28
29
|
|
29
30
|
it "logs exception backtraces when available" do
|
@@ -35,33 +36,33 @@ describe Lines do
|
|
35
36
|
|
36
37
|
it "works with anything" do
|
37
38
|
Lines.log("anything1", "anything2")
|
38
|
-
expect(output).to eq('msg=anything2' +
|
39
|
+
expect(output).to eq('msg=anything2' + NL)
|
39
40
|
end
|
40
41
|
|
41
42
|
it "doesn't convert nil args to msg" do
|
42
43
|
Lines.log("anything", nil)
|
43
|
-
expect(output).to eq('msg=anything' +
|
44
|
+
expect(output).to eq('msg=anything' + NL)
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
context ".context" do
|
48
49
|
it "has contextes" do
|
49
50
|
Lines.context(foo: "bar").log(a: 'b')
|
50
|
-
expect(output).to eq('a=b foo=bar' +
|
51
|
+
expect(output).to eq('a=b foo=bar' + NL)
|
51
52
|
end
|
52
53
|
|
53
54
|
it "has contextes with blocks" do
|
54
55
|
Lines.context(foo: "bar") do |ctx|
|
55
56
|
ctx.log(a: 'b')
|
56
57
|
end
|
57
|
-
expect(output).to eq('a=b foo=bar' +
|
58
|
+
expect(output).to eq('a=b foo=bar' + NL)
|
58
59
|
end
|
59
60
|
|
60
61
|
it "mixes everything" do
|
61
62
|
Lines.global[:app] = :self
|
62
63
|
ctx = Lines.context(foo: "bar")
|
63
64
|
ctx.log('msg', ahoi: true)
|
64
|
-
expect(output).to eq('app=self msg=msg ahoi=#t foo=bar' +
|
65
|
+
expect(output).to eq('app=self msg=msg ahoi=#t foo=bar' + NL)
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
@@ -69,7 +70,7 @@ describe Lines do
|
|
69
70
|
it "is provided for backward-compatibility" do
|
70
71
|
l = Lines.logger
|
71
72
|
l.info("hi")
|
72
|
-
expect(output).to eq('pri=info msg=hi' +
|
73
|
+
expect(output).to eq('pri=info msg=hi' + NL)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
@@ -77,7 +78,7 @@ describe Lines do
|
|
77
78
|
it "prepends data to the line" do
|
78
79
|
Lines.global["app"] = :self
|
79
80
|
Lines.log 'hey'
|
80
|
-
expect(output).to eq('app=self msg=hey' +
|
81
|
+
expect(output).to eq('app=self msg=hey' + NL)
|
81
82
|
end
|
82
83
|
|
83
84
|
it "resolves procs dynamically" do
|
@@ -86,15 +87,15 @@ describe Lines do
|
|
86
87
|
Lines.log 'test1'
|
87
88
|
Lines.log 'test2'
|
88
89
|
expect(output).to eq(
|
89
|
-
'count=1 msg=test1' +
|
90
|
-
'count=2 msg=test2' +
|
90
|
+
'count=1 msg=test1' + NL +
|
91
|
+
'count=2 msg=test2' + NL
|
91
92
|
)
|
92
93
|
end
|
93
94
|
|
94
95
|
it "doesn't fail if a proc has an exception" do
|
95
96
|
Lines.global[:X] = proc{ fail "error" }
|
96
97
|
Lines.log 'test'
|
97
|
-
expect(output).to eq("X='#<RuntimeError: error>' msg=test" +
|
98
|
+
expect(output).to eq("X='#<RuntimeError: error>' msg=test" + NL)
|
98
99
|
end
|
99
100
|
end
|
100
101
|
end
|
@@ -128,18 +129,22 @@ describe Lines::Dumper do
|
|
128
129
|
end
|
129
130
|
|
130
131
|
it "can dump a basicobject" do
|
131
|
-
expect_dump(foo: BasicObject.new).to match(/foo
|
132
|
+
expect_dump(foo: BasicObject.new).to match(/foo='#<BasicObject:0x[0-9a-f]+>'/)
|
132
133
|
end
|
133
134
|
|
134
135
|
it "can dump IO objects" do
|
135
136
|
expect_dump(foo: File.open(__FILE__)).to match(/foo='?#<File:[^>]+>'?/)
|
136
|
-
expect_dump(foo: STDOUT).to match(/^foo=(?:#<IO:<STDOUT
|
137
|
+
expect_dump(foo: STDOUT).to match(/^foo='(?:#<IO:<STDOUT>>|#<IO:fd 1>)'$/)
|
137
138
|
end
|
138
139
|
|
139
140
|
it "dumps time as ISO zulu format" do
|
140
141
|
expect_dump(foo: Time.at(1337)).to eq('foo=1970-01-01T00:22:17Z')
|
141
142
|
end
|
142
143
|
|
144
|
+
it "dumps date as ISO date" do
|
145
|
+
expect_dump(foo: Date.new(1968, 3, 7)).to eq('foo=1968-03-07')
|
146
|
+
end
|
147
|
+
|
143
148
|
it "dumps symbols as strings" do
|
144
149
|
expect_dump(foo: :some_symbol).to eq('foo=some_symbol')
|
145
150
|
expect_dump(foo: :"some symbol").to eq("foo='some symbol'")
|
@@ -158,14 +163,14 @@ describe Lines::Dumper do
|
|
158
163
|
end
|
159
164
|
|
160
165
|
it "dumps [number, literal] tuples as numberliteral" do
|
161
|
-
expect_dump(foo: [3, :ms]).to eq('foo=
|
162
|
-
expect_dump(foo: [54.2, 's']).to eq('foo=54.
|
166
|
+
expect_dump(foo: [3, :ms]).to eq('foo=3:ms')
|
167
|
+
expect_dump(foo: [54.2, 's']).to eq('foo=54.2:s')
|
163
168
|
end
|
164
169
|
|
165
170
|
it "knows how to handle circular dependencies" do
|
166
171
|
x = {}
|
167
172
|
x[:x] = x
|
168
|
-
expect_dump(x).to eq('x={x={x={...}}}')
|
173
|
+
expect_dump(x).to eq('x={x={x={x={...}}}}')
|
169
174
|
end
|
170
175
|
end
|
171
176
|
|
data/spec/parse-bench
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
libdir = File.expand_path('../../lib', __FILE__)
|
4
|
+
$:.unshift(libdir) unless $:.include? libdir
|
5
|
+
|
6
|
+
require 'lines'
|
7
|
+
|
8
|
+
loader = Lines.loader
|
9
|
+
|
10
|
+
start = Time.now
|
11
|
+
line_count = 0
|
12
|
+
|
13
|
+
$stdin.lines.each do |line|
|
14
|
+
begin
|
15
|
+
line_count += 1
|
16
|
+
loader.load(line)
|
17
|
+
rescue Lines::Error => ex
|
18
|
+
# Lines seem to get truncated by syslog when too long
|
19
|
+
if line.size < 2000
|
20
|
+
p line
|
21
|
+
p ex
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Parsed #{line_count / (Time.now - start)} lines per second"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lines
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Pfenniger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- spec/bench.rb
|
64
64
|
- spec/lines_loader_spec.rb
|
65
65
|
- spec/lines_spec.rb
|
66
|
+
- spec/parse-bench
|
66
67
|
- spec/spec_helper.rb
|
67
68
|
homepage: https://github.com/zimbatm/lines-ruby
|
68
69
|
licenses:
|
@@ -92,4 +93,5 @@ test_files:
|
|
92
93
|
- spec/bench.rb
|
93
94
|
- spec/lines_loader_spec.rb
|
94
95
|
- spec/lines_spec.rb
|
96
|
+
- spec/parse-bench
|
95
97
|
- spec/spec_helper.rb
|