sms-logparser 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f0b662e2f8fc572e36fb90d70772df4719bd74e
4
- data.tar.gz: b689ed872012488f135a78ddd38e3583025a02e5
3
+ metadata.gz: 7d8d1676ea63d17f63d196dd955d52bcd822c28b
4
+ data.tar.gz: 6a0a0341ed6a5050cefa8871835247122f2a2934
5
5
  SHA512:
6
- metadata.gz: 141e69433bd1a57508777f58f6f21f72c7d6456c56bf13b284ac84e85fd75045ab04df0e47aa608db8f0ade2136c9a44e14bcca9c268988d5e7e4acffe50616f
7
- data.tar.gz: 73fd2b7ddef48779755558005dbe50ee4bf53fc7c4d7f3e0c2f4f5d3dfd3f3d271bed72fd15e83c2f4e4f9dcaf1ef09d46cdf2ca61474f5c0641305c9dea0bd1
6
+ metadata.gz: dabe200a09f9dd8f508726c6b8ba8471847c713ae0e142c0121ea8eafb526321cc023972c6c0d830996de5107158981fafa987c53f44074af772947fe9a9bae8
7
+ data.tar.gz: b5e7aa89a019a2582fbf0296fc38a3350da2a7aba1cc41c03b7fed1f01948b768f570807fce987b81a9cf6842b7fb770c68ce363946ce55f4a1ed3a14c19825e
data/README.md CHANGED
@@ -1,46 +1,60 @@
1
- # SmsLogparser
1
+ # SMS Logparser
2
2
 
