fluent-plugin-pgjson 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +8 -0
- data/fluent-plugin-pgjson.gemspec +3 -1
- data/lib/fluent/plugin/out_pgjson.rb +44 -10
- data/test/helper.rb +3 -23
- data/test/plugin/test_out.rb +50 -19
- metadata +31 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7196d7c7b7181d2837c5a7e037caaf228335c1cac0b60b8212848d5b354652e3
|
4
|
+
data.tar.gz: a5c3f94dd289a27641c40daaa46a665b375afe9023ccbece49f44cd6338ff7fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: adc06d292c08d6bccc5b7ad4471abfbbe585af918044b306249f1c1f7ceb18eb3dd0eabbaedf2ddf04e62c88077d931288437be3faae7a272af731887d628e42
|
7
|
+
data.tar.gz: 7f627fdf20323d838f2254f7f64b101e5937e47b6113dd2e380759549195c23f26abf852fffc0679cd925bcfbb126736e486433343887e24f87eab4bd319752b
|
data/README.md
CHANGED
@@ -39,6 +39,12 @@ CREATE TABLE fluentd (
|
|
39
39
|
);
|
40
40
|
```
|
41
41
|
|
42
|
+
### Configurable JSON Encoder
|
43
|
+
|
44
|
+
Fluentd's standard JSON encoder is `yajl`.
|
45
|
+
`yajl` is robust for invalid byte sequence.
|
46
|
+
But this plugin's default value is `json` which is Ruby standard JSON encoder for backward compatibility.
|
47
|
+
|
42
48
|
## Configuration
|
43
49
|
|
44
50
|
### Example
|
@@ -73,6 +79,8 @@ CREATE TABLE fluentd (
|
|
73
79
|
|time_col|column name to insert time|time|
|
74
80
|
|tag_col|column name to insert tag|tag|
|
75
81
|
|record_col|column name to insert record|record|
|
82
|
+
|msgpack|use msgpack format for inserting records|false|
|
83
|
+
|encoder|choose prefer JSON encoder (yajl/json)|json|
|
76
84
|
|
77
85
|
## Copyright
|
78
86
|
|
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "fluent-plugin-pgjson"
|
6
|
-
s.version = "0.0.
|
6
|
+
s.version = "0.0.9"
|
7
7
|
s.authors = ["OKUNO Akihiro"]
|
8
8
|
s.email = ["choplin.choplin@gmail.com"]
|
9
9
|
s.homepage = "https://github.com/choplin/fluent-plugin-pgjson"
|
@@ -18,4 +18,6 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.add_runtime_dependency "fluentd"
|
20
20
|
s.add_runtime_dependency "pg"
|
21
|
+
s.add_development_dependency "test-unit", ">= 3.1.0"
|
22
|
+
s.add_development_dependency "rake", ">= 11.0"
|
21
23
|
end
|
@@ -1,8 +1,17 @@
|
|
1
|
-
|
1
|
+
require 'fluent/plugin/output'
|
2
|
+
require 'pg'
|
3
|
+
require 'yajl'
|
4
|
+
require 'json'
|
2
5
|
|
3
|
-
|
6
|
+
module Fluent::Plugin
|
7
|
+
|
8
|
+
class PgJsonOutput < Fluent::Output
|
4
9
|
Fluent::Plugin.register_output('pgjson', self)
|
5
10
|
|
11
|
+
helpers :compat_parameters
|
12
|
+
|
13
|
+
DEFAULT_BUFFER_TYPE = "memory"
|
14
|
+
|
6
15
|
config_param :host , :string , :default => 'localhost'
|
7
16
|
config_param :port , :integer , :default => 5432
|
8
17
|
config_param :sslmode , :string , :default => 'prefer'
|
@@ -14,35 +23,60 @@ class PgJsonOutput < Fluent::BufferedOutput
|
|
14
23
|
config_param :tag_col , :string , :default => 'tag'
|
15
24
|
config_param :record_col , :string , :default => 'record'
|
16
25
|
config_param :msgpack , :bool , :default => false
|
26
|
+
config_param :encoder , :enum, list: [:yajl, :json], :default => :json
|
27
|
+
config_param :time_format, :string , :default => '%F %T.%N %z'
|
28
|
+
|
29
|
+
config_section :buffer do
|
30
|
+
config_set_default :@type, DEFAULT_BUFFER_TYPE
|
31
|
+
config_set_default :chunk_keys, ['tag']
|
32
|
+
end
|
17
33
|
|
18
34
|
def initialize
|
19
35
|
super
|
20
|
-
require 'pg'
|
21
36
|
@conn = nil
|
22
37
|
end
|
23
38
|
|
24
39
|
def configure(conf)
|
40
|
+
compat_parameters_convert(conf, :buffer)
|
25
41
|
super
|
42
|
+
unless @chunk_key_tag
|
43
|
+
raise Fluent::ConfigError, "'tag' in chunk_keys is required."
|
44
|
+
end
|
45
|
+
@encoder = case @encoder
|
46
|
+
when :yajl
|
47
|
+
Yajl
|
48
|
+
when :json
|
49
|
+
JSON
|
50
|
+
end
|
26
51
|
end
|
27
52
|
|
28
53
|
def shutdown
|
29
|
-
super
|
30
|
-
|
31
54
|
if ! @conn.nil? and ! @conn.finished?
|
32
55
|
@conn.close()
|
33
56
|
end
|
57
|
+
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def formatted_to_msgpack_binary
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def multi_workers_ready?
|
66
|
+
true
|
34
67
|
end
|
35
68
|
|
36
69
|
def format(tag, time, record)
|
37
|
-
[
|
70
|
+
[Time.at(time).strftime(@time_format), record].to_msgpack
|
38
71
|
end
|
39
72
|
|
40
73
|
def write(chunk)
|
41
74
|
init_connection
|
42
75
|
@conn.exec("COPY #{@table} (#{@tag_col}, #{@time_col}, #{@record_col}) FROM STDIN WITH DELIMITER E'\\x01'")
|
43
76
|
begin
|
44
|
-
chunk.
|
45
|
-
|
77
|
+
tag = chunk.metadata.tag
|
78
|
+
chunk.msgpack_each do |time, record|
|
79
|
+
@conn.put_copy_data "#{tag}\x01#{time}\x01#{record_value(record)}\n"
|
46
80
|
end
|
47
81
|
rescue => err
|
48
82
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
@@ -62,7 +96,7 @@ class PgJsonOutput < Fluent::BufferedOutput
|
|
62
96
|
$log.debug "connecting to PostgreSQL server #{@host}:#{@port}, database #{@database}..."
|
63
97
|
|
64
98
|
begin
|
65
|
-
@conn =
|
99
|
+
@conn = PG::Connection.new(:dbname => @database, :host => @host, :port => @port, :sslmode => @sslmode, :user => @user, :password => @password)
|
66
100
|
rescue
|
67
101
|
if ! @conn.nil?
|
68
102
|
@conn.close()
|
@@ -77,7 +111,7 @@ class PgJsonOutput < Fluent::BufferedOutput
|
|
77
111
|
if @msgpack
|
78
112
|
"\\#{@conn.escape_bytea(record.to_msgpack)}"
|
79
113
|
else
|
80
|
-
json = record
|
114
|
+
json = @encoder.dump(record)
|
81
115
|
json.gsub!(/\\/){ '\\\\' }
|
82
116
|
json
|
83
117
|
end
|
data/test/helper.rb
CHANGED
@@ -1,27 +1,7 @@
|
|
1
|
-
require '
|
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
|
1
|
+
require 'bundler/setup'
|
10
2
|
require 'test/unit'
|
11
3
|
|
12
|
-
$LOAD_PATH.unshift(File.join(
|
13
|
-
$LOAD_PATH.unshift(
|
4
|
+
$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift(__dir__)
|
14
6
|
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
7
|
require 'fluent/plugin/out_pgjson'
|
25
|
-
|
26
|
-
class Test::Unit::TestCase
|
27
|
-
end
|
data/test/plugin/test_out.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
require 'pg'
|
2
2
|
require 'securerandom'
|
3
3
|
require 'helper'
|
4
|
+
require 'fluent/test/driver/output'
|
5
|
+
require 'fluent/test/helpers'
|
4
6
|
|
5
7
|
class PgJsonOutputTest < Test::Unit::TestCase
|
8
|
+
include Fluent::Test::Helpers
|
9
|
+
|
6
10
|
HOST = "localhost"
|
7
11
|
PORT = 5432
|
8
12
|
DATABASE = "postgres"
|
9
13
|
TABLE = "test_fluentd_#{SecureRandom.hex}"
|
10
|
-
USER = "postgres"
|
11
|
-
PASSWORD = "postgres"
|
14
|
+
USER = ENV["PSQL_USER"] || "postgres"
|
15
|
+
PASSWORD = ENV["PSQL_PASSWORD"] || "postgres"
|
12
16
|
|
13
17
|
TIME_COL = "time"
|
14
18
|
TAG_COL = "tag"
|
15
19
|
RECORD_COL = "record"
|
20
|
+
ENCODER = JSON
|
16
21
|
|
17
22
|
CONFIG = %[
|
18
23
|
type pgjson
|
@@ -31,8 +36,8 @@ class PgJsonOutputTest < Test::Unit::TestCase
|
|
31
36
|
Fluent::Test.setup
|
32
37
|
end
|
33
38
|
|
34
|
-
def create_driver(conf = CONFIG
|
35
|
-
Fluent::Test::
|
39
|
+
def create_driver(conf = CONFIG)
|
40
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::PgJsonOutput).configure(conf)
|
36
41
|
end
|
37
42
|
|
38
43
|
def test_configure
|
@@ -47,22 +52,46 @@ class PgJsonOutputTest < Test::Unit::TestCase
|
|
47
52
|
assert_equal TIME_COL, d.instance.time_col
|
48
53
|
assert_equal TAG_COL, d.instance.tag_col
|
49
54
|
assert_equal RECORD_COL, d.instance.record_col
|
55
|
+
assert_equal ENCODER, d.instance.encoder
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_invalid_chunk_keys
|
59
|
+
assert_raise_message(/'tag' in chunk_keys is required./) do
|
60
|
+
create_driver(Fluent::Config::Element.new(
|
61
|
+
'ROOT', '', {
|
62
|
+
'@type' => 'pgjson',
|
63
|
+
'host' => "#{HOST}",
|
64
|
+
'port' => "#{PORT}",
|
65
|
+
'database' => "#{DATABASE}",
|
66
|
+
'table' => "#{TABLE}",
|
67
|
+
'user' => "#{USER}",
|
68
|
+
'password' => "#{PASSWORD}",
|
69
|
+
'time_col' => "#{TIME_COL}",
|
70
|
+
'tag_col' => "#{TAG_COL}",
|
71
|
+
'record_col' => "#{RECORD_COL}",
|
72
|
+
}, [
|
73
|
+
Fluent::Config::Element.new('buffer', 'mykey', {
|
74
|
+
'chunk_keys' => 'mykey'
|
75
|
+
}, [])
|
76
|
+
]))
|
77
|
+
end
|
50
78
|
end
|
51
79
|
|
52
80
|
def test_write
|
53
81
|
with_connection do |conn|
|
54
82
|
tag = 'test'
|
55
|
-
time =
|
83
|
+
time = event_time("2014-12-26 07:58:37 UTC")
|
56
84
|
record = {"a"=>1}
|
57
85
|
|
58
|
-
d = create_driver(CONFIG
|
59
|
-
d.
|
60
|
-
|
86
|
+
d = create_driver(CONFIG)
|
87
|
+
d.run(default_tag: tag) do
|
88
|
+
d.feed(time, record)
|
89
|
+
end
|
61
90
|
wait_for_data(conn)
|
62
91
|
|
63
92
|
res = conn.exec("select * from #{TABLE}")[0]
|
64
93
|
assert_equal res[TAG_COL], tag
|
65
|
-
assert_equal
|
94
|
+
assert_equal event_time(res[TIME_COL]), time
|
66
95
|
assert_equal res[RECORD_COL], record.to_json
|
67
96
|
end
|
68
97
|
end
|
@@ -70,17 +99,18 @@ class PgJsonOutputTest < Test::Unit::TestCase
|
|
70
99
|
def test_escape_of_backslash
|
71
100
|
with_connection do |conn|
|
72
101
|
tag = 'test'
|
73
|
-
time =
|
102
|
+
time = event_time("2014-12-26 07:58:37 UTC")
|
74
103
|
record = {"a"=>"\"foo\""}
|
75
104
|
|
76
|
-
d = create_driver(CONFIG
|
77
|
-
d.
|
78
|
-
|
105
|
+
d = create_driver(CONFIG)
|
106
|
+
d.run(default_tag: tag) do
|
107
|
+
d.feed(time, record)
|
108
|
+
end
|
79
109
|
wait_for_data(conn)
|
80
110
|
|
81
111
|
res = conn.exec("select * from #{TABLE}")[0]
|
82
112
|
assert_equal res[TAG_COL], tag
|
83
|
-
assert_equal
|
113
|
+
assert_equal event_time(res[TIME_COL]), time
|
84
114
|
assert_equal res[RECORD_COL], record.to_json
|
85
115
|
end
|
86
116
|
end
|
@@ -88,17 +118,18 @@ class PgJsonOutputTest < Test::Unit::TestCase
|
|
88
118
|
def test_invalid_json
|
89
119
|
with_connection do |conn|
|
90
120
|
tag = 'test'
|
91
|
-
time =
|
121
|
+
time = event_time("2014-12-26 07:58:37 UTC")
|
92
122
|
|
93
|
-
d = create_driver(CONFIG
|
123
|
+
d = create_driver(CONFIG)
|
94
124
|
instance = d.instance
|
95
125
|
def instance.record_value(record)
|
96
126
|
'invalid json'
|
97
127
|
end
|
98
|
-
d.emit('', time.to_i)
|
99
128
|
|
100
129
|
assert_raise RuntimeError do
|
101
|
-
d.run
|
130
|
+
d.run(default_tag: tag) do
|
131
|
+
d.feed(time, {})
|
132
|
+
end
|
102
133
|
end
|
103
134
|
end
|
104
135
|
end
|
@@ -108,7 +139,7 @@ class PgJsonOutputTest < Test::Unit::TestCase
|
|
108
139
|
conn = nil
|
109
140
|
|
110
141
|
assert_nothing_raised do
|
111
|
-
conn =
|
142
|
+
conn = PG::Connection.new(:dbname => DATABASE, :host => HOST, :port => PORT, :user => USER, :password => PASSWORD)
|
112
143
|
end
|
113
144
|
|
114
145
|
conn
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-pgjson
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKUNO Akihiro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.1.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '11.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '11.0'
|
41
69
|
description: ''
|
42
70
|
email:
|
43
71
|
- choplin.choplin@gmail.com
|
@@ -75,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
103
|
version: '0'
|
76
104
|
requirements: []
|
77
105
|
rubyforge_project:
|
78
|
-
rubygems_version: 2.
|
106
|
+
rubygems_version: 2.7.6
|
79
107
|
signing_key:
|
80
108
|
specification_version: 4
|
81
109
|
summary: ''
|