lines 0.1.27 → 0.2.0
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 +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
|