logeater 0.2.4 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee9771f19fbb6a41d6a072d2f1a1fb8280cdcce7
4
- data.tar.gz: 2b82746a2c79e407ef544a1472ad31826a9f2587
3
+ metadata.gz: 678d8cc0af848644fd37cf1762d97a9d7779bd5a
4
+ data.tar.gz: 0ac115076cd9daedc7b74d4f01dd04e2d63e56a6
5
5
  SHA512:
6
- metadata.gz: da4a22896028c127870e05217d3c9125dc73ddc9e28b00afa4dea52d8dca980240ace54dba9f1aaf08d0a628aff861e423957f5080b5774a627a30db7653cdea
7
- data.tar.gz: d12c10c9df5a1a26fc1bcc36adfc8fb276046c43ef23bfc469057de9c72f8e7759305c53f576678667ecdaf3a452019289001f7cf9b78b53246122a2b8993f7c
6
+ metadata.gz: e3d70df2e56453afba0d382252555801e17e8d7c2ec7d83da83dc147e91bc61f6fd8dc4127bfaadf9c2d71bcdce561cd8c09403a5cc83a129443031ed3ff1545
7
+ data.tar.gz: 0085e7c733283ecb91ec507a5de05bb53bd2a2d1b4a63a23431cfb750505cd489e2425a63772da3f4f9cb92a274743551ac0a7c112cc0570d7f3c5813cc3272c
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
+ /.ruby-version
4
5
  /_yardoc/
5
6
  /coverage/
6
7
  /doc/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Logeater
2
2
 
3
- Parses log files and imports them into a database
3
+ Parses log files and imports them into a database or converts them to json
4
4
 
5
5
  ### Features
6
6
 
@@ -12,30 +12,17 @@ Parses log files and imports them into a database
12
12
 
13
13
  ### Usage
14
14
 
15
- Clone the gem
16
15
 
17
- git clone git@github.com:concordia-publishing-house/logeater.git
18
- bundle
16
+ ###### Importing log files
19
17
 
