fluent-plugin-td 0.9.7
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/AUTHORS +1 -0
- data/README.rdoc +7 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/example.conf +78 -0
- data/fluent-plugin-td.gemspec +42 -0
- data/lib/fluent/plugin/out_tdlog.rb +209 -0
- metadata +88 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
FURUHASHI Sadayuki <frsyuki _at_ gmail.com>
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gemspec|
|
8
|
+
gemspec.name = "fluent-plugin-td"
|
9
|
+
gemspec.summary = "Treasure Data plugin for Fluent event collector"
|
10
|
+
gemspec.author = "Sadayuki Furuhashi"
|
11
|
+
#gemspec.email = "frsyuki@gmail.com"
|
12
|
+
#gemspec.homepage = "http://fluent.github.com/"
|
13
|
+
gemspec.has_rdoc = false
|
14
|
+
gemspec.require_paths = ["lib"]
|
15
|
+
gemspec.add_dependency "fluent", "~> 0.9.7"
|
16
|
+
gemspec.test_files = Dir["test/**/*.rb"]
|
17
|
+
gemspec.files = Dir["bin/**/*", "lib/**/*", "test/**/*.rb"] +
|
18
|
+
%w[example.conf VERSION AUTHORS Rakefile fluent-plugin-td.gemspec]
|
19
|
+
gemspec.executables = []
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.test_files = Dir['test/*_test.rb']
|
28
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
29
|
+
t.ruby_opts << '-I.'
|
30
|
+
end
|
31
|
+
|
32
|
+
#VERSION_FILE = "lib/fluent/version.rb"
|
33
|
+
#
|
34
|
+
#file VERSION_FILE => ["VERSION"] do |t|
|
35
|
+
# version = File.read("VERSION").strip
|
36
|
+
# File.open(VERSION_FILE, "w") {|f|
|
37
|
+
# f.write <<EOF
|
38
|
+
#module Fluent
|
39
|
+
#
|
40
|
+
#VERSION = '#{version}'
|
41
|
+
#
|
42
|
+
#end
|
43
|
+
#EOF
|
44
|
+
# }
|
45
|
+
#end
|
46
|
+
#
|
47
|
+
#task :default => [VERSION_FILE, :build]
|
48
|
+
|
49
|
+
task :default => [:build]
|
50
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.7
|
data/example.conf
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
## match tag=apache.access and upload to Treasure Data
|
3
|
+
#<match apache.access>
|
4
|
+
# type tdlog
|
5
|
+
# apikey APIKEY
|
6
|
+
#</match>
|
7
|
+
|
8
|
+
|
9
|
+
## built-in TCP input
|
10
|
+
<source>
|
11
|
+
type tcp
|
12
|
+
</source>
|
13
|
+
|
14
|
+
## built-in UNIX socket input
|
15
|
+
#<source>
|
16
|
+
# type unix
|
17
|
+
#</source>
|
18
|
+
|
19
|
+
# HTTP input
|
20
|
+
# http://localhost:8888/<tag>?json=<json>
|
21
|
+
<source>
|
22
|
+
type http
|
23
|
+
port 8888
|
24
|
+
</source>
|
25
|
+
|
26
|
+
## File input
|
27
|
+
## read apache logs with tag=apache.access
|
28
|
+
#<source>
|
29
|
+
# type tail
|
30
|
+
# format apache
|
31
|
+
# path /var/log/httpd-access.log
|
32
|
+
# tag apache.access
|
33
|
+
#</source>
|
34
|
+
|
35
|
+
|
36
|
+
## match tag=apache.access and write to file
|
37
|
+
#<match apache.access>
|
38
|
+
# type file
|
39
|
+
# path /var/log/td-agent/access
|
40
|
+
#</match>
|
41
|
+
|
42
|
+
## match tag=debug.** and dump to console
|
43
|
+
<match debug.**>
|
44
|
+
type stdout
|
45
|
+
</match>
|
46
|
+
|
47
|
+
## match tag=system.** and forward to another td-agent server
|
48
|
+
#<match system.**>
|
49
|
+
# type tcp
|
50
|
+
# host 192.168.0.11
|
51
|
+
# <secondary>
|
52
|
+
# host 192.168.0.12
|
53
|
+
# </secondary>
|
54
|
+
#</match>
|
55
|
+
|
56
|
+
## match tag=myapp.** and forward and write to file
|
57
|
+
#<match myapp.**>
|
58
|
+
# type copy
|
59
|
+
# <store>
|
60
|
+
# type tcp
|
61
|
+
# host 192.168.0.13
|
62
|
+
# buffer_type file
|
63
|
+
# buffer_path /var/log/td-agent/myapp-forward
|
64
|
+
# retry_limit 50
|
65
|
+
# flush_interval 10s
|
66
|
+
# </store>
|
67
|
+
# <store>
|
68
|
+
# type file
|
69
|
+
# path /var/log/td-agent/myapp
|
70
|
+
# </store>
|
71
|
+
#</match>
|
72
|
+
|
73
|
+
## match not matched logs and write to file
|
74
|
+
#<match **>
|
75
|
+
# type file
|
76
|
+
# path /var/log/td-agent/else/%Y-%m-%d/%H.log
|
77
|
+
#</match>
|
78
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{fluent-plugin-td}
|
8
|
+
s.version = "0.9.7"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Sadayuki Furuhashi"]
|
12
|
+
s.date = %q{2011-08-06}
|
13
|
+
s.extra_rdoc_files = [
|
14
|
+
"README.rdoc"
|
15
|
+
]
|
16
|
+
s.files = [
|
17
|
+
"AUTHORS",
|
18
|
+
"Rakefile",
|
19
|
+
"VERSION",
|
20
|
+
"example.conf",
|
21
|
+
"fluent-plugin-td.gemspec",
|
22
|
+
"lib/fluent/plugin/out_tdlog.rb"
|
23
|
+
]
|
24
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
s.rubygems_version = %q{1.3.7}
|
27
|
+
s.summary = %q{Treasure Data plugin for Fluent event collector}
|
28
|
+
|
29
|
+
if s.respond_to? :specification_version then
|
30
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
31
|
+
s.specification_version = 3
|
32
|
+
|
33
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
34
|
+
s.add_runtime_dependency(%q<fluent>, ["~> 0.9.7"])
|
35
|
+
else
|
36
|
+
s.add_dependency(%q<fluent>, ["~> 0.9.7"])
|
37
|
+
end
|
38
|
+
else
|
39
|
+
s.add_dependency(%q<fluent>, ["~> 0.9.7"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module Fluent
|
2
|
+
|
3
|
+
|
4
|
+
class TreasureDataLogOutput < BufferedOutput
|
5
|
+
Plugin.register_output('tdlog', self)
|
6
|
+
|
7
|
+
HOST = ENV['TD_API_SERVER'] || 'api.treasure-data.com'
|
8
|
+
PORT = 80
|
9
|
+
USE_SSL = false
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
require 'fileutils'
|
13
|
+
require 'tempfile'
|
14
|
+
require 'zlib'
|
15
|
+
require 'net/http'
|
16
|
+
require 'json'
|
17
|
+
require 'cgi' # CGI.escape
|
18
|
+
require 'time' # Time#rfc2822
|
19
|
+
super
|
20
|
+
@tmpdir = '/tmp/fluent/tdlog'
|
21
|
+
@apikey = nil
|
22
|
+
@key = nil
|
23
|
+
@table_list = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def configure(conf)
|
27
|
+
super
|
28
|
+
|
29
|
+
@tmpdir = conf['tmpdir'] || @tmpdir
|
30
|
+
FileUtils.mkdir_p(@tmpdir)
|
31
|
+
|
32
|
+
@apikey = conf['apikey']
|
33
|
+
unless @apikey
|
34
|
+
raise ConfigError, "'apikey' parameter is required on tdlog output"
|
35
|
+
end
|
36
|
+
|
37
|
+
database = conf['database']
|
38
|
+
table = conf['table']
|
39
|
+
if database && table
|
40
|
+
if !validate_name(database)
|
41
|
+
raise ConfigError, "Invalid database name #{database.inspect}: #{conf}"
|
42
|
+
end
|
43
|
+
if !validate_name(table)
|
44
|
+
raise ConfigError, "Invalid table name #{table.inspect}: #{conf}"
|
45
|
+
end
|
46
|
+
@key = "#{database}.#{table}"
|
47
|
+
elsif (database && !table) || (!database && table)
|
48
|
+
raise ConfigError, "'database' and 'table' parameter are required on tdlog output"
|
49
|
+
end
|
50
|
+
|
51
|
+
@table_list = get_table_list
|
52
|
+
|
53
|
+
if @key && !@table_list.include?(@key)
|
54
|
+
raise ConfigError, "Table #{@key.inspect} does not exist on Treasure Data. Use 'td create-log-table #{database} #{table}' to create it."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def emit(tag, es, chain)
|
59
|
+
if @key
|
60
|
+
key = @key
|
61
|
+
else
|
62
|
+
database, table = tag.split('.')[-2,2]
|
63
|
+
if !validate_name(database) || !validate_name(table)
|
64
|
+
$log.debug { "Invalid tag #{tag.inspect}" }
|
65
|
+
return
|
66
|
+
end
|
67
|
+
key = "#{database}.#{table}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# check the table exists
|
71
|
+
unless @table_list.include?(key)
|
72
|
+
begin
|
73
|
+
@table_list = get_table_list
|
74
|
+
rescue
|
75
|
+
$log.warn "failed to update table list on Treasure Data", :error=>$!.to_s
|
76
|
+
$log.debug_backtrace $!
|
77
|
+
end
|
78
|
+
unless @table_list.include?(key)
|
79
|
+
database, table = key.split('.',2)
|
80
|
+
raise "Table #{key.inspect} does not exist on Treasure Data. Use 'td create-log-table #{database} #{table}' to create it."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
super(tag, es, chain, key)
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate_name(name)
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def format_stream(tag, es)
|
92
|
+
out = ''
|
93
|
+
es.each {|event|
|
94
|
+
record = event.record
|
95
|
+
record['time'] = event.time
|
96
|
+
record.to_msgpack(out)
|
97
|
+
}
|
98
|
+
out
|
99
|
+
end
|
100
|
+
|
101
|
+
def write(chunk)
|
102
|
+
database, table = chunk.key.split('.',2)
|
103
|
+
if !validate_name(database) || !validate_name(table)
|
104
|
+
$log.error "Invalid key name #{chunk.key.inspect}"
|
105
|
+
return
|
106
|
+
end
|
107
|
+
|
108
|
+
f = Tempfile.new("tdlog-", @tmpdir)
|
109
|
+
w = Zlib::GzipWriter.new(f)
|
110
|
+
|
111
|
+
chunk.write_to(w)
|
112
|
+
w.finish
|
113
|
+
w = nil
|
114
|
+
|
115
|
+
size = f.pos
|
116
|
+
f.pos = 0
|
117
|
+
upload(database, table, f, size)
|
118
|
+
|
119
|
+
ensure
|
120
|
+
w.close if w
|
121
|
+
f.close if f
|
122
|
+
end
|
123
|
+
|
124
|
+
def upload(database, table, io, size)
|
125
|
+
http, header = new_http
|
126
|
+
header['Content-Length'] = size.to_s
|
127
|
+
header['Content-Type'] = 'application/octet-stream'
|
128
|
+
|
129
|
+
url = "/v3/table/import/#{e database}/#{e table}/msgpack.gz"
|
130
|
+
|
131
|
+
req = Net::HTTP::Put.new(url, header)
|
132
|
+
if req.respond_to?(:body_stream=)
|
133
|
+
req.body_stream = io
|
134
|
+
else # Ruby 1.8
|
135
|
+
req.body = io.read
|
136
|
+
end
|
137
|
+
|
138
|
+
$log.trace { "uploading logs to Treasure Data database=#{database} table=#{table} (#{size}bytes)" }
|
139
|
+
|
140
|
+
response = http.request(req)
|
141
|
+
|
142
|
+
if response.code[0] != ?2
|
143
|
+
raise "Treasure Data upload failed: #{response.body}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_table_list
|
148
|
+
$log.info "updating table list from Treasure Data"
|
149
|
+
list = []
|
150
|
+
api_list_database.each {|db|
|
151
|
+
api_list_table(db).each {|t|
|
152
|
+
list << "#{db}.#{t}"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
list
|
156
|
+
end
|
157
|
+
|
158
|
+
def api_list_database
|
159
|
+
body = get("/v3/database/list")
|
160
|
+
js = JSON.load(body)
|
161
|
+
return js["databases"].map {|m| m['name'] }
|
162
|
+
end
|
163
|
+
|
164
|
+
def api_list_table(db)
|
165
|
+
body = get("/v3/table/list/#{e db}")
|
166
|
+
js = JSON.load(body)
|
167
|
+
return js["tables"].map {|m| m['name'] }
|
168
|
+
end
|
169
|
+
|
170
|
+
def get(path)
|
171
|
+
http, header = new_http
|
172
|
+
|
173
|
+
request = Net::HTTP::Get.new(path, header)
|
174
|
+
|
175
|
+
response = http.request(request)
|
176
|
+
|
177
|
+
if response.code[0] != ?2
|
178
|
+
raise "Treasure Data API failed: #{response.body}"
|
179
|
+
end
|
180
|
+
|
181
|
+
return response.body
|
182
|
+
end
|
183
|
+
|
184
|
+
def new_http
|
185
|
+
http = Net::HTTP.new(HOST, PORT)
|
186
|
+
if USE_SSL
|
187
|
+
http.use_ssl = true
|
188
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
189
|
+
store = OpenSSL::X509::Store.new
|
190
|
+
http.cert_store = store
|
191
|
+
end
|
192
|
+
|
193
|
+
# TODO read_timeout
|
194
|
+
#http.read_timeout = options[:read_timeout]
|
195
|
+
|
196
|
+
header = {}
|
197
|
+
header['Authorization'] = "TD1 #{@apikey}"
|
198
|
+
header['Date'] = Time.now.rfc2822
|
199
|
+
|
200
|
+
return http, header
|
201
|
+
end
|
202
|
+
|
203
|
+
def e(s)
|
204
|
+
CGI.escape(s.to_s)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-td
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 53
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 7
|
10
|
+
version: 0.9.7
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sadayuki Furuhashi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-06 00:00:00 +09:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: fluent
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 53
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 9
|
33
|
+
- 7
|
34
|
+
version: 0.9.7
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description:
|
38
|
+
email:
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README.rdoc
|
45
|
+
files:
|
46
|
+
- AUTHORS
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- example.conf
|
50
|
+
- fluent-plugin-td.gemspec
|
51
|
+
- lib/fluent/plugin/out_tdlog.rb
|
52
|
+
- README.rdoc
|
53
|
+
has_rdoc: true
|
54
|
+
homepage:
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.7
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Treasure Data plugin for Fluent event collector
|
87
|
+
test_files: []
|
88
|
+
|