heroku-log-parser 0.2
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.
- data/.gitignore +20 -0
- data/README.md +52 -0
- data/heroku-log-parser.gemspec +52 -0
- data/lib/heroku-log-parser.rb +77 -0
- data/lib/heroku-log-parser/version.rb +3 -0
- metadata +50 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
heroku-log-parser
|
2
|
+
=======
|
3
|
+
|
4
|
+
A [syslog (rfc5424)](http://tools.ietf.org/html/rfc5424#section-6) parser written in Ruby and specifically
|
5
|
+
targeting Heroku's [http log drain](https://devcenter.heroku.com/articles/labs-https-drains).
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
Declare `heroku-log-parser` in your `Gemfile`.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'heroku-log-parser', :git => 'git@github.com:rwdaigle/heroku-log-parser.git'
|
13
|
+
```
|
14
|
+
|
15
|
+
Run bundler.
|
16
|
+
|
17
|
+
```term
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
msg_str = "156 <40>1 2012-11-30T06:45:26+00:00 heroku web.3 d.73ea7440-270a-435a-a0ea-adf50b4e5f5a - Starting process with command `bundle exec rackup config.ru -p 24405`"
|
25
|
+
|
26
|
+
HerokuLogParser.parse(msg_str)
|
27
|
+
#=> [{:priority=>40, :syslog_version=>1, :emitted_at=>2012-11-30 06:45:26 UTC, :hostname=>"heroku", :appname=>nil, :proc_id=>"web.3", :msg_id=>"d.73ea7440-270a-435a-a0ea-adf50b4e5f5a", :structured_data=>nil, :message=>"Starting process with command `bundle exec rackup config.ru -p 24405`"}]
|
28
|
+
```
|
29
|
+
|
30
|
+
`HerokuLogParser` is a stateless, regex-based parser that accepts a string of data holding one or more syslog messages
|
31
|
+
and returns an array of syslog message properties for each message. For those unwilling to read the spec, the
|
32
|
+
list of syslog tokens is as follows (and is stored in the `HerokuLogParser::SYSLOG_KEYS` array):
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
HerokuLogParser::SYSLOG_KEYS
|
36
|
+
#=> [:priority, :syslog_version, :emitted_at, :hostname, :appname, :proc_id, :msg_id, :structured_data, :message]
|
37
|
+
```
|
38
|
+
|
39
|
+
## Contributions
|
40
|
+
|
41
|
+
* [Ryan Smith](https://github.com/ryandotsmith/) for his work on [l2met](https://github.com/ryandotsmith/l2met) which forms the foundation of heroku-log-parser.
|
42
|
+
|
43
|
+
## Todos
|
44
|
+
|
45
|
+
* TESTS!!!!
|
46
|
+
* 2nd order parsing. For instance, for parsing a structured message body into key=value pairs (including the structured_data message part)
|
47
|
+
|
48
|
+
## Issues
|
49
|
+
|
50
|
+
Please submit all issues to the project's Github issues.
|
51
|
+
|
52
|
+
-- [@rwdaigle](https://twitter.com/rwdaigle)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'heroku-log-parser/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "heroku-log-parser"
|
6
|
+
s.version = HerokuLogParser::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.author = "Ryan Daigle"
|
9
|
+
s.email = ["ryan.daigle@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/rwdaigle/heroku-log-parser"
|
11
|
+
s.summary = "Syslog message parser"
|
12
|
+
s.description = "Easily parse Heroku's syslog-based application log-stream"
|
13
|
+
|
14
|
+
s.rubyforge_project = "heroku-log-parser"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
if File.exists?('UPGRADING')
|
22
|
+
s.post_install_message = File.read("UPGRADING")
|
23
|
+
end
|
24
|
+
|
25
|
+
s.required_ruby_version = ">= 1.8.7"
|
26
|
+
|
27
|
+
# s.add_dependency('activerecord', '>= 3.0.0')
|
28
|
+
# s.add_dependency('activemodel', '>= 3.0.0')
|
29
|
+
# s.add_dependency('activesupport', '>= 3.0.0')
|
30
|
+
# s.add_dependency('cocaine', '~> 0.4.0')
|
31
|
+
# s.add_dependency('mime-types')
|
32
|
+
|
33
|
+
# s.add_development_dependency('shoulda')
|
34
|
+
# s.add_development_dependency('appraisal')
|
35
|
+
# s.add_development_dependency('mocha')
|
36
|
+
# s.add_development_dependency('aws-sdk', '>= 1.2.0')
|
37
|
+
# s.add_development_dependency('bourne')
|
38
|
+
# s.add_development_dependency('sqlite3', '~> 1.3.4')
|
39
|
+
# s.add_development_dependency('cucumber', '~> 1.2.1')
|
40
|
+
# s.add_development_dependency('aruba')
|
41
|
+
# s.add_development_dependency('nokogiri')
|
42
|
+
# s.add_development_dependency('capybara')
|
43
|
+
# s.add_development_dependency('bundler')
|
44
|
+
# s.add_development_dependency('cocaine', '~> 0.2')
|
45
|
+
# s.add_development_dependency('fog', '>= 1.4.0', "< 1.7.0")
|
46
|
+
# s.add_development_dependency('pry')
|
47
|
+
# s.add_development_dependency('launchy')
|
48
|
+
# s.add_development_dependency('rake')
|
49
|
+
# s.add_development_dependency('fakeweb')
|
50
|
+
# s.add_development_dependency('railties')
|
51
|
+
# s.add_development_dependency('actionmailer')
|
52
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class HerokuLogParser
|
2
|
+
|
3
|
+
SYSLOG_KEYS = :priority, :syslog_version, :emitted_at, :hostname, :appname, :proc_id, :msg_id, :structured_data, :message
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def parse(data_str)
|
8
|
+
events = []
|
9
|
+
lines(data_str) do |line|
|
10
|
+
if(matching = line.match(line_regex))
|
11
|
+
events << event_data(matching)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
events
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# http://tools.ietf.org/html/rfc5424#page-8
|
20
|
+
# frame <prority>version time hostname <appname-missing> procid msgid [no structured data = '-'] msg
|
21
|
+
# 120 <40>1 2012-11-30T06:45:29+00:00 heroku web.3 d.73ea7440-270a-435a-a0ea-adf50b4e5f5a - State changed from starting to up
|
22
|
+
def line_regex
|
23
|
+
@line_regex ||= /\<(\d+)\>(1) (\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\+00:00) ([a-z0-9-]+) ([a-z0-9\-\_\.]+) ([a-z0-9\-\_\.]+) (\-) (.*)$/
|
24
|
+
end
|
25
|
+
|
26
|
+
# Heroku's http log drains (https://devcenter.heroku.com/articles/labs-https-drains)
|
27
|
+
# utilize octet counting framing (http://tools.ietf.org/html/draft-gerhards-syslog-plain-tcp-12#section-3.4.1)
|
28
|
+
# for transmission of syslog messages over TCP. Properly parse and delimit
|
29
|
+
# individual syslog messages, many of which may be contained in a single packet.
|
30
|
+
#
|
31
|
+
# I am still uncertain if this is the place for transport layer protocol handling. I suspect not.
|
32
|
+
#
|
33
|
+
def lines(data_str, &block)
|
34
|
+
d = data_str
|
35
|
+
while d && d.length > 0
|
36
|
+
if matching = d.match(/^(\d+) /) # if have a counting frame, use it
|
37
|
+
num_bytes = matching[1].to_i
|
38
|
+
frame_offset = matching[0].length
|
39
|
+
line_end = frame_offset + num_bytes
|
40
|
+
msg = d[frame_offset..line_end]
|
41
|
+
yield msg
|
42
|
+
d = d[line_end..d.length]
|
43
|
+
elsif matching = d.match(/\n/) # Newlines = explicit message delimiter
|
44
|
+
d = matching.post_match
|
45
|
+
else
|
46
|
+
STDERR.puts("Unable to parse: #{d}")
|
47
|
+
return
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Heroku is missing the appname token, otherwise can treat as standard syslog format
|
53
|
+
def event_data(matching)
|
54
|
+
event = {}
|
55
|
+
event[:priority] = matching[1].to_i
|
56
|
+
event[:syslog_version] = matching[2].to_i
|
57
|
+
event[:emitted_at] = nil?(matching[3]) ? nil : Time.parse(matching[3]).utc
|
58
|
+
event[:hostname] = interpret_nil(matching[4])
|
59
|
+
event[:appname] = nil
|
60
|
+
event[:proc_id] = interpret_nil(matching[5])
|
61
|
+
event[:msg_id] = interpret_nil(matching[6])
|
62
|
+
event[:structured_data] = interpret_nil(matching[7])
|
63
|
+
event[:message] = interpret_nil(matching[8])
|
64
|
+
event
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def interpret_nil(val)
|
70
|
+
nil?(val) ? nil : val
|
71
|
+
end
|
72
|
+
|
73
|
+
def nil?(val)
|
74
|
+
val == "-"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heroku-log-parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Daigle
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-14 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Easily parse Heroku's syslog-based application log-stream
|
15
|
+
email:
|
16
|
+
- ryan.daigle@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- README.md
|
23
|
+
- heroku-log-parser.gemspec
|
24
|
+
- lib/heroku-log-parser.rb
|
25
|
+
- lib/heroku-log-parser/version.rb
|
26
|
+
homepage: https://github.com/rwdaigle/heroku-log-parser
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.8.7
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project: heroku-log-parser
|
46
|
+
rubygems_version: 1.8.23
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: Syslog message parser
|
50
|
+
test_files: []
|