papertrail 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gem 'rake', '0.8.7'
4
4
 
data/README.md CHANGED
@@ -9,12 +9,17 @@ Supports optional Boolean search queries and polling for new events
9
9
  $ papertrail -f "(www OR db) (nginx OR pgsql) -accepted"
10
10
 
11
11
  Output is line-buffered so it can be fed into a pipe, like for grep.
12
- See below for colorization setup.
12
+ ANSI color codes are rendered in color on suitable terminals; see below for
13
+ additional colorization options.
13
14
 
14
15
  The [Connection] class can be used by other apps to perform one-off
15
16
  API searches or follow (tail) events matching a given query. Interface
16
17
  may change.
17
18
 
19
+ Also includes `papertrail-add-system`, `papertrail-remove-system`,
20
+ `papertrail-add-group`, and `papertrail-join-group` binaries, which
21
+ invoke the corresponding Papertrail [HTTP API] call.
22
+
18
23
 
19
24
  ## Quick Start
20
25
 
@@ -22,7 +27,13 @@ may change.
22
27
  $ echo "token: 123456789012345678901234567890ab" > ~/.papertrail.yml
23
28
  $ papertrail
24
29
 
25
- Retrieve token from Papertrail [User Profile].
30
+ Retrieve the token from Papertrail [User Profile].
31
+
32
+ The API token can also be passed in the `PAPERTRAIL_API_TOKEN`
33
+ environment variable instead of a configuration file. Example:
34
+
35
+ $ export PAPERTRAIL_API_TOKEN='abc123'
36
+ $ papertrail
26
37
 
27
38
 
28
39
  ## Installation
@@ -44,7 +55,7 @@ examples/papertrail.yml.example):
44
55
  Retrieve token from Papertrail [User Profile]. For compatibility with
45
56
  older config files, `username` and `password` keys are also supported.
46
57
 
47
- You may want to alias "trail" to "papertrail", like:
58
+ You may want to alias "pt" to "papertrail", like:
48
59
 
49
60
  echo "alias pt=papertrail" >> ~/.bashrc
50
61
 
@@ -70,18 +81,25 @@ You may want to alias "trail" to "papertrail", like:
70
81
  Examples:
71
82
  papertrail -f
72
83
  papertrail something
73
- papertrail 1.2.3 Failure
84
+ papertrail --min-time "20 minutes ago" 1.2.3 Failure
74
85
  papertrail -s ns1 "connection refused"
75
86
  papertrail -f "(www OR db) (nginx OR pgsql) -accepted"
76
87
  papertrail -f -g Production "(nginx OR pgsql) -accepted"
77
88
  papertrail -g Production --min-time 'yesterday at noon' --max-time 'today at 4am'
78
89
 
90
+ Includes 4 binaries to change Papertrail settings: papertrail-add-system, papertrail-remove-system,
91
+ papertrail-add-group, papertrail-leave-group. Run with --help or see README.
92
+
79
93
  More: http://papertrailapp.com/
94
+
80
95
 
96
+ ### Colors
81
97
 
82
- ## Colors
98
+ ANSI color codes are retained, so log messages which are already colorized
99
+ will automatically render in color on ANSI-capable terminals.
83
100
 
