fluent-plugin-unix-client 0.1.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc18f5099028958215b671685ea41ebcd5c6754c0b1ee0eb46b3fc87f0b08226
4
- data.tar.gz: a0d5bbe77d1ccd49d9c5c7a452a3702397fdfd9c41da6f81fd7a35e509b43ba5
3
+ metadata.gz: e730565e1f497509cb2d5e579527d5b500ba3240312fa0bb6527972db7e57ff4
4
+ data.tar.gz: 9d4a45f7a0c3cbf6b30b00be745dc624ed79d5b7e10f9753c7af2a5728148cca
5
5
  SHA512:
6
- metadata.gz: f28da19e7f6291fad09fd1a19373d241db41c9d29eedc53b79e0f6432b7d9ad499dea0d5ba5d65440a2f1030e88888f63cbe3086f94eff21334c40811c2fb5c2
7
- data.tar.gz: 22b091803d9db3ae70d1df0fcf3d9a2e7077bbf83b62274b54512a3c9b9900f304e75308396e8f3742536f15dfac540a489da2f1c575c74d7734c389d9c00658
6
+ metadata.gz: 3a186a0de2333d95f32162f54b38fcab619b2e113122d0e08c7d10338aa9e3b64eb6c838ef34828772fec956d4c255bfd11be5c84af5aa703a631381bb7800b5
7
+ data.tar.gz: dfd15243ad040f815bd5c9c5dccb24c72bc57cadcbacc118cad2f4a1f0a597bfcbcc4d801e3294d63af04fbd440555d972165a33a08a88ef07a20a19e0c894a9
data/README.md CHANGED
@@ -48,8 +48,20 @@ The payload is read up to this character.
48
48
 
49
49
  Default value: `"\n"` (newline).
50
50
 
51
+ ### format_json (bool) (optional)
52
+
53
+ When recieved JSON data splitted by the delimiter is not completed, like '[{...},', trim '[', ']' and ',' characters to format.
54
+
55
+ Please see `Sample` below for details.
56
+
57
+ Default value: false.
58
+
51
59
  ## Sample
52
60
 
61
+ ### For JSON
62
+
63
+ Config:
64
+
53
65
  ```
54
66
  <source>
55
67
  @type unix_client
@@ -66,6 +78,53 @@ Default value: `"\n"` (newline).
66
78
  </match>
67
79
  ```
68
80
 
81
+ Assumed Data:
82
+
83
+ * ndjson
84
+ ```
85
+ {"key":0}\n
86
+ {"key":0}\n
87
+ ...
88
+ ```
89
+
90
+ * JSON list
91
+ ```
92
+ [{"key":0}, {"key":0}, ...]\n
93
+ [{"key":0}, {"key":0}, ...]\n
94
+ ...
95
+ ```
96
+
97
+ ### Use `format_json`
98
+
99
+ Config:
100
+
101
+ ```
102
+ <source>
103
+ @type unix_client
104
+ tag debug.unix_client
105
+ path /tmp/unix.sock
106
+ <parse>
107
+ @type json
108
+ </parse>
109
+ delimiter "\n"
110
+ format_json true
111
+ </source>
112
+
113
+ <match debug.**>
114
+ @type stdout
115
+ </match>
116
+ ```
117
+
118
+ Assumed Data:
119
+
120
+ ```
121
+ [{"key":0},\n
122
+ {"key":0},\n
123
+ ...
124
+ {"key":0}]\n
125
+ ...
126
+ ```
127
+
69
128
  ## Specification
70
129
 
71
130
  * This recieves data from UNIX domain socket which **is opened by another application**.
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "fluent-plugin-unix-client"
6
- spec.version = "0.1.0"
6
+ spec.version = "1.0.0"
7
7
  spec.authors = ["daipom"]
8
8
  spec.email = ["reangoapyththeorem@gmail.com"]
9
9
 
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "fluent/plugin/input"
17
17
  require 'socket'
18
+ require "json"
18
19
 
19
20
  module Fluent
20
21
  module Plugin
@@ -29,11 +30,19 @@ module Fluent
29
30
  config_param :path, :string
30
31
  desc 'The payload is read up to this character.'
31
32
  config_param :delimiter, :string, default: "\n"
33
+ desc "When recieved JSON data splitted by the delimiter is not completed, like '[{...},'," \
34
+ " trim '[', ']' and ',' characters to format."
35
+ config_param :format_json, :bool, default: false
32
36
 
33
37
  def configure(conf)
34
38
  super
35
39
  @parser = parser_create
36
- @socket_handler = SocketHandler.new(@path, delimiter: @delimiter, log: log)
40
+ @socket_handler = SocketHandler.new(
41
+ @path,
42
+ delimiter: @delimiter,
43
+ format_json: @format_json,
44
+ log: log,
45
+ )
37
46
  end
