fluent-plugin-pgjson 0.0.8 → 0.0.9
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.
- 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: ''
|