fluent-plugin-postgres 0.0.1
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 +18 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +133 -0
- data/Rakefile +11 -0
- data/fluent-plugin-postgres.gemspec +21 -0
- data/lib/fluent/plugin/out_postgres.rb +74 -0
- data/test/helper.rb +28 -0
- data/test/plugin/test_out_postgres.rb +107 -0
- metadata +113 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2013 Uken Games
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# fluent-plugin-postgres
|
2
|
+
|
3
|
+
|
4
|
+
## Changes from mysql:
|
5
|
+
|
6
|
+
- We currently don't suppor json format
|
7
|
+
- You need to specify a SQL query
|
8
|
+
- Placeholders are numbered (yeah, I know).
|
9
|
+
|
10
|
+
Other than that, just bear in mind that it's Postgres SQL.
|
11
|
+
|
12
|
+
### Quick example
|
13
|
+
|
14
|
+
<match output.by.sql.*>
|
15
|
+
type postgres
|
16
|
+
host master.db.service.local
|
17
|
+
# port 3306 # default
|
18
|
+
database application_logs
|
19
|
+
username myuser
|
20
|
+
password mypass
|
21
|
+
key_names status,bytes,vhost,path,rhost,agent,referer
|
22
|
+
sql INSERT INTO accesslog (status,bytes,vhost,path,rhost,agent,referer) VALUES ($1,$2,$3,$4,$5,$6,$7)
|
23
|
+
flush_intervals 5s
|
24
|
+
</match>
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
## Component
|
29
|
+
|
30
|
+
### PostgresOutput
|
31
|
+
|
32
|
+
Plugin to store Postgres tables over SQL, to each columns per values, or to single column as json.
|
33
|
+
|
34
|
+
## Configuration
|
35
|
+
|
36
|
+
### MysqlOutput
|
37
|
+
|
38
|
+
MysqlOutput needs MySQL server's host/port/database/username/password, and INSERT format as SQL, or as table name and columns.
|
39
|
+
|
40
|
+
<match output.by.sql.*>
|
41
|
+
type mysql
|
42
|
+
host master.db.service.local
|
43
|
+
# port 3306 # default
|
44
|
+
database application_logs
|
45
|
+
username myuser
|
46
|
+
password mypass
|
47
|
+
key_names status,bytes,vhost,path,rhost,agent,referer
|
48
|
+
sql INSERT INTO accesslog (status,bytes,vhost,path,rhost,agent,referer) VALUES (?,?,?,?,?,?,?)
|
49
|
+
flush_intervals 5s
|
50
|
+
</match>
|
51
|
+
|
52
|
+
<match output.by.names.*>
|
53
|
+
type mysql
|
54
|
+
host master.db.service.local
|
55
|
+
database application_logs
|
56
|
+
username myuser
|
57
|
+
password mypass
|
58
|
+
key_names status,bytes,vhost,path,rhost,agent,referer
|
59
|
+
table accesslog
|
60
|
+
# 'columns' names order must be same with 'key_names'
|
61
|
+
columns status,bytes,vhost,path,rhost,agent,referer
|
62
|
+
flush_intervals 5s
|
63
|
+
</match>
|
64
|
+
|
65
|
+
Or, insert json into single column.
|
66
|
+
|
67
|
+
<match output.as.json.*>
|
68
|
+
type mysql
|
69
|
+
host master.db.service.local
|
70
|
+
database application_logs
|
71
|
+
username root
|
72
|
+
table accesslog
|
73
|
+
columns jsondata
|
74
|
+
format json
|
75
|
+
flush_intervals 5s
|
76
|
+
</match>
|
77
|
+
|
78
|
+
To include time/tag into output, use `include_time_key` and `include_tag_key`, like this:
|
79
|
+
|
80
|
+
<match output.with.tag.and.time.*>
|
81
|
+
type mysql
|
82
|
+
host my.mysql.local
|
83
|
+
database anydatabase
|
84
|
+
username yourusername
|
85
|
+
password secret
|
86
|
+
|
87
|
+
include_time_key yes
|
88
|
+
### default `time_format` is ISO-8601
|
89
|
+
# time_format %Y%m%d-%H%M%S
|
90
|
+
### default `time_key` is 'time'
|
91
|
+
# time_key timekey
|
92
|
+
|
93
|
+
include_tag_key yes
|
94
|
+
### default `tag_key` is 'tag'
|
95
|
+
# tag_key tagkey
|
96
|
+
|
97
|
+
table anydata
|
98
|
+
key_names time,tag,field1,field2,field3,field4
|
99
|
+
sql INSERT INTO baz (coltime,coltag,col1,col2,col3,col4) VALUES (?,?,?,?,?,?)
|
100
|
+
</match>
|
101
|
+
|
102
|
+
Or, for json:
|
103
|
+
|
104
|
+
<match output.with.tag.and.time.as.json.*>
|
105
|
+
type mysql
|
106
|
+
host database.local
|
107
|
+
database foo
|
108
|
+
username root
|
109
|
+
|
110
|
+
include_time_key yes
|
111
|
+
utc # with UTC timezome output (default: localtime)
|
112
|
+
time_format %Y%m%d-%H%M%S
|
113
|
+
time_key timeattr
|
114
|
+
|
115
|
+
include_tag_key yes
|
116
|
+
tag_key tagattr
|
117
|
+
table accesslog
|
118
|
+
columns jsondata
|
119
|
+
format json
|
120
|
+
</match>
|
121
|
+
#=> inserted json data into column 'jsondata' with addtional attribute 'timeattr' and 'tagattr'
|
122
|
+
|
123
|
+
## TODO
|
124
|
+
|
125
|
+
* implement 'tag_mapped'
|
126
|
+
* dynamic tag based table selection
|
127
|
+
|
128
|
+
## Copyright
|
129
|
+
|
130
|
+
* Copyright
|
131
|
+
* Copyright 2013 Uken Games
|
132
|
+
* License
|
133
|
+
* Apache License, Version 2.0
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'fluent-plugin-postgres'
|
4
|
+
s.version = '0.0.1'
|
5
|
+
s.authors = ['TAGOMORI Satoshi', 'Diogo Terror', 'pitr']
|
6
|
+
s.email = ['team@uken.com']
|
7
|
+
s.description = %q{fluent plugin to insert on PostgreSQL}
|
8
|
+
s.summary = %q{fluent plugin to insert on PostgreSQL}
|
9
|
+
s.homepage = 'https://github.com/uken/fluent-plugin-postgres'
|
10
|
+
s.license = 'Apache 2.0'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split($\)
|
13
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.add_dependency 'fluentd'
|
18
|
+
s.add_dependency 'pg'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Fluent::PostgresOutput < Fluent::BufferedOutput
|
2
|
+
Fluent::Plugin.register_output('postgres', self)
|
3
|
+
|
4
|
+
include Fluent::SetTimeKeyMixin
|
5
|
+
include Fluent::SetTagKeyMixin
|
6
|
+
|
7
|
+
config_param :host, :string
|
8
|
+
config_param :port, :integer, :default => nil
|
9
|
+
config_param :database, :string
|
10
|
+
config_param :username, :string
|
11
|
+
config_param :password, :string, :default => ''
|
12
|
+
|
13
|
+
config_param :key_names, :string, :default => nil # nil allowed for json format
|
14
|
+
config_param :sql, :string, :default => nil
|
15
|
+
config_param :table, :string, :default => nil
|
16
|
+
config_param :columns, :string, :default => nil
|
17
|
+
|
18
|
+
config_param :format, :string, :default => "raw" # or json
|
19
|
+
|
20
|
+
attr_accessor :handler
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
require 'pg'
|
25
|
+
end
|
26
|
+
|
27
|
+
# We don't currently support mysql's analogous json format
|
28
|
+
def configure(conf)
|
29
|
+
super
|
30
|
+
|
31
|
+
if @format == 'json'
|
32
|
+
@format_proc = Proc.new{|tag, time, record| record.to_json}
|
33
|
+
else
|
34
|
+
@key_names = @key_names.split(',')
|
35
|
+
@format_proc = Proc.new{|tag, time, record| @key_names.map{|k| record[k]}}
|
36
|
+
end
|
37
|
+
|
38
|
+
if @columns.nil? and @sql.nil?
|
39
|
+
raise Fluent::ConfigError, "columns or sql MUST be specified, but missing"
|
40
|
+
end
|
41
|
+
if @columns and @sql
|
42
|
+
raise Fluent::ConfigError, "both of columns and sql are specified, but specify one of them"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def shutdown
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def format(tag, time, record)
|
55
|
+
[tag, time, @format_proc.call(tag, time, record)].to_msgpack
|
56
|
+
end
|
57
|
+
|
58
|
+
def client
|
59
|
+
PG::Connection.new({
|
60
|
+
:host => @host, :port => @port,
|
61
|
+
:user => @username, :password => @password,
|
62
|
+
:dbname => @database
|
63
|
+
})
|
64
|
+
end
|
65
|
+
|
66
|
+
def write(chunk)
|
67
|
+
handler = self.client
|
68
|
+
handler.prepare("write", @sql)
|
69
|
+
chunk.msgpack_each { |tag, time, data|
|
70
|
+
handler.exec_prepared("write", data)
|
71
|
+
}
|
72
|
+
handler.close
|
73
|
+
end
|
74
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
|
+
require 'fluent/test'
|
15
|
+
unless ENV.has_key?('VERBOSE')
|
16
|
+
nulllogger = Object.new
|
17
|
+
nulllogger.instance_eval {|obj|
|
18
|
+
def method_missing(method, *args)
|
19
|
+
# pass
|
20
|
+
end
|
21
|
+
}
|
22
|
+
$log = nulllogger
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'fluent/plugin/out_postgres'
|
26
|
+
|
27
|
+
class Test::Unit::TestCase
|
28
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'pg'
|
3
|
+
|
4
|
+
class PostgresOutputTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Fluent::Test.setup
|
7
|
+
end
|
8
|
+
|
9
|
+
CONFIG = %[
|
10
|
+
host database.local
|
11
|
+
database foo
|
12
|
+
username bar
|
13
|
+
password mogera
|
14
|
+
key_names field1,field2,field3
|
15
|
+
sql INSERT INTO baz (col1,col2,col3,col4) VALUES (?,?,?,?)
|
16
|
+
]
|
17
|
+
|
18
|
+
def create_driver(conf=CONFIG, tag='test')
|
19
|
+
d = Fluent::Test::BufferedOutputTestDriver.new(Fluent::PostgresOutput, tag).configure(conf)
|
20
|
+
d.instance.instance_eval {
|
21
|
+
def client
|
22
|
+
obj = Object.new
|
23
|
+
obj.instance_eval {
|
24
|
+
def prepare(*args); true; end
|
25
|
+
def exec_prepared(*args); true; end
|
26
|
+
def close; true; end
|
27
|
+
}
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
}
|
31
|
+
d
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_configure_fails_if_both_cols_and_sql_specified
|
35
|
+
assert_raise(Fluent::ConfigError) {
|
36
|
+
create_driver %[
|
37
|
+
host database.local
|
38
|
+
database foo
|
39
|
+
username bar
|
40
|
+
password mogera
|
41
|
+
key_names field1,field2,field3
|
42
|
+
sql INSERT INTO baz (col1,col2,col3,col4) VALUES (?,?,?,?)
|
43
|
+
columns col1,col2,col3,col4
|
44
|
+
]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_configure_fails_if_neither_cols_or_sql_specified
|
49
|
+
assert_raise(Fluent::ConfigError) {
|
50
|
+
create_driver %[
|
51
|
+
host database.local
|
52
|
+
database foo
|
53
|
+
username bar
|
54
|
+
password mogera
|
55
|
+
key_names field1,field2,field3
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_time_and_tag_key
|
61
|
+
d = create_driver %[
|
62
|
+
host database.local
|
63
|
+
database foo
|
64
|
+
username bar
|
65
|
+
password mogera
|
66
|
+
include_time_key yes
|
67
|
+
utc
|
68
|
+
include_tag_key yes
|
69
|
+
table baz
|
70
|
+
key_names time,tag,field1,field2,field3,field4
|
71
|
+
sql INSERT INTO baz (coltime,coltag,col1,col2,col3,col4) VALUES (?,?,?,?,?,?)
|
72
|
+
]
|
73
|
+
assert_equal 'INSERT INTO baz (coltime,coltag,col1,col2,col3,col4) VALUES (?,?,?,?,?,?)', d.instance.sql
|
74
|
+
|
75
|
+
time = Time.utc(2012,12,17,1,23,45).to_i
|
76
|
+
record = {'field1'=>'value1','field2'=>'value2','field3'=>'value3','field4'=>'value4'}
|
77
|
+
d.emit(record, time)
|
78
|
+
d.expect_format ['test', time, ['2012-12-17T01:23:45Z','test','value1','value2','value3','value4']].to_msgpack
|
79
|
+
d.run
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_time_and_tag_key_complex
|
83
|
+
d = create_driver %[
|
84
|
+
host database.local
|
85
|
+
database foo
|
86
|
+
username bar
|
87
|
+
password mogera
|
88
|
+
include_time_key yes
|
89
|
+
utc
|
90
|
+
time_format %Y%m%d-%H%M%S
|
91
|
+
time_key timekey
|
92
|
+
include_tag_key yes
|
93
|
+
tag_key tagkey
|
94
|
+
table baz
|
95
|
+
key_names timekey,tagkey,field1,field2,field3,field4
|
96
|
+
sql INSERT INTO baz (coltime,coltag,col1,col2,col3,col4) VALUES (?,?,?,?,?,?)
|
97
|
+
]
|
98
|
+
assert_equal 'INSERT INTO baz (coltime,coltag,col1,col2,col3,col4) VALUES (?,?,?,?,?,?)', d.instance.sql
|
99
|
+
|
100
|
+
time = Time.new(2012,12,17,9,23,45,'+09:00').to_i
|
101
|
+
record = {'field1'=>'value1','field2'=>'value2','field3'=>'value3','field4'=>'value4'}
|
102
|
+
d.emit(record, time)
|
103
|
+
d.expect_format ['test', time, ['20121217-002345','test','value1','value2','value3','value4']].to_msgpack
|
104
|
+
d.run
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-postgres
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- TAGOMORI Satoshi
|
9
|
+
- Diogo Terror
|
10
|
+
- pitr
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-02-11 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: fluentd
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: pg
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
description: fluent plugin to insert on PostgreSQL
|
65
|
+
email:
|
66
|
+
- team@uken.com
|
67
|
+
executables: []
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- .gitignore
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- fluent-plugin-postgres.gemspec
|
77
|
+
- lib/fluent/plugin/out_postgres.rb
|
78
|
+
- test/helper.rb
|
79
|
+
- test/plugin/test_out_postgres.rb
|
80
|
+
homepage: https://github.com/uken/fluent-plugin-postgres
|
81
|
+
licenses:
|
82
|
+
- Apache 2.0
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
hash: 4318069174566996150
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
hash: 4318069174566996150
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.23
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: fluent plugin to insert on PostgreSQL
|
111
|
+
test_files:
|
112
|
+
- test/helper.rb
|
113
|
+
- test/plugin/test_out_postgres.rb
|