38
47
 
39
48
  def start
@@ -60,21 +69,34 @@ module Fluent
60
69
 
61
70
  raw_records.each do |raw_record|
62
71
  @parser.parse(raw_record) do |time, record|
63
- router.emit(@tag, time, record)
72
+ emit_one_parsed(time, record)
64
73
  end
65
74
  end
66
75
  end
76
+
77
+ def emit_one_parsed(time, record)
78
+ case record
79
+ when Array
80
+ es = Fluent::MultiEventStream.new
81
+ record.each do |e|
82
+ es.add(time, e)
83
+ end
84
+ router.emit_stream(@tag, es)
85
+ else
86
+ router.emit(@tag, time, record)
87
+ end
88
+ end
67
89
  end
68
90
 
69
91
 
70
92
  class SocketHandler
71
93
  MAX_LENGTH_RECEIVE_ONCE = 10000
72
94
 
73
- def initialize(path, delimiter: "\n", log: nil)
95
+ def initialize(path, delimiter: "\n", format_json: false, log: nil)
74
96
  @path = path
75
97
  @log = log
76
98
  @socket = nil
77
- @buf = Buffer.new(delimiter)
99
+ @buf = Buffer.new(delimiter, format_json: format_json)
78
100
  end
79
101
 
80
102
  def connected?
@@ -142,9 +164,10 @@ module Fluent
142
164
 
143
165
 
144
166
  class Buffer
145
- def initialize(delimiter)
167
+ def initialize(delimiter, format_json: false)
146
168
  @buf = ""
147
169
  @delimiter = delimiter
170
+ @format_json = format_json
148
171
  end
149
172
 
150
173
  def add(data)
@@ -160,7 +183,8 @@ module Fluent
160
183
 
161
184
  pos_read = 0
162
185
  while pos_next_delimiter = @buf.index(@delimiter, pos_read)
163
- records << @buf[pos_read...pos_next_delimiter]
186
+ fixed = fix_format(@buf[pos_read...pos_next_delimiter])
187
+ records << fixed unless fixed.empty?
164
188
  pos_read = pos_next_delimiter + @delimiter.size
165
189
  end
166
190
 
@@ -168,6 +192,41 @@ module Fluent
168
192
 
169
193
  records
170
194
  end
195
+
196
+ private
197
+
198
+ def fix_format(record)
199
+ return record if record.empty?
200
+ return record unless @format_json
201
+
202
+ fix_uncompleted_json(record)
203
+ end
204
+
205
+ def fix_uncompleted_json(record)
206
+ return record if is_correct_json(record)
207
+
208
+ # Assume uncompleted JSON such as "[{...},", "{...},", or "{...}]"
209
+
210
+ if record[0] == "["
211
+ record.slice!(0)
212
+ return record if record.empty?
213
+ end
214
+
215
+ if record[-1] == "," || record[-1] == "]"
216
+ record.slice!(-1)
217
+ return record if record.empty?
218
+ end
219
+
220
+ record
221
+ end
222
+
223
+ def is_correct_json(record)
224
+ # Just to check the format
225
+ JSON.parse(record)
226
+ return true
227
+ rescue JSON::ParserError
228
+ return false
229
+ end
171
230
  end
172
231
  end
173
232
  end
@@ -9,12 +9,11 @@ require_relative "./unix_server.rb"
9
9
  class UnixClientInputTest < Test::Unit::TestCase
10
10
  def setup
11
11
  Fluent::Test.setup
12
- @thread = nil
12
+ @server_thread = nil
13
13
  end
14
14
 
15
15
  def teardown
16
- @thread.kill if @thread
17
- @thread = nil
16
+ stop_server
18
17
  FileUtils.rm_rf(TMP_DIR)
19
18
  end
20
19
 
@@ -32,6 +31,14 @@ class UnixClientInputTest < Test::Unit::TestCase
32
31
  d = create_driver(config_with_json_parser)
33
32
  assert_equal "unix_client", d.instance.tag
34
33
  assert_equal "#{TMP_DIR}/socket.sock", d.instance.path
34
+ assert_equal false, d.instance.format_json
35
+ end
36
+
37
+ def test_configure_with_format_json
38
+ d = create_driver(config_with_json_parser_and_format_json)
39
+ assert_equal "unix_client", d.instance.tag
40
+ assert_equal "#{TMP_DIR}/socket.sock", d.instance.path
41
+ assert_equal true, d.instance.format_json
35
42
  end
36
43
 
37
44
  def test_receive_json
@@ -100,6 +107,67 @@ class UnixClientInputTest < Test::Unit::TestCase
100
107
  end
101
108
  end