3
- sms-logparser - DB-Logparser for Simplex Media Server (SMS). Reads access logs stored in a MySQL database (coming from the SWISS TXT CDN) and pushes them to the SMS API.
3
+ sms-logparser - Logparser for Simplex Media Server (SMS). Reads access logs stored in a MySQL database (coming from the SWISS TXT CDN) and sends them to the SMS API.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/sms-logparser.png)](http://badge.fury.io/rb/sms-logparser)
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ Install the sms-logpaser gem:
10
10
 
11
- gem 'sms-logparser'
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install sms-logparser
11
+ ```bash
12
+ $ gem install sms-logparser
13
+ ```
20
14
 
21
15
  ## Setup
22
16
 
23
17
  Create the database table to track which logs have been parsed:
24
18
 
25
- $ sms-logparser setup
19
+ ```bash
20
+ $ sms-logparser setup
21
+ ```
26
22
 
27
23
  Make a test run:
28
24
 
29
- $ sms-logparser parse --simulate --verbose
25
+ ```bash
26
+ $ sms-logparser parse --simulate --verbose
27
+ ```
30
28
 
31
29
  ## Usage
32
30
 
33
31
  See available commands:
34
32
 
35
- $ sms-logparser help
33
+ ```bash
34
+ $ sms-logparser help
35
+ ```
36
36
 
37
37
  Parse logs from database and send them to the API
38
38
 
39
- $ sms-logparser parse
39
+ ```bash
40
+ $ sms-logparser parse
41
+ ```
40
42
 
41
43
  Show the last parser runs:
42
44
 
43
- $ sms-logparser last_runs
45
+ ```bash
46
+ $ sms-logparser history
47
+ ``
48
+
49
+ ## Configuration file
50
+
51
+ sms-logparser tries to read default options from a yaml file named '.sms-logparser.yml' placed in your home directory. Using the "-c/--config" flag you can adapt the path to the configuration file.
52
+
53
+ An configuration for adapting the default MySQL password could look like this:
54
+
55
+ ```yaml
56
+ :mysql_password: "my!secret"
57
+ ```
44
58
 
45
59
  ## Development
46
60
 
@@ -6,12 +6,17 @@ module SmsLogparser
6
6
  end
7
7
 
8
8
  def send(data)
9
+ urls = []
9
10
  base_url = "#{@options[:api_base_path]}/"
10
11
  base_url += "#{data[:customer_id]}/"
11
12
  base_url += "#{data[:author_id]}/"
12
13
  base_url += "#{data[:project_id]}"
13
- urls = ["#{base_url}/#{data[:traffic_type]}/#{data[:bytes]}"]
14
- urls << "#{base_url}/#{data[:visitor_type]}/1" if data[:visitor_type]
14
+ unless data[:file] =~ /.*\.m3u8$/
15
+ urls = ["#{base_url}/#{data[:traffic_type]}/#{data[:bytes]}"]
16
+ end
17
+ if data[:visitor_type]
18
+ urls << "#{base_url}/#{data[:visitor_type]}/1"
19
+ end
15
20
  unless @options[:simulate]
16
21
  urls.each do |url|
17
22
  begin
@@ -1,22 +1,56 @@
1
1
  module SmsLogparser
2
2
  class Cli < Thor
3
+ require 'yaml'
3
4
 
4
- class_option :mysql_host, :default => 'localhost', aliases: %w(-h)
5
- class_option :mysql_user, :default => 'root', aliases: %w(-u)
6
- class_option :mysql_password, aliases: %w(-p)
7
- class_option :mysql_db, :default => 'Syslog', aliases: %w(-d)
5
+ STATUS = {
6
+ :ok => 0,
7
+ :api_error => 1
8
+ }
8
9
 
9
- desc "version", "print cloudstack-cli version number"
10
+ class_option :config,
11
+ :default => File.join(Dir.home, '.sms-logparser.yml'),
12
+ :aliases => %w(-c),
13
+ :desc => "Configuration file for default options"
14
+
15
+ class_option :mysql_host,
16
+ :default => 'localhost',
17
+ :aliases => %w(-h),
18
+ :desc => "MySQL host"
19
+
20
+ class_option :mysql_user,
21
+ :default => 'root',
22
+ :aliases => %w(-u),
23
+ :desc => "MySQL user"
24
+
25
+ class_option :mysql_password,
26
+ :aliases => %w(-p),
27
+ :desc => "MySQL password"
28
+
29
+ class_option :mysql_db,
30
+ :default => 'Syslog',
31
+ :aliases => %w(-d),
32
+ :desc => "MySQL database"
33
+
34
+ desc "version", "Print cloudstack-cli version number"
10
35
  def version
11
36
  say "sms-logparser version #{SmsLogparser::VERSION}"
12
37
  end
13
38
  map %w(-v --version) => :version
14
39
 
15
- desc "parse", "Check the database for pcache logs and send them to SMS"
16
- option :api_base_path, :default => 'http://dev.simplex.tv/creator/rest', aliases: %w(-a)
17
- option :api_key, aliases: %w(-k)
18
- option :simulate, :type => :boolean, :default => false, aliases: %w(-s)
19
- option :verbose, :type => :boolean, :default => false, aliases: %w(-v)
40
+ desc "parse", "Check the database for pcache logs and send them to the SMS-API"
41
+ option :api_base_path,
42
+ :default => 'http://dev.simplex.tv/creator/rest',
43
+ :aliases => %w(-a)
44
+ option :api_key,
45
+ :aliases => %w(-k)
46
+ option :simulate,
47
+ :type => :boolean,
48
+ :default => false,
49
+ :aliases => %w(-s)
50
+ option :verbose,
51
+ :type => :boolean,
52
+ :default => false,
53
+ :aliases => %w(-v)
20
54
  def parse
21
55
  start_time = Time.now
22
56
  count = 0
@@ -25,44 +59,61 @@ module SmsLogparser
25
59
  entries = mysql.get_entries
26
60
  api = Api.new(options)
27
61
  last_id = mysql.get_last_parse_id
62
+ status = STATUS[:ok]
28
63
  entries.each do |entry|
29
- if Parser.match(entry['Message'])
64
+ if Parser.match?(entry['Message'])
30
65
  data = Parser.extract_data_from_msg(entry['Message'])
31
66
  begin
32
67
  urls = api.send(data)
33
68
  rescue => e
34
69
  say "Error: #{e.message}", :red
35
70
  say "Aborting parser run...", :red
71
+ status = STATUS[:api_error]
36
72
  break
37
73
  end
38
74
  last_id = entry['ID']
39
75
  count += 1
40
- verbose_parser_output(data, urls, entry) if options[:verbose]
76
+ if options[:verbose]
77
+ verbose_parser_output(data, urls, entry)
78
+ end
41
79
  end
42
80
  end
43
- mysql.write_parse_result(last_id, count) unless options[:simulate]
81
+ unless options[:simulate]
82
+ mysql.write_parse_result(last_id, count, status)
83
+ end
44
84
  say "Started:\t", :cyan
45
85
  say start_time.strftime('%d.%d.%Y %T')
46
86
  say "Runtime:\t", :cyan
47
87
  say "#{(Time.now - start_time).round(2)}s"
48
- options[:simulate] ? say("Events found:\t", :cyan) : say("Events sent:\t", :cyan)
88
+ if options[:simulate]
89
+ say("Events found:\t", :cyan)
90
+ else
91
+ say("Events sent:\t", :cyan)
92
+ end
49
93
  say count
50
94
  rescue => e
51
95
  say "Error: #{e.message}", :red
52
96
  end
53
97
  end
54
98
 
55
- desc "last_runs", "List the last paser runs"
56
- def last_runs
99
+ desc "history", "List the last paser runs"
100
+ option :results,
101
+ :type => :numeric,
102
+ :default => 10,
103
+ :aliases => %w(-n),
104
+ :desc => "Number of results to display"
105
+ def history
106
+ puts options
57
107
  begin
58
- runs = Mysql.new(options).last_runs
108
+ runs = Mysql.new(options).last_runs(options[:results])
59
109
  if runs.size > 0
60
- table = [%w(RunAt #Events LastEventID)]
61
- runs.each do |run|
110
+ table = [%w(RunAt #Events LastEventID Status)]
111
+ runs.to_a.reverse.each do |run|
62
112
  table << [
63
113
  run['RunAt'],
64
114
  run['EventsFound'],
65
- run['LastEventID']
115
+ run['LastEventID'],
116
+ STATUS.index(run['Status']).upcase
66
117
  ]
67
118
  end
68
119
  print_table table
@@ -75,26 +126,46 @@ module SmsLogparser
75
126
  end
76
127
 
77
128
  desc "setup", "Create the parser table to track the last logs parsed"
129
+ option :force,
130
+ :type => :boolean,
131
+ :default => false,
132
+ :aliases => %w(-f),
133
+ :desc => "Drop an existing table if it exists"
78
134
  def setup
79
135
  begin
80
- Mysql.new(options).create_parser_table
81
- say "OK", :green
136
+ case Mysql.new(options).create_parser_table(options[:force])
137
+ when 0
138
+ say "OK, table created.", :green
139
+ when 1
140
+ say "Table already exists.", :yellow
141
+ end
82
142
  rescue => e
83
143
  say "Error: #{e.message}", :red
84
144
  end
85
145
  end
86
146
 
87
147
  no_commands do
148
+
88
149
  def verbose_parser_output(data, urls, entry)
89
150
  say "ID:\t", :cyan
90
151
  say entry['ID']
91
152
  say "URL:\t", :cyan
92
153
  say urls.join("\n\t")
93
154
  say "Data:\t", :cyan
94
- say data.map{|k,v| "#{k}:\t#{v}"}.join("\n\t")
155
+ say data.map{|k,v| "#{k}:\t#{v}"}.join("\n\t") || "\n"
156
+ puts
95
157
  puts "-" * 100
96
158
  puts
97
159
  end
160
+
161
+ def options
162
+ original_options = super
163
+ filename = original_options[:config] || File.join(Dir.home, '.sms-logparser.yml')
164
+ return original_options unless File.exists?(filename)
165
+ defaults = ::YAML::load_file(filename) || {}
166
+ defaults.merge(original_options)
167
+ end
168
+
98
169
  end
99
170
 
100
171
  end
@@ -14,27 +14,54 @@ module SmsLogparser
14
14
  )
15
15
  end
16
16
 
17
- def last_runs
17
+ def last_runs(results = 10)
18
18
  begin
19
19
  runs = client.query(
20
- "SELECT * FROM SmsParserRuns ORDER BY ID ASC LIMIT 10"
20
+ "SELECT * FROM SmsParserRuns ORDER BY ID DESC LIMIT #{results}"
21
21
  )
22
22
  rescue Mysql2::Error => e
23
23
  raise e
24
24
  end
25
25
  end
26
26
 
27
- def create_parser_table
27
+ def parser_table_exists?
28
28
  begin
29
29
  return client.query(
30
- "CREATE TABLE IF NOT EXISTS\
31
- SmsParserRuns(\
32
- ID INT PRIMARY KEY AUTO_INCREMENT,\
33
- RunAt datetime DEFAULT NULL,\
34
- LastEventID INT DEFAULT NULL,\
35
- EventsFound INT DEFAULT 0,\
36
- INDEX `LastEventID_I1` (`LastEventID`)
37
- )"
30
+ "SHOW TABLES LIKE 'SmsParserRuns'"
31
+ ).size > 0
32
+ rescue Mysql2::Error => e
33
+ raise e
34
+ end
35
+ end
36
+
37
+ def create_parser_table(force = false)
38
+ if force
39
+ drop_parser_table
40
+ elsif parser_table_exists?
41
+ return 1
42
+ end
43
+ begin
44
+ client.query(
45
+ "CREATE TABLE SmsParserRuns(\
46
+ ID SERIAL PRIMARY KEY AUTO_INCREMENT,\
47
+ RunAt datetime DEFAULT NULL,\
48
+ LastEventID BIGINT(20) UNSIGNED DEFAULT 0,\
49
+ EventsFound INT DEFAULT 0,\
50
+ Status TINYINT UNSIGNED DEFAULT 0,\
51
+ INDEX `LastEventID_I1` (`LastEventID`)
52
+ )"
53
+ )
54
+ rescue Mysql2::Error => e
55
+ raise e
56
+ end
57
+ return 0
58
+ end
59
+
60
+ def drop_parser_table
61
+ return nil unless parser_table_exists?
62
+ begin
63
+ return client.query(
64
+ "DROP TABLE SmsParserRuns"
38
65
  )
39
66
  rescue Mysql2::Error => e
40
67
  raise e
@@ -53,12 +80,13 @@ module SmsLogparser
53
80
  end
54
81
  end
55
82
 
56
- def write_parse_result(id, count)
57
- client.query("INSERT INTO SmsParserRuns(RunAt, LastEventID, EventsFound)\
83
+ def write_parse_result(id, count, status)
84
+ client.query("INSERT INTO SmsParserRuns(RunAt, LastEventID, EventsFound, Status)\
58
85
  VALUES(\
59
86
  '#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}',\
60
87
  #{id},\
61
- #{count}
88
+ #{count},\
89
+ #{status}
62
90
  )"
63
91
  )
64
92
  end
@@ -18,8 +18,14 @@ module SmsLogparser
18
18
  }
19
19
  end
20
20
 
21
- def self.match(message)
22
- message =~ /\/content\/.*\.(f4v|flv|mp4|mp3|ts|m3u8) .* (200|206)/
21
+ def self.match?(message)
22
+ match = message.match(
23
+ /\/content\/.+\/(\w+\.(f4v|flv|mp4|mp3|ts|m3u8)) .+ (200|206)/i
24
+ )
25
+ if match
26
+ return true unless match[1] =~ /detect.mp4|index.m3u8/i
27
+ end
28
+ false
23
29
  end
24
30
 
25
31
  # see https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
@@ -1,3 +1,3 @@
1
1
  module SmsLogparser
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = '>= 1.9.3'
22
+
21
23
  spec.add_development_dependency "bundler", "~> 1.3"
22
24
  spec.add_development_dependency "rake"
23
25
 
data/spec/api_spec.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmsLogparser::Api do
4
+
5
+ before do
6
+ @api = SmsLogparser::Api.new(
7
+ simulate: true,
8
+ api_base_path: "http://myapi.com/rest/"
9
+ )
10
+ end
11
+
12
+ it "sends the correct information to the api" do
13
+ data = {
14
+ :customer_id => 1,
15
+ :author_id => 2,
16
+ :project_id => 3,
17
+ :file => 'myfile.mp4',
18
+ :bytes => 128,
19
+ :user_agent => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko)",
20
+ :traffic_type => 'TRAFFIC_WEBCAST',
21
+ :visitor_type => 'VISITORS_WEBCAST'
22
+ }
23
+ urls = @api.send(data)
24
+ urls.size.must_equal 2
25
+ urls[0].must_match /.+\/1\/2\/3\/TRAFFIC_WEBCAST\/128$/
26
+ urls[1].must_match /.+\/1\/2\/3\/VISITORS_WEBCAST\/1$/
27
+ end
28
+
29
+ it "does not send traffic for m3u8 files" do
30
+ data = {
31
+ :customer_id => 100,
32
+ :author_id => 200,
33
+ :project_id => 300,
34
+ :file => 'myfile.m3u8',
35
+ :bytes => 512,
36
+ :user_agent => 'Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0',
37
+ :traffic_type => 'TRAFFIC_MOBILE',
38
+ :visitor_type => 'VISITORS_MOBILE'
39
+ }
40
+ urls = @api.send(data)
41
+ urls.size.must_equal 1
42
+ urls[0].must_match /.+\/100\/200\/300\/VISITORS_MOBILE\/1$/
43
+ end
44
+
45
+ it "does not send visitor info if no visitor_type" do
46
+ data = {
47
+ :customer_id => 101,
48
+ :author_id => 202,
49
+ :project_id => 303,
50
+ :file => 'myfile.mp4',
51
+ :bytes => 48,
52
+ :user_agent => 'Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0',
53
+ :traffic_type => 'TRAFFIC_MOBILE',
54
+ }
55
+ urls = @api.send(data)
56
+ urls.size.must_equal 1
57
+ urls[0].must_match /.+\/101\/202\/303\/TRAFFIC_MOBILE\/48$/
58
+ end
59
+
60
+ end
data/spec/cli_spec.rb CHANGED
@@ -14,7 +14,7 @@ describe SmsLogparser::Cli do
14
14
  out, err = capture_io do
15
15
  TestHelper.sms_logparser.setup
16
16
  end
17
- out.must_match /OK/
17
+ out.must_match /OK.*/
18
18
  end
19
19
 
20
20
  it "can parse a log database and find matches" do
@@ -40,15 +40,15 @@ describe SmsLogparser::Cli do
40
40
  out.must_match /\s+0$/
41
41
  end
42
42
 
43
- it "lists parser runs" do
43
+ it "can show the parser history" do
44
44
  TestHelper.seed_db(1)
45
45
  parser = TestHelper.sms_logparser
46
46
  parser.options[:api_base_path] = 'http://devnull-as-a-service.com/dev/null/'
47
47
  out, err = capture_io do
48
48
  TestHelper.sms_logparser.setup
49
49
  parser.parse
50
- parser.last_runs
50
+ parser.history
51
51
  end
52
- assert_equal(err, "")
52
+ err.must_equal ""
53
53
  end
54
54
  end
data/spec/parser_spec.rb CHANGED
@@ -4,41 +4,49 @@ describe SmsLogparser::Parser do
4
4
 
5
5
  %w(f4v flv mp4 mp3 ts m3u8).each do |extension|
6
6
  it "matches #{extension} files" do
7
- SmsLogparser::Parser.match(
7
+ SmsLogparser::Parser.match?(
8
8
  "GET /content/2/719/54986/file.#{extension} HTTP/1.1\" 200 6741309 "
9
- ).wont_be_nil
9
+ ).must_equal true
10
10
  end
11
11
  end
12
12
 
13
13
  %w(jpg js css m4a docx).each do |extension|
14
14
  it "does not matche #{extension} files" do
15
- SmsLogparser::Parser.match(
15
+ SmsLogparser::Parser.match?(
16
16
  "GET /content/2/719/54986/file.#{extension} HTTP/1.1\" 200 6741309 "
17
- ).must_be_nil
17
+ ).must_equal false
18
18
  end
19
19
  end
20
20
 
21
21
  %w(200 206).each do |status|
22
22
  it "does match status code #{status}" do
23
- SmsLogparser::Parser.match(
23
+ SmsLogparser::Parser.match?(
24
24
  "GET /content/2/719/54986/file.mp4 HTTP/1.1\" #{status} 50000 "
25
- ).wont_be_nil
25
+ ).must_equal true
26
26
  end
27
27
  end
28
28
 
29
29
  %w(404 500 304).each do |status|
30
30
  it "does not match status code #{status}" do
31
- SmsLogparser::Parser.match(
31
+ SmsLogparser::Parser.match?(
32
32
  "GET /content/2/719/54986/file.mp4 HTTP/1.1\" #{status} 50000 "
33
- ).must_be_nil
33
+ ).must_equal false
34
34
  end
35
35
  end
36
36
 
37
- %w(contents public index CONTENT).each do |dir|
37
+ %w(contents public index assets).each do |dir|
38
38
  it "does not match directories other than /content" do
39
- SmsLogparser::Parser.match(
39
+ SmsLogparser::Parser.match?(
40
40
  "GET /#{dir}/2/719/54986/file.mp4 HTTP/1.1\" 200 50000 "
41
- ).must_be_nil
41
+ ).must_equal false
42
+ end
43
+ end
44
+
45
+ %w(detect.mp4 index.m3u8).each do |file|
46
+ it "does not match excluded files" do
47
+ SmsLogparser::Parser.match?(
48
+ "GET /content/2/719/54986/#{file} HTTP/1.1\" 200 128 "
49
+ ).must_equal false
42
50
  end
43
51
  end
44
52
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sms-logparser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - niwo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-25 00:00:00.000000000 Z
11
+ date: 2014-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -101,6 +101,7 @@ files:
101
101
  - lib/sms-logparser/parser.rb
102
102
  - lib/sms-logparser/version.rb
103
103
  - sms-logparser.gemspec
104
+ - spec/api_spec.rb
104
105
  - spec/cli_spec.rb
105
106
  - spec/parser_spec.rb
106
107
  - spec/spec_helper.rb
@@ -116,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
117
  requirements:
117
118
  - - '>='
118
119
  - !ruby/object:Gem::Version
119
- version: '0'
120
+ version: 1.9.3
120
121
  required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  requirements:
122
123
  - - '>='
@@ -129,6 +130,7 @@ signing_key:
129
130
  specification_version: 4
130
131
  summary: SMS Logparser
131
132
  test_files:
133
+ - spec/api_spec.rb
132
134
  - spec/cli_spec.rb
133
135
  - spec/parser_spec.rb
134
136
  - spec/spec_helper.rb