20
- Create the development database
18
+ gem install logeater
19
+ DATABASE_URL=<production database url> logeater import <app name> <path to logs>/*.gz
21
20
 
22
- bundle exec rake db:create db:migrate
21
+ ###### Converting log files to JSON
23
22
 
24
- Install the gem
23
+ gem install logeater
24
+ logeater parse <app name> <path to logs>/*.gz > parsed-logs.json
25
25
 
26
- bundle exec rake install
27
-
28
- Import log files
29
-
30
- logeater my_app ~/Desktop/logs/*.gz
31
-
32
-
33
- ### To Do
34
-
35
- - Set up databases without cloning the gem?
36
- - Import to a [Heroku Postgres database](https://dashboard.heroku.com/apps/logs-production)?
37
- - Parse other kinds of logs?
38
- - Collect other data from Rails logs?
39
26
 
40
27
 
41
28
  ### Contributing
@@ -3,7 +3,7 @@ class CreateRequests < ActiveRecord::Migration
3
3
  create_table :requests do |t|
4
4
  t.string :app, null: false
5
5
  t.string :logfile, null: false
6
-
6
+
7
7
  t.string :uuid, null: false
8
8
  t.string :subdomain
9
9
  t.timestamp :started_at
@@ -18,10 +18,10 @@ class CreateRequests < ActiveRecord::Migration
18
18
  t.string :format
19
19
  t.integer :http_status
20
20
  t.string :http_response
21
-
21
+
22
22
  t.timestamps
23
23
  end
24
-
24
+
25
25
  add_index :requests, :app
26
26
  add_index :requests, :logfile
27
27
  add_index :requests, :uuid, unique: true
@@ -0,0 +1,7 @@
1
+ class AddIndexes < ActiveRecord::Migration
2
+ def change
3
+ add_index :requests, :completed_at
4
+ add_index :requests, :subdomain
5
+ add_index :requests, :params, using: :gin
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class DropHttpResponse < ActiveRecord::Migration
2
+ def up
3
+ remove_column :requests, :http_response
4
+ end
5
+
6
+ def down
7
+ add_column :requests, :http_response, :string
8
+ end
9
+ end
data/db/schema.rb CHANGED
@@ -11,38 +11,40 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20150602022241) do
14
+ ActiveRecord::Schema.define(version: 20160502131002) do
15
15
 
16
16
  # These are extensions that must be enabled in order to support this database
17
17
  enable_extension "plpgsql"
18
18
 
19
19
  create_table "requests", force: :cascade do |t|
20
- t.string "app", limit: 255, null: false
21
- t.string "logfile", limit: 255, null: false
22
- t.string "uuid", limit: 255, null: false
23
- t.string "subdomain", limit: 255
20
+ t.string "app", null: false
21
+ t.string "logfile", null: false
22
+ t.string "uuid", null: false
23
+ t.string "subdomain"
24
24
  t.datetime "started_at"
25
25
  t.datetime "completed_at"
26
26
  t.integer "duration"
27
- t.string "http_method", limit: 255
27
+ t.string "http_method"
28
28
  t.text "path"
29
29
  t.jsonb "params"
30
- t.string "controller", limit: 255
31
- t.string "action", limit: 255
32
- t.string "remote_ip", limit: 255
33
- t.string "format", limit: 255
30
+ t.string "controller"
31
+ t.string "action"
32
+ t.string "remote_ip"
33
+ t.string "format"
34
34
  t.integer "http_status"
35
- t.string "http_response", limit: 255
36
- t.datetime "created_at", null: false
37
- t.datetime "updated_at", null: false
35
+ t.datetime "created_at"
36
+ t.datetime "updated_at"
38
37
  t.integer "user_id"
39
38
  t.boolean "tester_bar"
40
39
  end
41
40
 
42
41
  add_index "requests", ["app"], name: "index_requests_on_app", using: :btree
42
+ add_index "requests", ["completed_at"], name: "index_requests_on_completed_at", using: :btree
43
43
  add_index "requests", ["controller", "action"], name: "index_requests_on_controller_and_action", using: :btree
44
44
  add_index "requests", ["http_status"], name: "index_requests_on_http_status", using: :btree
45
45
  add_index "requests", ["logfile"], name: "index_requests_on_logfile", using: :btree
46
+ add_index "requests", ["params"], name: "index_requests_on_params", using: :gin
47
+ add_index "requests", ["subdomain"], name: "index_requests_on_subdomain", using: :btree
46
48
  add_index "requests", ["uuid"], name: "index_requests_on_uuid", unique: true, using: :btree
47
49
 
48
50
  end
data/lib/logeater/cli.rb CHANGED
@@ -57,14 +57,12 @@ module Logeater
57
57
  reader = Logeater::Reader.new(app, file, options.slice(:progress, :verbose))
58
58
  reader.remove_existing_entries!
59
59
 
60
- started_count = Logeater::Request.count
61
60
  started_at = Time.now
62
- reader.import
61
+ count = reader.import
63
62
  finished_at = Time.now
64
- finished_count = Logeater::Request.count
65
63
 
66
64
  $stderr.puts " > \e[34mImported \e[1m%d\e[0;34m requests in \e[1m%.2f\e[0;34m seconds (%d of %d)\e[0m\n\n" % [
67
- finished_count - started_count,
65
+ count,
68
66
  finished_at - started_at,
69
67
  i + 1,
70
68
  files.length ]
@@ -3,15 +3,15 @@ require "ripper"
3
3
  module Logeater
4
4
  class ParamsParser
5
5
  attr_reader :params
6
-
6
+
7
7
  def initialize(params)
8
8
  @params = params
9
9
  end
10
-
10
+
11
11
  def parse!
12
12
  identify tokenize_hash(clean(params))
13
13
  end
14
-
14
+
15
15
  def clean(params)
16
16
  loop do
17
17
  result = params.gsub(/\#<((?:[\w]|::)+):[^<>]+>/) { "\"#{$1}\"" }
@@ -20,46 +20,46 @@ module Logeater
20
20
  end
21
21
  params
22
22
  end
23
-
23
+
24
24
  private
25
-
25
+
26
26
  def tokenize_hash(ruby)
27
27
  sexp = Ripper.sexp(ruby)
28
28
  raise Parser::MalformedParameters.new(ruby) unless sexp
29
-
29
+
30
30
  # [:program, [[:hash, ... ]]]
31
31
  sexp[1][0]
32
32
  end
33
-
33
+
34
34
  def identify(sexp)
35
35
  case sexp[0]
36
-
36
+
37
37
  # [:string_literal, [:string_content, [:@tstring_content, "utf8", [1, 2]]]]
38
38
  # [:string_literal, [:string_content]]
39
39
  when :string_literal then sexp[1][1] ? sexp[1][1][1] : ""
40
-
40
+
41
41
  # [:@int, "10", [1, 14]]
42
42
  when :@int then sexp[1].to_i
43
-
43
+
44
44
  # [:@float, "10.56", [1, 14]]
45
45
  when :@float then sexp[1].to_f
46
-
46
+
47
47
  # [:unary, :-@, [:@float, \\\"173.41\\\", [1, 17285]]]
48
48
  when :unary then
49
49
  return -identify(sexp[2]) if sexp[1] == :-@
50
50
  raise Parser::ParserNotImplemented, "Unknown unary operator: #{sexp[1].inspect}"
51
-
51
+
52
52
  # [:var_ref, [:@kw, "true", [1, 12]]]
53
53
  when :var_ref then
54
54
  return true if sexp[1][1] == "true"
55
55
  return false if sexp[1][1] == "false"
56
56
  return nil if sexp[1][1] == "nil"
57
57
  raise Parser::ParserNotImplemented, "Unknown variable: #{sexp[1].inspect}"
58
-
58
+
59
59
  # [:array, [[:@int, "1", [1, 9]], [:@int, "4", [1, 12]]]]
60
60
  # [:array, nil]
61
61
  when :array then sexp[1] ? sexp[1].map { |sexp| identify(sexp) } : []
62
-
62
+
63
63
  # [:hash,
64
64
  # [:assoclist_from_args,
65
65
  # [[:assoc_new,
@@ -67,7 +67,7 @@ module Logeater
67
67
  # [:string_literal, [:string_content, [:@tstring_content, "✓", [1, 12]]]]]]]]]
68
68
  # [:hash, nil]
69
69
  when :hash then sexp[1] ? sexp[1][1].each_with_object({}) { |(_, key, value), hash| hash[identify(key)] = identify(value) } : {}
70
-
70
+
71
71
  else
72
72
  raise Parser::ParserNotImplemented, "I don't know how to identify #{sexp.inspect}"
73
73
  nil
@@ -76,6 +76,6 @@ module Logeater
76
76
  raise Parser::ParserNotImplemented, "An exception occurred when parsing #{sexp.inspect}\n#{$!.class.name}: #{$!.message}"
77
77
  nil
78
78
  end
79
-
79
+
80
80
  end
81
81
  end
@@ -5,52 +5,52 @@ require "logeater/parser_errors"
5
5
 
6
6
  module Logeater
7
7
  class Parser
8
-
9
-
8
+
9
+
10
10
  LINE_MATCHER = /^
11
11
  [A-Z],\s
12
12
  \[(?<timestamp>[^\s\]]+)(?:\s[^\]]*)?\]\s+
13
13
  (?<log_level>[A-Z]+)\s+\-\-\s:\s+
14
14
  (?<message>.*)
15
15
  $/x.freeze
16
-
16
+
17
17
  REQUEST_LINE_MATCHER = /^
18
18
  \[(?<subdomain>[^\]]+)\]\s
19
19
  \[(?<uuid>[\w\-]{36})\]\s+
20
20
  (?:\[(?:guest|user\.(?<user_id>\d+)(?<tester_bar>:cph)?)\]\s+)?
21
21
  (?<message>.*)
22
22
  $/x.freeze
23
-
23
+
24
24
  REQUEST_STARTED_MATCHER = /^
25
25
  Started\s
26
26
  (?<http_method>[A-Z]+)\s
27
27
  "(?<path>[^"]+)"\sfor\s
28
28
  (?<remote_ip>[\d\.]+)
29
29
  /x.freeze
30
-
30
+
31
31
  REQUEST_CONTROLLER_MATCHER = /^
32
32
  Processing\sby\s
33
33
  (?<controller>[A-Za-z0-9:]+)\#
34
34
  (?<action>[a-z_0-9]+)\sas\s
35
35
  (?<format>.*)
36
36
  /x.freeze
37
-
37
+
38
38
  REQUEST_PARAMETERS_MATCHER = /^
39
39
  Parameters:\s
40
40
  (?<params>\{.*\})
41
41
  $/x.freeze
42
-
42
+
43
43
  REQUEST_COMPLETED_MATCHER = /^
44
44
  Completed\s
45
45
  (?<http_status>\d\d\d)\s
46
46
  (?:(?<http_response>.*)\s)?in\s
47
47
  (?<duration>[\d\.]+)(?<units>ms)\b
48
48
  /x.freeze # optional: (Views: 0.1ms | ActiveRecord: 50.0ms)
49
-
49
+
50
50
  def parse!(line)
51
51
  match = line.match LINE_MATCHER
52
52
  raise UnmatchedLine.new(line) unless match
53
-
53
+
54
54
  result = {
55
55
  type: :generic,
56
56
  timestamp: match["timestamp"],
@@ -59,13 +59,13 @@ module Logeater
59
59
  }.merge(
60
60
  parse_message(match["message"]))
61
61
  end
62
-
62
+
63
63
  def parse_message(message)
64
64
  match = message.match REQUEST_LINE_MATCHER
65
65
  return {} unless match
66
-
66
+
67
67
  message = match["message"]
68
-
68
+
69
69
  { subdomain: match["subdomain"],
70
70
  uuid: match["uuid"],
71
71
  type: :request_line,
@@ -75,37 +75,37 @@ module Logeater
75
75
  }.merge(
76
76
  parse_message_extra(message))
77
77
  end
78
-
78
+
79
79
  def parse_message_extra(message)
80
80
  match = message.match(REQUEST_STARTED_MATCHER)
81
81
  return parse_request_started_message(match) if match
82
-
82
+
83
83
  match = message.match(REQUEST_CONTROLLER_MATCHER)
84
84
  return parse_request_controller_message(match) if match
85
-
85
+
86
86
  match = message.match(REQUEST_PARAMETERS_MATCHER)
87
87
  return parse_request_params_message(match) if match
88
-
88
+
89
89
  match = message.match(REQUEST_COMPLETED_MATCHER)
90
90
  return parse_request_completed_message(match) if match
91
-
91
+
92
92
  {}
93
93
  end
94
-
94
+
95
95
  def parse_request_started_message(match)
96
96
  { type: :request_started,
97
97
  http_method: match["http_method"],
98
98
  path: parsed_uri[match["path"]],
99
99
  remote_ip: match["remote_ip"] }
100
100
  end
101
-
101
+
102
102
  def parse_request_controller_message(match)
103
103
  { type: :request_controller,
104
104
  controller: normalized_controller_name[match["controller"]],
105
105
  action: match["action"],
106
106
  format: match["format"] }
107
107
  end
108
-
108
+
109
109
  def parse_request_params_message(match)
110
110
  { type: :request_params,
111
111
  params: ParamsParser.new(match["params"]).parse! }
@@ -113,35 +113,35 @@ module Logeater
113
113
  log "Unable to parse parameters: #{match["params"].inspect}"
114
114
  { params: match["params"] }
115
115
  end
116
-
116
+
117
117
  def parse_request_completed_message(match)
118
118
  { type: :request_completed,
119
119
  http_status: match["http_status"].to_i,
120
120
  http_response: match["http_response"],
121
121
  duration: match["duration"].to_i }
122
122
  end
123
-
124
-
125
-
123
+
124
+
125
+
126
126
  def log(statement)
127
127
  $stderr.puts "\e[33m#{statement}\e[0m"
128
128
  end
129
-
130
-
131
-
129
+
130
+
131
+
132
132
  def initialize
133
133
  @normalized_controller_name = Hash.new do |hash, controller_name|
134
134
  hash[controller_name] = controller_name.underscore.gsub(/_controller$/, "")
135
135
  end
136
-
136
+
137
137
  @parsed_uri = Hash.new do |hash, uri|
138
138
  hash[uri] = Addressable::URI.parse(uri).path
139
139
  end
140
140
  end
141
-
141
+
142
142
  private
143
143
  attr_reader :normalized_controller_name,
144
144
  :parsed_uri
145
-
145
+
146
146
  end
147
147
  end
@@ -1,30 +1,30 @@
1
1
  module Logeater
2
2
  class Parser
3
-
3
+
4
4
  class Error < ::ArgumentError
5
5
  def initialize(message, input)
6
6
  super "#{message}: #{input.inspect}"
7
7
  end
8
8
  end
9
-
9
+
10
10
  class UnmatchedLine < Error
11
11
  def initialize(input)
12
12
  super "Unmatched line", input
13
13
  end
14
14
  end
15
-
15
+
16
16
  class MalformedTimestamp < Error
17
17
  def initialize(input)
18
18
  super "Malformed timestamp", input
19
19
  end
20
20
  end
21
-
21
+
22
22
  class MalformedParameters < Error
23
23
  def initialize(input)
24
24
  super "Malformed parameters", input
25
25
  end
26
26
  end
27
-
27
+
28
28
  class ParserNotImplemented < Error
29
29
  def initialize(input)
30
30
  super "Unable to parse", input
@@ -6,7 +6,7 @@ require "oj"
6
6
  module Logeater
7
7
  class Reader
8
8
  attr_reader :app, :path, :filename, :batch_size
9
-
9
+
10
10
  def initialize(app, path, options={})
11
11
  @app = app
12
12
  @path = path
@@ -15,25 +15,28 @@ module Logeater
15
15
  @show_progress = options.fetch :progress, false
16
16
  @batch_size = options.fetch :batch_size, 500
17
17
  @verbose = options.fetch :verbose, false
18
+ @count = 0
18
19
  @requests = {}
19
20
  @completed_requests = []
20
21
  end
21
-
22
-
23
-
22
+
23
+
24
+
24
25
  def reimport
25
26
  remove_existing_entries!
26
27
  import
27
28
  end
28
-
29
+
29
30
  def import
31
+ @count = 0
30
32
  each_request do |attributes|
31
33
  completed_requests.push Logeater::Request.new(attributes)
32
34
  save! if completed_requests.length >= batch_size
33
35
  end
34
36
  save!
37
+ @count
35
38
  end
36
-
39
+
37
40
  def parse(to: $stdout)
38
41
  to << "["
39
42
  first = true
@@ -49,19 +52,19 @@ module Logeater
49
52
  ensure
50
53
  to << "]"
51
54
  end
52
-
55
+
53
56
  def remove_existing_entries!
54
57
  Logeater::Request.where(app: app, logfile: filename).delete_all
55
58
  end
56
-
59
+
57
60
  def show_progress?
58
61
  @show_progress
59
62
  end
60
-
63
+
61
64
  def verbose?
62
65
  @verbose
63
66
  end
64
-
67
+
65
68
  def each_line
66
69
  File.open(path) do |file|
67
70
  io = File.extname(path) == ".gz" ? Zlib::GzipReader.new(file) : file
@@ -74,7 +77,7 @@ module Logeater
74
77
  end
75
78
  end
76
79
  alias :scan :each_line
77
-
80
+
78
81
  def each_request
79
82
  count = 0
80
83
  each_line do |line|
@@ -85,63 +88,64 @@ module Logeater
85
88
  end
86
89
  count
87
90
  end
88
-
89
-
90
-
91
+
92
+
93
+
91
94
  private
92
95
  attr_reader :parser, :requests, :completed_requests
93
-
96
+
94
97
  def process_line!(line, &block)
95
98
  attributes = parser.parse!(line)
96
-
99
+
97
100
  return if [:generic, :request_line].member? attributes[:type]
98
-
101
+
99
102
  if attributes[:type] == :request_started
100
103
  requests[attributes[:uuid]] = attributes
101
104
  .slice(:uuid, :subdomain, :http_method, :path, :remote_ip, :user_id, :tester_bar)
102
105
  .merge(started_at: attributes[:timestamp], logfile: filename, app: app)
103
106
  return
104
107
  end
105
-
108
+
106
109
  request_attributes = requests[attributes[:uuid]]
107
110
  unless request_attributes
108
111
  log "Attempting to record #{attributes[:type].inspect}; but there is no request started with UUID #{attributes[:uuid].inspect}" if verbose?
109
112
  return
110
113
  end
111
-
114
+
112
115
  case attributes[:type]
113
116
  when :request_controller
114
117
  request_attributes.merge! attributes.slice(:controller, :action, :format)
115
-
118
+
116
119
  when :request_params
117
120
  request_attributes.merge! attributes.slice(:params)
118
-
121
+
119
122
  when :request_completed
120
123
  request_attributes.merge! attributes
121
- .slice(:http_status, :http_response, :duration)
124
+ .slice(:http_status, :duration)
122
125
  .merge(completed_at: attributes[:timestamp])
123
-
126
+
124
127
  yield request_attributes
125
128
  requests.delete attributes[:uuid]
126
129
  end
127
-
130
+
128
131
  rescue Logeater::Parser::UnmatchedLine
129
132
  $stderr.puts "\e[90m#{$!.message}\e[0m" if verbose?
130
133
  rescue Logeater::Parser::Error
131
134
  log $!.message
132
135
  end
133
-
136
+
134
137
  def save!
135
138
  return if completed_requests.empty?
136
- Logeater::Request.import(completed_requests)
139
+ result = Logeater::Request.import(completed_requests)
140
+ @count += result.num_inserts
137
141
  completed_requests.clear
138
142
  end
139
-
140
-
141
-
143
+
144
+
145
+
142
146
  def log(statement)
143
147
  $stderr.puts "\e[33m#{statement}\e[0m"
144
148
  end
145
-
149
+
146
150
  end
147
151
  end
@@ -1,3 +1,3 @@
1
1
  module Logeater
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.0"
3
3
  end
data/logeater.gemspec CHANGED
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency "standalone_migrations", "~> 4.0.0"
25
25
  spec.add_dependency "addressable"
26
26
  spec.add_dependency "ruby-progressbar"
27
- spec.add_dependency "activerecord-import", "~> 0.10.0"
27
+ spec.add_dependency "activerecord-import", "~> 0.10"
28
28
  spec.add_dependency "thor", "~> 0.19.1"
29
- spec.add_dependency "oj", "~> 2.12.14"
29
+ spec.add_dependency "oj", "~> 2.15"
30
30
 
31
31
  spec.add_development_dependency "bundler", "~> 1.7"
32
32
  spec.add_development_dependency "rake", "~> 10.0"
@@ -2,27 +2,27 @@ require "test_helper"
2
2
 
3
3
  class LogeaterTest < ActiveSupport::TestCase
4
4
  attr_reader :logfile
5
-
6
-
5
+
6
+
7
7
  context "Given the log of a single request, it" do
8
8
  setup do
9
9
  @logfile = File.expand_path("../../data/single_request.log", __FILE__)
10
10
  end
11
-
11
+
12
12
  should "identify the name of the logfile" do
13
13
  assert_equal "single_request.log", reader.filename
14
14
  end
15
-
15
+
16
16
  should "create an entry in the database" do
17
17
  assert_difference "Logeater::Request.count", +1 do
18
18
  reader.import
19
19
  end
20
20
  end
21
-
21
+
22
22
  should "set all the attributes" do
23
23
  reader.import
24
24
  request = Logeater::Request.first
25
-
25
+
26
26
  params = {"refresh_page" => "true", "id" => "1035826228"}
27
27
  assert_equal "test", request.app
28
28
  assert_equal "single_request.log", request.logfile
@@ -39,42 +39,41 @@ class LogeaterTest < ActiveSupport::TestCase
39
39
  assert_equal "71.218.222.249", request.remote_ip
40
40
  assert_equal "JS", request.format
41
41
  assert_equal 200, request.http_status
42
- assert_equal "OK", request.http_response
43
42
  end
44
-
43
+
45
44
  should "erase any entries that had already been imported with that app and filename" do
46
45
  Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "1")
47
46
  Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "2")
48
47
  Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "3")
49
-
48
+
50
49
  assert_difference "Logeater::Request.count", -2 do
51
50
  reader.reimport
52
51
  end
53
52
  end
54
53
  end
55
-
56
-
54
+
55
+
57
56
  context "Given a gzipped logfile, it" do
58
57
  setup do
59
58
  @logfile = File.expand_path("../../data/single_request.gz", __FILE__)
60
59
  end
61
-
60
+
62
61
  should "create an entry in the database" do
63
62
  assert_difference "Logeater::Request.count", +1 do
64
63
  reader.import
65
64
  end
66
65
  end
67
66
  end
68
-
69
-
67
+
68
+
70
69
  private
71
-
70
+
72
71
  def app
73
72
  "test"
74
73
  end
75
-
74
+
76
75
  def reader
77
76
  Logeater::Reader.new(app, logfile)
78
77
  end
79
-
78
+
80
79
  end
data/test/test_helper.rb CHANGED
@@ -18,13 +18,13 @@ DatabaseCleaner.clean
18
18
  DatabaseCleaner.strategy = :transaction
19
19
 
20
20
  class ActiveSupport::TestCase
21
-
21
+
22
22
  setup do
23
23
  DatabaseCleaner.start
24
24
  end
25
-
25
+
26
26
  teardown do
27
27
  DatabaseCleaner.clean
28
28
  end
29
-
29
+
30
30
  end
@@ -1,83 +1,83 @@
1
1
  require "test_helper"
2
2
 
3
3
  class ParamsParserTest < ActiveSupport::TestCase
4
-
5
-
4
+
5
+
6
6
  context "Given a simple hash, it" do
7
7
  should "parse it" do
8
8
  assert_parses '{"utf8"=>"✓"}' => {"utf8"=>"✓"}
9
9
  end
10
-
10
+
11
11
  should "handle integers" do
12
12
  assert_parses '{"person_id"=>10}' => {"person_id"=>10}
13
13
  end
14
-
14
+
15
15
  should "handle floats" do
16
16
  assert_parses '{"person_id"=>10.56}' => {"person_id"=>10.56}
17
17
  end
18
-
18
+
19
19
  should "handle negatives" do
20
20
  assert_parses '{"person_id"=>-10}' => {"person_id"=>-10}
21
21
  assert_parses '{"person_id"=>-10.56}' => {"person_id"=>-10.56}
22
22
  end
23
-
23
+
24
24
  should "handle booleans" do
25
25
  assert_parses '{"visible"=>true}' => {"visible"=>true}
26
26
  end
27
-
27
+
28
28
  should "handle nil" do
29
29
  assert_parses '{"visible"=>nil}' => {"visible"=>nil}
30
30
  end
31
-
31
+
32
32
  should "handle arrays" do
33
33
  assert_parses '{"ids"=>[1, 4]}' => {"ids"=>[1,4]}
34
34
  end
35
-
36
-
37
-
35
+
36
+
37
+
38
38
  should "handle empty strings" do
39
39
  assert_parses '{"visible"=>""}' => {"visible"=>""}
40
40
  end
41
-
41
+
42
42
  should "handle empty arrays" do
43
43
  assert_parses '{"array"=>[]}' => {"array"=>[]}
44
44
  end
45
-
45
+
46
46
  should "handle empty hashes" do
47
47
  assert_parses '{"hash"=>{}}' => {"hash"=>{}}
48
48
  end
49
49
  end
50
-
51
-
50
+
51
+
52
52
  context "Given a hash with more than one key, it" do
53
53
  should "parse it" do
54
54
  assert_parses '{"utf8"=>"✓", "authenticity_token"=>"kDM07..."}' => {"utf8"=>"✓", "authenticity_token"=>"kDM07..."}
55
55
  end
56
56
  end
57
-
58
-
57
+
58
+
59
59
  context "Given a hash with a nested hash, it" do
60
60
  should "handle nested hashes" do
61
61
  assert_parses '{"person"=>{"name"=>"Tim"}}' => {"person"=>{"name"=>"Tim"}}
62
62
  end
63
-
63
+
64
64
  should "handle arrays of nested hashes" do
65
65
  assert_parses '{"people"=>[{"id"=>1},{"id"=>2}]}' => {"people"=>[{"id"=>1},{"id"=>2}]}
66
66
  end
67
67
  end
68
-
69
-
68
+
69
+
70
70
  context "Given a hash with a serialized Ruby object, it" do
71
71
  should "parse it" do
72
72
  assert_parses '{"tempfile"=>#<Tempfile:/tmp/RackMultipart20141213-1847-1c8fpzw>}' => {"tempfile"=>"Tempfile"}
73
73
  end
74
-
74
+
75
75
  should "ignore the object's ivars" do
76
76
  assert_parses '{"photo"=>#<ActionDispatch::Http::UploadedFile:0x007f7c685318a8 @tempfile=#<Tempfile:/tmp/RackMultipart20141213-1847-1c8fpzw>, @original_filename="Martin-008.JPG", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"person[photo]\"; filename=\"Martin-008.JPG\"\\r\\nContent-Type: image/jpeg\\r\\n\">}' => {"photo"=>"ActionDispatch::Http::UploadedFile"}
77
77
  end
78
78
  end
79
-
80
-
79
+
80
+
81
81
  context "Given an invalid input, it" do
82
82
  should "raise MalformedParameters" do
83
83
  assert_raises Logeater::Parser::MalformedParameters do
@@ -85,17 +85,17 @@ class ParamsParserTest < ActiveSupport::TestCase
85
85
  end
86
86
  end
87
87
  end
88
-
89
-
88
+
89
+
90
90
  private
91
-
91
+
92
92
  def assert_parses(params)
93
93
  value, result = params.to_a[0]
94
94
  assert_equal result, parse!(value)
95
95
  end
96
-
96
+
97
97
  def parse!(value)
98
98
  Logeater::ParamsParser.new(value).parse!
99
99
  end
100
-
100
+
101
101
  end
@@ -2,247 +2,247 @@ require "test_helper"
2
2
 
3
3
  class ParserTest < ActiveSupport::TestCase
4
4
  attr_reader :line
5
-
6
-
7
-
5
+
6
+
7
+
8
8
  context "given a line that doesn't start with the generic log prefix, it" do
9
9
  setup do
10
10
  @line = "\n"
11
11
  end
12
-
12
+
13
13
  should "raise UnmatchedLine" do
14
14
  assert_raises Logeater::Parser::UnmatchedLine do
15
15
  parse!
16
16
  end
17
17
  end
18
18
  end
19
-
20
-
21
-
19
+
20
+
21
+
22
22
  context "given any generic log line, it" do
23
23
  setup do
24
24
  @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : the message\n"
25
25
  end
26
-
26
+
27
27
  should "identify the line as :generic" do
28
28
  assert_parses type: :generic
29
29
  end
30
-
30
+
31
31
  should "identify the log level" do
32
32
  assert_parses log_level: "INFO"
33
33
  end
34
-
34
+
35
35
  should "identify the time, including milliseconds" do
36
36
  assert_parses timestamp: "2015-01-10T15:18:05.850839"
37
37
  end
38
-
38
+
39
39
  should "identify the remainder of the log message" do
40
40
  assert_parses message: "the message"
41
41
  end
42
42
  end
43
-
44
-
45
-
43
+
44
+
45
+
46
46
  context "given a log line for a Rails request, it" do
47
47
  setup do
48
48
  @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
49
49
  end
50
-
50
+
51
51
  should "identify the line as :generic" do
52
52
  assert_parses type: :request_line
53
53
  end
54
-
54
+
55
55
  should "identify the subdomain" do
56
56
  assert_parses subdomain: "livingsaviorco"
57
57
  end
58
-
58
+
59
59
  should "identify the request's ID" do
60
60
  assert_parses uuid: "2d89d962-57c4-47c9-a9e9-6a16a5f22a12"
61
61
  end
62
-
62
+
63
63
  should "identify the remainder of the log message" do
64
64
  assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
65
65
  end
66
66
  end
67
-
68
-
69
-
67
+
68
+
69
+
70
70
  context "given a log line for a Rails request" do
71
71
  context "that indicates the current user (who was logged in with the tester bar), it" do
72
72
  setup do
73
73
  @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [user.902544074:cph] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
74
74
  end
75
-
75
+
76
76
  should "identify the user's id" do
77
77
  assert_parses user_id: 902544074
78
78
  end
79
-
79
+
80
80
  should "notice that the user was logged-in with the tester bar" do
81
81
  assert_parses tester_bar: true
82
82
  end
83
-
83
+
84
84
  should "identify the remainder of the log message" do
85
85
  assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
86
86
  end
87
87
  end
88
-
88
+
89
89
  context "that indicates the current user (who was not logged in with the tester bar), it" do
90
90
  setup do
91
91
  @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [user.902544074] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
92
92
  end
93
-
93
+
94
94
  should "identify the user's id" do
95
95
  assert_parses user_id: 902544074
96
96
  end
97
-
97
+
98
98
  should "notice that the user was not logged-in with the tester bar" do
99
99
  assert_parses tester_bar: false
100
100
  end
101
-
101
+
102
102
  should "identify the remainder of the log message" do
103
103
  assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
104
104
  end
105
105
  end
106
-
106
+
107
107
  context "that indicates that the user is logged out, it" do
108
108
  setup do
109
109
  @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [guest] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
110
110
  end
111
-
111
+
112
112
  should "identify the remainder of the log message" do
113
113
  assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
114
114
  end
115
115
  end
116
116
  end
117
-
118
-
119
-
117
+
118
+
119
+
120
120
  context "given the \"Started\" line, it" do
121
121
  setup do
122
122
  @line = "I, [2015-01-10T15:18:12.064392 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Started GET \"/people/1035826228?refresh_page=true\" for 71.218.222.249 at 2015-01-10 15:18:12 +0000"
123
123
  end
124
-
124
+
125
125
  should "identify the line as :request_started" do
126
126
  assert_parses type: :request_started
127
127
  end
128
-
128
+
129
129
  should "identify the HTTP method" do
130
130
  assert_parses http_method: "GET"
131
131
  end
132
-
132
+
133
133
  should "identify the path (without params)" do
134
134
  assert_parses path: "/people/1035826228"
135
135
  end
136
-
136
+
137
137
  should "identify the remote client's IP address" do
138
138
  assert_parses remote_ip: "71.218.222.249"
139
139
  end
140
140
  end
141
-
142
-
143
-
141
+
142
+
143
+
144
144
  context "given the \"Processing by\" line, it" do
145
145
  setup do
146
146
  @line = "I, [2015-01-10T15:18:12.067034 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Processing by Api::V1::PeopleController#show as JS"
147
147
  end
148
-
148
+
149
149
  should "identify the line as :request_controller" do
150
150
  assert_parses type: :request_controller
151
151
  end
152
-
152
+
153
153
  should "identify the controller and action" do
154
154
  assert_parses controller: "api/v1/people", action: "show"
155
155
  end
156
-
156
+
157
157
  should "identify the format requested" do
158
158
  assert_parses format: "JS"
159
159
  end
160
160
  end
161
-
162
-
163
-
161
+
162
+
163
+
164
164
  context "given the \"Parameters\" line, it" do
165
165
  setup do
166
166
  @line = "I, [2015-01-10T15:18:12.067134 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Parameters: {\"refresh_page\"=>\"true\", \"id\"=>\"1035826228\"}"
167
167
  end
168
-
168
+
169
169
  should "identify the line as :request_params" do
170
170
  assert_parses type: :request_params
171
171
  end
172
-
172
+
173
173
  should "identify the params" do
174
174
  assert_parses params: {"refresh_page" => "true", "id" => "1035826228"}
175
175
  end
176
176
  end
177
-
177
+
178
178
  context "when the \"Parameters\" line contains invalid syntax, it" do
179
179
  setup do
180
180
  @line = "I, [2015-01-10T15:18:12.067134 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Parameters: {\"refresh_page\"=>}"
181
181
  end
182
-
182
+
183
183
  should "return the params unparsed" do
184
184
  assert_parses params: "{\"refresh_page\"=>}"
185
185
  end
186
186
  end
187
-
188
-
189
-
187
+
188
+
189
+
190
190
  context "given the \"Completed\" line, it" do
191
191
  setup do
192
192
  @line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 401 Unauthorized in 2ms"
193
193
  end
194
-
194
+
195
195
  should "identify the line as :request_completed" do
196
196
  assert_parses type: :request_completed
197
197
  end
198
-
198
+
199
199
  should "identify the HTTP response" do
200
200
  assert_parses http_status: 401, http_response: "Unauthorized"
201
201
  end
202
-
202
+
203
203
  should "identify the duration of the request" do
204
204
  assert_parses duration: 2
205
205
  end
206
206
  end
207
-
207
+
208
208
  context "when the \"Completed\" line doesn't have a textual description of the status, it" do
209
209
  setup do
210
210
  @line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 307 in 0.8ms"
211
211
  end
212
-
212
+
213
213
  should "be cool with that" do
214
214
  assert_parses http_status: 307, http_response: nil
215
215
  end
216
216
  end
217
-
217
+
218
218
  context "when the \"Completed\" line contains a breakdown of times, it" do
219
219
  setup do
220
220
  @line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 200 OK in 196ms (Views: 0.1ms | ActiveRecord: 50.0ms)"
221
221
  end
222
-
222
+
223
223
  should "identify the duration of the request" do
224
224
  assert_parses duration: 196
225
225
  end
226
226
  end
227
-
228
-
229
-
227
+
228
+
229
+
230
230
  private
231
-
231
+
232
232
  def assert_parses(expectations)
233
233
  results = parse!
234
-
234
+
235
235
  expectations.each do |key, value|
236
236
  assert_equal value, results[key]
237
237
  end
238
238
  end
239
-
239
+
240
240
  def parse!
241
241
  parser.parse!(line)
242
242
  end
243
-
243
+
244
244
  def parser
245
245
  Logeater::Parser.new
246
246
  end
247
-
247
+
248
248
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logeater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Lail
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-03 00:00:00.000000000 Z
11
+ date: 2016-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.10.0
103
+ version: '0.10'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.10.0
110
+ version: '0.10'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: thor
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 2.12.14
131
+ version: '2.15'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 2.12.14
138
+ version: '2.15'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: bundler
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -241,6 +241,8 @@ files:
241
241
  - db/migrate/20150207183757_change_requests_params_to_jsonb.rb
242
242
  - db/migrate/20150224021844_change_requests_path_to_text.rb
243
243
  - db/migrate/20150602022241_add_user_id_and_tester_bar_to_requests.rb
244
+ - db/migrate/20160218230227_add_indexes.rb
245
+ - db/migrate/20160502131002_drop_http_response.rb
244
246
  - db/schema.rb
245
247
  - lib/logeater.rb
246
248
  - lib/logeater/cli.rb
@@ -277,7 +279,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
279
  version: '0'
278
280
  requirements: []
279
281
  rubyforge_project:
280
- rubygems_version: 2.2.2
282
+ rubygems_version: 2.5.1
281
283
  signing_key:
282
284
  specification_version: 4
283
285
  summary: Parses log files and imports them into a database