102
109
 
110
+ def test_receive_json_list
111
+ d = create_driver(config_with_json_parser_and_format_json)
112
+ path = d.instance.path
113
+ delimiter = "\n"
114
+
115
+ msgs = ["hoge", "fuga", "foo"]
116
+
117
+ start_server(path)
118
+
119
+ d.run(expect_records: 3, timeout: 10) do
120
+ sleep 1
121
+ UNIXSocket.open(path) do |sock|
122
+ sock.write("[")
123
+ sock.write(JSON.generate(raw_data(msg: msgs[0])))
124
+ sock.write(",")
125
+ sock.write(delimiter)
126
+
127
+ sock.write(JSON.generate(raw_data(msg: msgs[1])))
128
+ sock.write(",")
129
+ sock.write(delimiter)
130
+
131
+ sock.write(JSON.generate(raw_data(msg: msgs[2])))
132
+ sock.write(delimiter)
133
+ sock.write("]")
134
+ sock.write(delimiter)
135
+ end
136
+ end
137
+
138
+ assert_equal 3, d.events.length
139
+
140
+ d.events.each_with_index do |event, i|
141
+ assert_equal msgs[i], event[2]["msg"]
142
+ end
143
+ end
144
+
145
+ def test_receive_json_list_with_one_delimiter
146
+ d = create_driver(config_with_json_parser_and_format_json)
147
+ path = d.instance.path
148
+ delimiter = "\n"
149
+
150
+ msgs = ["hoge", "fuga", "foo"]
151
+
152
+ start_server(path)
153
+
154
+ d.run(expect_records: 3, timeout: 10) do
155
+ sleep 1
156
+ data = msgs.map {|msg| raw_data(msg: msg)}
157
+
158
+ UNIXSocket.open(path) do |sock|
159
+ sock.write(JSON.generate(data))
160
+ sock.write(delimiter)
161
+ end
162
+ end
163
+
164
+ assert_equal 3, d.events.length
165
+
166
+ d.events.each_with_index do |event, i|
167
+ assert_equal msgs[i], event[2]["msg"]
168
+ end
169
+ end
170
+
103
171
  private
104
172
 
105
173
  def create_driver(conf)
@@ -124,24 +192,43 @@ class UnixClientInputTest < Test::Unit::TestCase
124
192
  !
125
193
  end
126
194
 
195
+ def config_with_json_parser_and_format_json
196
+ BASE_CONFIG + %!
197
+ format_json true
198
+ <parse>
199
+ @type json
200
+ </parse>
201
+ !
202
+ end
203
+
127
204
  def start_server(path)
128
- @thread = Thread.new do
205
+ @server_thread = Thread.new do
129
206
  server = UnixBroadcastServer.new(path)
130
207
  server.run
131
208
  end
132
209
  sleep 1
133
210
  end
134
211
 
212
+ def stop_server
213
+ if @server_thread
214
+ @server_thread.kill if @server_thread
215
+ @server_thread.join
216
+ @server_thread = nil
217
+ end
218
+ end
219
+
135
220
  def send_json(path, time: nil, msg: DEFAULT_MSG, delimiter: "\n")
136
- msg = JSON.generate(
137
- {
138
- "time" => time.nil? ? Time.now.to_i : time,
139
- "msg" => msg
140
- }
141
- )
221
+ msg = JSON.generate(raw_data(time: time, msg: msg))
142
222
  UNIXSocket.open(path) do |sock|
143
223
  sock.write(msg)
144
224
  sock.write(delimiter)
145
225
  end
146
226
  end
227
+
228
+ def raw_data(time: nil, msg: DEFAULT_MSG)
229
+ {
230
+ "time" => time.nil? ? Time.now.to_i : time,
231
+ "msg" => msg
232
+ }
233
+ end
147
234
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-unix-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - daipom
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-31 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,7 +72,7 @@ dependencies:
72
72
  - - "<"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '2'
75
- description:
75
+ description:
76
76
  email:
77
77
  - reangoapyththeorem@gmail.com
78
78
  executables: []
@@ -93,7 +93,7 @@ homepage: https://github.com/daipom/fluent-plugin-unix-client
93
93
  licenses:
94
94
  - Apache-2.0
95
95
  metadata: {}
96
- post_install_message:
96
+ post_install_message:
97
97
  rdoc_options: []
98
98
  require_paths:
99
99
  - lib
@@ -108,8 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  requirements: []
111
- rubygems_version: 3.2.15
112
- signing_key:
111
+ rubygems_version: 3.1.6
112
+ signing_key:
113
113
  specification_version: 4
114
114
  summary: Fluentd Input plugin to receive data from UNIX domain socket. This is a client
115
115
  version of the default `unix` input plugin.