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 ADDED
@@ -0,0 +1 @@
1
+ FURUHASHI Sadayuki <frsyuki _at_ gmail.com>
@@ -0,0 +1,7 @@
1
+ = Treasure Data output plugin for Fluent
2
+
3
+ == Copyright
4
+
5
+ Copyright:: Copyright (c) 2011 Treasure Data, Inc.
6
+ License:: Apache License, Version 2.0
7
+
@@ -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
@@ -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
+