84
- Pipe through [colortail] or [MultiTail]. We recommend colortail:
101
+ To manually colorize monochrome logs, pipe through [colortail] or
102
+ [MultiTail]. We recommend `colortail``:
85
103
 
86
104
  $ sudo gem install colortail
87
105
 
@@ -103,12 +121,12 @@ Add the function line to your `~/.bashrc`.
103
121
 
104
122
  For complete control, pipe through anything capable of inserting ANSI
105
123
  control characters. Here's an example that colorizes 3 fields separately
106
- - the first 15 characters for the date, a word for the hostname, and a
107
- word for the program name:
124
+ (the first 15 characters for the date, a word for the hostname, and a
125
+ word for the program name):
108
126
 
109
127
  $ papertrail | perl -pe 's/^(.{15})(.)([\S]+)(.)([\S]+)/\e[1;31;43m\1\e[0m\2\e[1;31;43m\3\e[0m\4\e[1;31;43m\5\e[0m/g'
110
128
 
111
- the "1;31;43" are bold (1), foreground red (31), background yellow (43),
129
+ the `1;31;43` are bold (1), foreground red (31), background yellow (43),
112
130
  and can be any ANSI [escape characters].
113
131
 
114
132
  ### UTF-8 (non-English searches)
@@ -126,6 +144,28 @@ at invocation. For example, to persist that in a `.bashrc`:
126
144
 
127
145
  export RUBYOPT="-E:UTF-8"
128
146
 
147
+ ### Negation-only queries
148
+
149
+ Unix shells handle arguments beginning with hyphens (`-`) differently
150
+ ([why](http://unix.stackexchange.com/questions/11376/what-does-double-dash-mean)).
151
+ Usually this is moot because most searches start with a positive match.
152
+ To search only for log messages without a given string, use `--`. For
153
+ example, to search for `-whatever`, run:
154
+
155
+ papertrail -- -whatever
156
+
157
+
158
+ ## Add/Remove Systems, Create Group, Join Group
159
+
160
+ In addition to tail and search with the `papertrail` binary, the gem includes
161
+ 4 other binaries which wrap other parts of Papertrail's [HTTP API] to explicitly
162
+ add or remove a system, to create a new group, and to join a system to a group.
163
+
164
+ In most cases, configuration is automatic and these are not not necessary.
165
+
166
+ To see usage, run any of these commands with `--help`: `papertrail-add-system`,
167
+ `papertrail-remove-system`, `papertrail-add-group`, `papertrail-join-group`.
168
+
129
169
 
130
170
  ## Contribute
131
171
 
@@ -147,6 +187,7 @@ to your enhancement.
147
187
  [binary]: https://github.com/papertrail/papertrail-cli/blob/master/bin/papertrail
148
188
  [Papertrail]: http://papertrailapp.com/
149
189
  [Connection]: https://github.com/papertrail/papertrail-cli/blob/master/lib/papertrail/connection.rb
190
+ [HTTP API]: http://help.papertrailapp.com/kb/how-it-works/http-api
150
191
  [User Profile]: https://papertrailapp.com/user/edit
151
192
  [RubyGems]: https://rubygems.org/gems/papertrail-cli
152
193
  [colortail]: http://rubydoc.info/gems/colortail
data/lib/papertrail.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Papertrail
2
- VERSION = "0.9.7"
2
+ VERSION = "0.9.8"
3
3
  end
4
4
 
5
- require 'papertrail/search_client'
5
+ require 'papertrail/connection'
@@ -15,18 +15,14 @@ module Papertrail
15
15
  @options = {
16
16
  :configfile => nil,
17
17
  :delay => 2,
18
- :follow => false
18
+ :follow => false,
19
+ :token => ENV['PAPERTRAIL_API_TOKEN']
19
20
  }
20
21
 
21
22
  @query_options = {}
22
23
  end
23
24
 
24
25
  def run
25
- # Let it slide if we have invalid JSON
26
- if JSON.respond_to?(:default_options)
27
- JSON.default_options[:check_utf8] = false
28
- end
29
-
30
26
  if configfile = find_configfile
31
27
  configfile_options = load_configfile(configfile)
32
28
  options.merge!(configfile_options)
@@ -157,7 +153,7 @@ module Papertrail
157
153
  def usage
158
154
  <<-EOF
159
155
 
160
- Usage:
156
+ Usage:
161
157
  papertrail [-f] [-s system] [-g group] [-d seconds] [-c papertrail.yml] [-j] [--min-time mintime] [--max-time maxtime] [query]
162
158
 
163
159
  Examples:
@@ -9,13 +9,9 @@ module Papertrail
9
9
  include Papertrail::CliHelpers
10
10
 
11
11
  def run
12
- # Let it slide if we have invalid JSON
13
- if JSON.respond_to?(:default_options)
14
- JSON.default_options[:check_utf8] = false
15
- end
16
-
17
12
  options = {
18
13
  :configfile => nil,
14
+ :token => ENV['PAPERTRAIL_API_TOKEN'],
19
15
  }
20
16
 
21
17
  if configfile = find_configfile
@@ -71,8 +67,8 @@ module Papertrail
71
67
  def usage
72
68
  <<-EOF
73
69
 
74
- Usage:
75
- papertrail-add-group [-g group] [-w system-wildcard] [-c papertrail.yml]
70
+ Usage:
71
+ papertrail-add-group [-g group] [-w system-wildcard] [-c papertrail.yml]
76
72
 
77
73
  Example:
78
74
  papertrail-add-group -g mygroup -w mygroup-systems*
@@ -11,13 +11,9 @@ module Papertrail
11
11
  attr_reader :program_name
12
12
 
13
13
  def run
14
- # Let it slide if we have invalid JSON
15
- if JSON.respond_to?(:default_options)
16
- JSON.default_options[:check_utf8] = false
17
- end
18
-
19
14
  options = {
20
15
  :configfile => nil,
16
+ :token => ENV['PAPERTRAIL_API_TOKEN'],
21
17
  }
22
18
 
23
19
  if configfile = find_configfile
@@ -9,13 +9,9 @@ module Papertrail
9
9
  include Papertrail::CliHelpers
10
10
 
11
11
  def run
12
- # Let it slide if we have invalid JSON
13
- if JSON.respond_to?(:default_options)
14
- JSON.default_options[:check_utf8] = false
15
- end
16
-
17
12
  options = {
18
13
  :configfile => nil,
14
+ :token => ENV['PAPERTRAIL_API_TOKEN'],
19
15
  }
20
16
 
21
17
  if configfile = find_configfile
@@ -67,8 +63,8 @@ module Papertrail
67
63
  def usage
68
64
  <<-EOF
69
65
 
70
- Usage:
71
- papertrail-join-group [-s system] [-g group] [-c papertrail.yml]
66
+ Usage:
67
+ papertrail-join-group [-s system] [-g group] [-c papertrail.yml]
72
68
 
73
69
  Example:
74
70
  papertrail-join-group -s mymachine -g mygroup
@@ -9,13 +9,9 @@ module Papertrail
9
9
  include Papertrail::CliHelpers
10
10
 
11
11
  def run
12
- # Let it slide if we have invalid JSON
13
- if JSON.respond_to?(:default_options)
14
- JSON.default_options[:check_utf8] = false
15
- end
16
-
17
12
  options = {
18
13
  :configfile => nil,
14
+ :token => ENV['PAPERTRAIL_API_TOKEN'],
19
15
  }
20
16
 
21
17
  if configfile = find_configfile
@@ -63,8 +59,8 @@ module Papertrail
63
59
  def usage
64
60
  <<-EOF
65
61
 
66
- Usage:
67
- papertrail-remove-system [-s system] [-c papertrail.yml]
62
+ Usage:
63
+ papertrail-remove-system [-s system] [-c papertrail.yml]
68
64
 
69
65
  Example:
70
66
  papertrail-remove-system -s mysystemname
@@ -1,10 +1,7 @@
1
- require 'addressable/uri'
2
- require 'faraday'
1
+ require 'forwardable'
3
2
  require 'openssl'
4
- require 'faraday_middleware'
5
- require 'yajl/json_gem'
6
- require 'zlib'
7
3
 
4
+ require 'papertrail/http_client'
8
5
  require 'papertrail/search_query'
9
6
 
10
7
  module Papertrail
@@ -32,16 +29,11 @@ module Papertrail
32
29
  ssl_options[:ca_file] = '/etc/ssl/certs/ca-certificates.crt'
33
30
  end
34
31
 
35
- @connection = Faraday::Connection.new(:url => 'https://papertrailapp.com/api/v1', :ssl => ssl_options) do |builder|
36
- builder.use Faraday::Request::UrlEncoded
37
- builder.adapter Faraday.default_adapter
38
- builder.use Faraday::Response::RaiseError
39
- builder.use FaradayMiddleware::ParseJson, :content_type => /\bjson$/
40
- end.tap do |conn|
32
+ @connection = Papertrail::HttpClient.new(ssl_options).tap do |conn|
41
33
  if options[:username] && options[:password]
42
34
  conn.basic_auth(options[:username], options[:password])
43
35
  else
44
- conn.headers['X-Papertrail-Token'] = options[:token]
36
+ conn.token_auth(options[:token])
45
37
  end
46
38
  end
47
39
  end
@@ -125,8 +117,7 @@ module Papertrail
125
117
  request[:destination_port] = options[:destination_port]
126
118
  end
127
119
 
128
- response = @connection.post("systems.json", request)
129
- raise response.body.inspect unless response.success?
120
+ @connection.post("systems.json", request)
130
121
  end
131
122
 
132
123
  def unregister_source(name)
@@ -0,0 +1,128 @@
1
+ require 'delegate'
2
+ require 'net/https'
3
+
4
+ require 'papertrail/okjson'
5
+
6
+ module Papertrail
7
+
8
+ # Used because Net::HTTPOK in Ruby 1.8 hasn't method body=
9
+ class HttpResponse < SimpleDelegator
10
+
11
+ def initialize(response)
12
+ super(response)
13
+ end
14
+
15
+ def body
16
+ @body ||= Papertrail::OkJson.decode(__getobj__.body)
17
+ end
18
+
19
+ end
20
+
21
+ class HttpClient
22
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
23
+
24
+ def initialize(ssl)
25
+ @ssl = ssl
26
+ @headers = {}
27
+ end
28
+
29
+ def basic_auth(login, pass)
30
+ @headers['Authorization'] = 'Basic ' + ["#{login}:#{pass}"].pack('m').delete("\r\n")
31
+ end
32
+
33
+ def token_auth(token)
34
+ @headers['X-Papertrail-Token'] = token
35
+ end
36
+
37
+ def get(path, params = {})
38
+ if params.size > 0
39
+ path = "#{path}?#{build_nested_query(params)}"
40
+ end
41
+ on_complete(https.get(request_uri(path), @headers))
42
+ end
43
+
44
+ def put(path, params)
45
+ on_complete(https.put(request_uri(path), build_nested_query(params), @headers))
46
+ end
47
+
48
+ def post(path, params)
49
+ on_complete(https.post(request_uri(path), build_nested_query(params), @headers))
50
+ end
51
+
52
+ def delete(path)
53
+ on_complete(https.delete(request_uri(path), @headers))
54
+ end
55
+
56
+ private
57
+
58
+ def request_uri(path)
59
+ path.start_with?('/api/v1/') ? path : "/api/v1/#{path}"
60
+ end
61
+
62
+ def https
63
+ http = Net::HTTP.new('papertrailapp.com', 443)
64
+ http.use_ssl = true
65
+ http.verify_mode = ssl_verify_mode
66
+ http.cert_store = ssl_cert_store
67
+
68
+ http.cert = @ssl[:client_cert] if @ssl[:client_cert]
69
+ http.key = @ssl[:client_key] if @ssl[:client_key]
70
+ http.ca_file = @ssl[:ca_file] if @ssl[:ca_file]
71
+ http.ca_path = @ssl[:ca_path] if @ssl[:ca_path]
72
+ http.verify_depth = @ssl[:verify_depth] if @ssl[:verify_depth]
73
+ http.ssl_version = @ssl[:version] if @ssl[:version]
74
+
75
+ http
76
+ end
77
+
78
+ def ssl_verify_mode
79
+ @ssl[:verify_mode] || begin
80
+ if @ssl.fetch(:verify, true)
81
+ OpenSSL::SSL::VERIFY_PEER
82
+ else
83
+ OpenSSL::SSL::VERIFY_NONE
84
+ end
85
+ end
86
+ end
87
+
88
+ def ssl_cert_store
89
+ return @ssl[:cert_store] if @ssl[:cert_store]
90
+ # Use the default cert store by default, i.e. system ca certs
91
+ cert_store = OpenSSL::X509::Store.new
92
+ cert_store.set_default_paths
93
+ cert_store
94
+ end
95
+
96
+ def on_complete(response)
97
+ case response
98
+ when Net::HTTPSuccess
99
+ Papertrail::HttpResponse.new(response)
100
+ else
101
+ response.error!
102
+ end
103
+ end
104
+
105
+ def build_nested_query(value, prefix = nil)
106
+ case value
107
+ when Array
108
+ value.map { |v| build_nested_query(v, "#{prefix}%5B%5D") }.join("&")
109
+ when Hash
110
+ value.map { |k, v|
111
+ build_nested_query(v, prefix ? "#{prefix}%5B#{escape(k)}%5D" : escape(k))
112
+ }.join("&")
113
+ when NilClass
114
+ prefix
115
+ else
116
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
117
+ "#{prefix}=#{escape(value)}"
118
+ end
119
+ end
120
+
121
+ def escape(s)
122
+ s.to_s.gsub(ESCAPE_RE) {
123
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
124
+ }.tr(' ', '+')
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,602 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ # See https://github.com/kr/okjson for updates.
24
+
25
+ require 'stringio'
26
+
27
+ module Papertrail
28
+ # Some parts adapted from
29
+ # http://golang.org/src/pkg/json/decode.go and
30
+ # http://golang.org/src/pkg/utf8/utf8.go
31
+ module OkJson
32
+ Upstream = '43'
33
+ extend self
34
+
35
+
36
+ # Decodes a json document in string s and
37
+ # returns the corresponding ruby value.
38
+ # String s must be valid UTF-8. If you have
39
+ # a string in some other encoding, convert
40
+ # it first.
41
+ #
42
+ # String values in the resulting structure
43
+ # will be UTF-8.
44
+ def decode(s)
45
+ ts = lex(s)
46
+ v, ts = textparse(ts)
47
+ if ts.length > 0
48
+ raise Error, 'trailing garbage'
49
+ end
50
+ v
51
+ end
52
+
53
+
54
+ # Encodes x into a json text. It may contain only
55
+ # Array, Hash, String, Numeric, true, false, nil.
56
+ # (Note, this list excludes Symbol.)
57
+ # X itself must be an Array or a Hash.
58
+ # No other value can be encoded, and an error will
59
+ # be raised if x contains any other value, such as
60
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
61
+ # is not a String.
62
+ # Strings contained in x must be valid UTF-8.
63
+ def encode(x)
64
+ case x
65
+ when Hash then objenc(x)
66
+ when Array then arrenc(x)
67
+ else
68
+ raise Error, 'root value must be an Array or a Hash'
69
+ end
70
+ end
71
+
72
+
73
+ def valenc(x)
74
+ case x
75
+ when Hash then objenc(x)
76
+ when Array then arrenc(x)
77
+ when String then strenc(x)
78
+ when Numeric then numenc(x)
79
+ when true then "true"
80
+ when false then "false"
81
+ when nil then "null"
82
+ else
83
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
84
+ end
85
+ end
86
+
87
+
88
+ private
89
+
90
+
91
+ # Parses a "json text" in the sense of RFC 4627.
92
+ # Returns the parsed value and any trailing tokens.
93
+ # Note: this is almost the same as valparse,
94
+ # except that it does not accept atomic values.
95
+ def textparse(ts)
96
+ if ts.length <= 0
97
+ raise Error, 'empty'
98
+ end
99
+
100
+ typ, _, val = ts[0]
101
+ case typ
102
+ when '{' then objparse(ts)
103
+ when '[' then arrparse(ts)
104
+ else
105
+ raise Error, "unexpected #{val.inspect}"
106
+ end
107
+ end
108
+
109
+
110
+ # Parses a "value" in the sense of RFC 4627.
111
+ # Returns the parsed value and any trailing tokens.
112
+ def valparse(ts)
113
+ if ts.length <= 0
114
+ raise Error, 'empty'
115
+ end
116
+
117
+ typ, _, val = ts[0]
118
+ case typ
119
+ when '{' then objparse(ts)
120
+ when '[' then arrparse(ts)
121
+ when :val,:str then [val, ts[1..-1]]
122
+ else
123
+ raise Error, "unexpected #{val.inspect}"
124
+ end
125
+ end
126
+
127
+
128
+ # Parses an "object" in the sense of RFC 4627.
129
+ # Returns the parsed value and any trailing tokens.
130
+ def objparse(ts)
131
+ ts = eat('{', ts)
132
+ obj = {}
133
+
134
+ if ts[0][0] == '}'
135
+ return obj, ts[1..-1]
136
+ end
137
+
138
+ k, v, ts = pairparse(ts)
139
+ obj[k] = v
140
+
141
+ if ts[0][0] == '}'
142
+ return obj, ts[1..-1]
143
+ end
144
+
145
+ loop do
146
+ ts = eat(',', ts)
147
+
148
+ k, v, ts = pairparse(ts)
149
+ obj[k] = v
150
+
151
+ if ts[0][0] == '}'
152
+ return obj, ts[1..-1]
153
+ end
154
+ end
155
+ end
156
+
157
+
158
+ # Parses a "member" in the sense of RFC 4627.
159
+ # Returns the parsed values and any trailing tokens.
160
+ def pairparse(ts)
161
+ (typ, _, k), ts = ts[0], ts[1..-1]
162
+ if typ != :str
163
+ raise Error, "unexpected #{k.inspect}"
164
+ end
165
+ ts = eat(':', ts)
166
+ v, ts = valparse(ts)
167
+ [k, v, ts]
168
+ end
169
+
170
+
171
+ # Parses an "array" in the sense of RFC 4627.
172
+ # Returns the parsed value and any trailing tokens.
173
+ def arrparse(ts)
174
+ ts = eat('[', ts)
175
+ arr = []
176
+
177
+ if ts[0][0] == ']'
178
+ return arr, ts[1..-1]
179
+ end
180
+
181
+ v, ts = valparse(ts)
182
+ arr << v
183
+
184
+ if ts[0][0] == ']'
185
+ return arr, ts[1..-1]
186
+ end
187
+
188
+ loop do
189
+ ts = eat(',', ts)
190
+
191
+ v, ts = valparse(ts)
192
+ arr << v
193
+
194
+ if ts[0][0] == ']'
195
+ return arr, ts[1..-1]
196
+ end
197
+ end
198
+ end
199
+
200
+
201
+ def eat(typ, ts)
202
+ if ts[0][0] != typ
203
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
204
+ end
205
+ ts[1..-1]
206
+ end
207
+
208
+
209
+ # Scans s and returns a list of json tokens,
210
+ # excluding white space (as defined in RFC 4627).
211
+ def lex(s)
212
+ ts = []
213
+ while s.length > 0
214
+ typ, lexeme, val = tok(s)
215
+ if typ == nil
216
+ raise Error, "invalid character at #{s[0,10].inspect}"
217
+ end
218
+ if typ != :space
219
+ ts << [typ, lexeme, val]
220
+ end
221
+ s = s[lexeme.length..-1]
222
+ end
223
+ ts
224
+ end
225
+
226
+
227
+ # Scans the first token in s and
228
+ # returns a 3-element list, or nil
229
+ # if s does not begin with a valid token.
230
+ #
231
+ # The first list element is one of
232
+ # '{', '}', ':', ',', '[', ']',
233
+ # :val, :str, and :space.
234
+ #
235
+ # The second element is the lexeme.
236
+ #
237
+ # The third element is the value of the
238
+ # token for :val and :str, otherwise
239
+ # it is the lexeme.
240
+ def tok(s)
241
+ case s[0]
242
+ when ?{ then ['{', s[0,1], s[0,1]]
243
+ when ?} then ['}', s[0,1], s[0,1]]
244
+ when ?: then [':', s[0,1], s[0,1]]
245
+ when ?, then [',', s[0,1], s[0,1]]
246
+ when ?[ then ['[', s[0,1], s[0,1]]
247
+ when ?] then [']', s[0,1], s[0,1]]
248
+ when ?n then nulltok(s)
249
+ when ?t then truetok(s)
250
+ when ?f then falsetok(s)
251
+ when ?" then strtok(s)
252
+ when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]]
253
+ else
254
+ numtok(s)
255
+ end
256
+ end
257
+
258
+
259
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
260
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
261
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
262
+
263
+
264
+ def numtok(s)
265
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
266
+ if m && m.begin(0) == 0
267
+ if !m[2] && !m[3]
268
+ [:val, m[0], Integer(m[0])]
269
+ elsif m[2]
270
+ [:val, m[0], Float(m[0])]
271
+ else
272
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
273
+ end
274
+ else
275
+ []
276
+ end
277
+ end
278
+
279
+
280
+ def strtok(s)
281
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
282
+ if ! m
283
+ raise Error, "invalid string literal at #{abbrev(s)}"
284
+ end
285
+ [:str, m[0], unquote(m[0])]
286
+ end
287
+
288
+
289
+ def abbrev(s)
290
+ t = s[0,10]
291
+ p = t['`']
292
+ t = t[0,p] if p
293
+ t = t + '...' if t.length < s.length
294
+ '`' + t + '`'
295
+ end
296
+
297
+
298
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
299
+ # The rules are different than for Ruby, so we cannot use eval.
300
+ # Unquote will raise an error if q contains control characters.
301
+ def unquote(q)
302
+ q = q[1...-1]
303
+ a = q.dup # allocate a big enough string
304
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
305
+ if rubydoesenc?
306
+ a.force_encoding('UTF-8')
307
+ end
308
+ r, w = 0, 0
309
+ while r < q.length
310
+ c = q[r]
311
+ if c == ?\\
312
+ r += 1
313
+ if r >= q.length
314
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
315
+ end
316
+
317
+ case q[r]
318
+ when ?",?\\,?/,?'
319
+ a[w] = q[r]
320
+ r += 1
321
+ w += 1
322
+ when ?b,?f,?n,?r,?t
323
+ a[w] = Unesc[q[r]]
324
+ r += 1
325
+ w += 1
326
+ when ?u
327
+ r += 1
328
+ uchar = begin
329
+ hexdec4(q[r,4])
330
+ rescue RuntimeError => e
331
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
332
+ end
333
+ r += 4
334
+ if surrogate? uchar
335
+ if q.length >= r+6
336
+ uchar1 = hexdec4(q[r+2,4])
337
+ uchar = subst(uchar, uchar1)
338
+ if uchar != Ucharerr
339
+ # A valid pair; consume.
340
+ r += 6
341
+ end
342
+ end
343
+ end
344
+ if rubydoesenc?
345
+ a[w] = '' << uchar
346
+ w += 1
347
+ else
348
+ w += ucharenc(a, w, uchar)
349
+ end
350
+ else
351
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
352
+ end
353
+ elsif c == ?" || c < Spc
354
+ raise Error, "invalid character in string literal \"#{q}\""
355
+ else
356
+ # Copy anything else byte-for-byte.
357
+ # Valid UTF-8 will remain valid UTF-8.
358
+ # Invalid UTF-8 will remain invalid UTF-8.
359
+ # In ruby >= 1.9, c is a codepoint, not a byte,
360
+ # in which case this is still what we want.
361
+ a[w] = c
362
+ r += 1
363
+ w += 1
364
+ end
365
+ end
366
+ a[0,w]
367
+ end
368
+
369
+
370
+ # Encodes unicode character u as UTF-8
371
+ # bytes in string a at position i.
372
+ # Returns the number of bytes written.
373
+ def ucharenc(a, i, u)
374
+ if u <= Uchar1max
375
+ a[i] = (u & 0xff).chr
376
+ 1
377
+ elsif u <= Uchar2max
378
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
379
+ a[i+1] = (Utagx | (u&Umaskx)).chr
380
+ 2
381
+ elsif u <= Uchar3max
382
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
383
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
384
+ a[i+2] = (Utagx | (u&Umaskx)).chr
385
+ 3
386
+ else
387
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
388
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
389
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
390
+ a[i+3] = (Utagx | (u&Umaskx)).chr
391
+ 4
392
+ end
393
+ end
394
+
395
+
396
+ def hexdec4(s)
397
+ if s.length != 4
398
+ raise Error, 'short'
399
+ end
400
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
401
+ end
402
+
403
+
404
+ def subst(u1, u2)
405
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
406
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
407
+ end
408
+ return Ucharerr
409
+ end
410
+
411
+
412
+ def surrogate?(u)
413
+ Usurr1 <= u && u < Usurr3
414
+ end
415
+
416
+
417
+ def nibble(c)
418
+ if ?0 <= c && c <= ?9 then c.ord - ?0.ord
419
+ elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10
420
+ elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
421
+ else
422
+ raise Error, "invalid hex code #{c}"
423
+ end
424
+ end
425
+
426
+
427
+ def objenc(x)
428
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
429
+ end
430
+
431
+
432
+ def arrenc(a)
433
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
434
+ end
435
+
436
+
437
+ def keyenc(k)
438
+ case k
439
+ when String then strenc(k)
440
+ else
441
+ raise Error, "Hash key is not a string: #{k.inspect}"
442
+ end
443
+ end
444
+
445
+
446
+ def strenc(s)
447
+ t = StringIO.new
448
+ t.putc(?")
449
+ r = 0
450
+
451
+ while r < s.length
452
+ case s[r]
453
+ when ?" then t.print('\\"')
454
+ when ?\\ then t.print('\\\\')
455
+ when ?\b then t.print('\\b')
456
+ when ?\f then t.print('\\f')
457
+ when ?\n then t.print('\\n')
458
+ when ?\r then t.print('\\r')
459
+ when ?\t then t.print('\\t')
460
+ else
461
+ c = s[r]
462
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
463
+ if rubydoesenc?
464
+ begin
465
+ # c.ord will raise an error if c is invalid UTF-8
466
+ if c.ord < Spc.ord
467
+ c = "\\u%04x" % [c.ord]
468
+ end
469
+ t.write(c)
470
+ rescue
471
+ t.write(Ustrerr)
472
+ end
473
+ elsif c < Spc
474
+ t.write("\\u%04x" % c)
475
+ elsif Spc <= c && c <= ?~
476
+ t.putc(c)
477
+ else
478
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
479
+ r += n - 1 # r is incremented below
480
+ end
481
+ end
482
+ r += 1
483
+ end
484
+ t.putc(?")
485
+ t.string
486
+ end
487
+
488
+
489
+ def numenc(x)
490
+ if ((x.nan? || x.infinite?) rescue false)
491
+ raise Error, "Numeric cannot be represented: #{x}"
492
+ end
493
+ "#{x}"
494
+ end
495
+
496
+
497
+ # Copies the valid UTF-8 bytes of a single character
498
+ # from string s at position i to I/O object t, and
499
+ # returns the number of bytes copied.
500
+ # If no valid UTF-8 char exists at position i,
501
+ # ucharcopy writes Ustrerr and returns 1.
502
+ def ucharcopy(t, s, i)
503
+ n = s.length - i
504
+ raise Utf8Error if n < 1
505
+
506
+ c0 = s[i].ord
507
+
508
+ # 1-byte, 7-bit sequence?
509
+ if c0 < Utagx
510
+ t.putc(c0)
511
+ return 1
512
+ end
513
+
514
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
515
+
516
+ raise Utf8Error if n < 2 # need continuation byte
517
+ c1 = s[i+1].ord
518
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
519
+
520
+ # 2-byte, 11-bit sequence?
521
+ if c0 < Utag3
522
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
523
+ t.putc(c0)
524
+ t.putc(c1)
525
+ return 2
526
+ end
527
+
528
+ # need second continuation byte
529
+ raise Utf8Error if n < 3
530
+
531
+ c2 = s[i+2].ord
532
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
533
+
534
+ # 3-byte, 16-bit sequence?
535
+ if c0 < Utag4
536
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
537
+ raise Utf8Error if u <= Uchar2max
538
+ t.putc(c0)
539
+ t.putc(c1)
540
+ t.putc(c2)
541
+ return 3
542
+ end
543
+
544
+ # need third continuation byte
545
+ raise Utf8Error if n < 4
546
+ c3 = s[i+3].ord
547
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
548
+
549
+ # 4-byte, 21-bit sequence?
550
+ if c0 < Utag5
551
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
552
+ raise Utf8Error if u <= Uchar3max
553
+ t.putc(c0)
554
+ t.putc(c1)
555
+ t.putc(c2)
556
+ t.putc(c3)
557
+ return 4
558
+ end
559
+
560
+ raise Utf8Error
561
+ rescue Utf8Error
562
+ t.write(Ustrerr)
563
+ return 1
564
+ end
565
+
566
+
567
+ def rubydoesenc?
568
+ ::String.method_defined?(:force_encoding)
569
+ end
570
+
571
+
572
+ class Utf8Error < ::StandardError
573
+ end
574
+
575
+
576
+ class Error < ::StandardError
577
+ end
578
+
579
+
580
+ Utagx = 0b1000_0000
581
+ Utag2 = 0b1100_0000
582
+ Utag3 = 0b1110_0000
583
+ Utag4 = 0b1111_0000
584
+ Utag5 = 0b1111_1000
585
+ Umaskx = 0b0011_1111
586
+ Umask2 = 0b0001_1111
587
+ Umask3 = 0b0000_1111
588
+ Umask4 = 0b0000_0111
589
+ Uchar1max = (1<<7) - 1
590
+ Uchar2max = (1<<11) - 1
591
+ Uchar3max = (1<<16) - 1
592
+ Ucharerr = 0xFFFD # unicode "replacement char"
593
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
594
+ Usurrself = 0x10000
595
+ Usurr1 = 0xd800
596
+ Usurr2 = 0xdc00
597
+ Usurr3 = 0xe000
598
+
599
+ Spc = ' '[0]
600
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
601
+ end
602
+ end
@@ -9,15 +9,13 @@ module Papertrail
9
9
  end
10
10
 
11
11
  def search
12
- response = @connection.get('/api/v1/events/search.json') do |r|
13
- r.params = @options.dup
14
-
15
- r.params[:q] = @query if @query
16
- r.params[:min_id] = @max_id if @max_id
17
- end
12
+ params = @options.dup
13
+ params[:q] = @query if @query
14
+ params[:min_id] = @max_id if @max_id
18
15
 
16
+ response = @connection.get('/api/v1/events/search.json', params)
19
17
  @max_id = response.body['max_id']
20
18
  Papertrail::SearchResult.new(response.body)
21
19
  end
22
20
  end
23
- end
21
+ end
data/papertrail.gemspec CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'papertrail'
16
- s.version = '0.9.7'
17
- s.date = '2013-02-18'
16
+ s.version = '0.9.8'
17
+ s.date = '2014-03-04'
18
18
  s.rubyforge_project = 'papertrail'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -49,11 +49,7 @@ Gem::Specification.new do |s|
49
49
 
50
50
  ## List your runtime dependencies here. Runtime dependencies are those
51
51
  ## that are needed for an end user to actually USE your code.
52
- s.add_dependency('addressable')
53
- s.add_dependency('yajl-ruby')
54
52
  s.add_dependency('chronic')
55
- s.add_dependency('faraday', [ '>= 0.6', '< 0.9' ])
56
- s.add_dependency('faraday_middleware', [ '~> 0.8.4' ])
57
53
 
58
54
  ## List your development dependencies here. Development dependencies are
59
55
  ## those that are only needed during development
@@ -83,6 +79,8 @@ Gem::Specification.new do |s|
83
79
  lib/papertrail/cli_remove_system.rb
84
80
  lib/papertrail/connection.rb
85
81
  lib/papertrail/event.rb
82
+ lib/papertrail/http_client.rb
83
+ lib/papertrail/okjson.rb
86
84
  lib/papertrail/search_query.rb
87
85
  lib/papertrail/search_result.rb
88
86
  papertrail.gemspec
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: papertrail
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 43
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
8
  - 9
8
- - 7
9
- version: 0.9.7
9
+ - 8
10
+ version: 0.9.8
10
11
  platform: ruby
11
12
  authors:
12
13
  - Papertrail
@@ -14,78 +15,22 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2013-02-18 00:00:00 -08:00
18
- default_executable: papertrail
18
+ date: 2014-03-04 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- type: :runtime
22
- version_requirements: &id001 !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- segments:
27
- - 0
28
- version: "0"
29
- name: addressable
30
- requirement: *id001
31
- prerelease: false
32
- - !ruby/object:Gem::Dependency
33
- type: :runtime
34
- version_requirements: &id002 !ruby/object:Gem::Requirement
35
- requirements:
36
- - - ">="
37
- - !ruby/object:Gem::Version
38
- segments:
39
- - 0
40
- version: "0"
41
- name: yajl-ruby
42
- requirement: *id002
43
- prerelease: false
44
- - !ruby/object:Gem::Dependency
45
- type: :runtime
46
- version_requirements: &id003 !ruby/object:Gem::Requirement
47
- requirements:
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- segments:
51
- - 0
52
- version: "0"
53
21
  name: chronic
54
- requirement: *id003
55
22
  prerelease: false
56
- - !ruby/object:Gem::Dependency
57
- type: :runtime
58
- version_requirements: &id004 !ruby/object:Gem::Requirement
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
59
25
  requirements:
60
26
  - - ">="
61
27
  - !ruby/object:Gem::Version
28
+ hash: 3
62
29
  segments:
63
30
  - 0
64
- - 6
65
- version: "0.6"
66
- - - <
67
- - !ruby/object:Gem::Version
68
- segments:
69
- - 0
70
- - 9
71
- version: "0.9"
72
- name: faraday
73
- requirement: *id004
74
- prerelease: false
75
- - !ruby/object:Gem::Dependency
31
+ version: "0"
76
32
  type: :runtime
77
- version_requirements: &id005 !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ~>
80
- - !ruby/object:Gem::Version
81
- segments:
82
- - 0
83
- - 8
84
- - 4
85
- version: 0.8.4
86
- name: faraday_middleware
87
- requirement: *id005
88
- prerelease: false
33
+ version_requirements: *id001
89
34
  description: Command-line client for Papertrail hosted log management service. Tails and searches app server logs and system syslog. Supports Boolean search and works with grep and pipe output (Unix).
90
35
  email: troy@sevenscale.com
91
36
  executables:
@@ -118,10 +63,11 @@ files:
118
63
  - lib/papertrail/cli_remove_system.rb
119
64
  - lib/papertrail/connection.rb
120
65
  - lib/papertrail/event.rb
66
+ - lib/papertrail/http_client.rb
67
+ - lib/papertrail/okjson.rb
121
68
  - lib/papertrail/search_query.rb
122
69
  - lib/papertrail/search_result.rb
123
70
  - papertrail.gemspec
124
- has_rdoc: true
125
71
  homepage: http://github.com/papertrail/papertrail-cli
126
72
  licenses: []
127
73
 
@@ -131,23 +77,27 @@ rdoc_options:
131
77
  require_paths:
132
78
  - lib
133
79
  required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
134
81
  requirements:
135
82
  - - ">="
136
83
  - !ruby/object:Gem::Version
84
+ hash: 3
137
85
  segments:
138
86
  - 0
139
87
  version: "0"
140
88
  required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
141
90
  requirements:
142
91
  - - ">="
143
92
  - !ruby/object:Gem::Version
93
+ hash: 3
144
94
  segments:
145
95
  - 0
146
96
  version: "0"
147
97
  requirements: []
148
98
 
149
99
  rubyforge_project: papertrail
150
- rubygems_version: 1.3.6
100
+ rubygems_version: 1.8.24
151
101
  signing_key:
152
102
  specification_version: 2
153
103
  summary: Command-line client for Papertrail hosted log management service.