fluent-plugin-json-in-json-2 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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +49 -0
- data/Rakefile +10 -0
- data/fluent-plugin-json-in-json.gemspec +25 -0
- data/lib/fluent/plugin/parser_json_in_json.rb +46 -0
- data/test/helper.rb +128 -0
- data/test/test_parser.rb +54 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e99746f88495ff8ed13fd6d295a4522cc641172c
|
4
|
+
data.tar.gz: 870714d1b589915f6642de346c7ca21f19f7a0cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f0ebd7c9647c3e36afa83c732929339c8ddab29b5d02b592799e76687024842f5f51c15cc29640a2a4397577290c128f8bbfe798d087a7e25a40ae2f5f51f38b
|
7
|
+
data.tar.gz: 4ee7b4768620d0179f60e63776b631207b63650d766de565a689ead87ac75a8deda9ed913f2b0f234a29f05ef98a0bb42abf4437b63b4f9339bcffddd78c81e6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# fluent-plugin-json-in-json
|
2
|
+
|
3
|
+
## Requirements
|
4
|
+
|
5
|
+
| fluent-plugin-json-in-json| Fluentd | Ruby |
|
6
|
+
|---------------------------|-------------|--------|
|
7
|
+
| >= 1.0.0 | >= v0.14.0 | >= 2.1 |
|
8
|
+
| < 1.0.0 | >= v0.12.0 | >= 1.9 |
|
9
|
+
|
10
|
+
This fluentd parser plugin parses JSON log lines with nested JSON strings. For
|
11
|
+
example, given a docker log of ``{"log": "{\"foo\": \"bar\"}"}``, the log record
|
12
|
+
will be parsed into ``{:log => { :foo => "bar" }}``.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
gem 'fluent-plugin-json-in-json'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install fluent-plugin-json-in-json
|
27
|
+
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
```
|
32
|
+
<source>
|
33
|
+
type tail
|
34
|
+
path /var/lib/docker/containers/*/*-json.log
|
35
|
+
pos_file /var/log/fluentd-docker.pos
|
36
|
+
time_format %Y-%m-%dT%H:%M:%S
|
37
|
+
tag docker.*
|
38
|
+
format json_in_json
|
39
|
+
read_from_head true
|
40
|
+
</source>
|
41
|
+
```
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
1. Fork it
|
46
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
47
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
48
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
49
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
Gem::Specification.new do |spec|
|
3
|
+
spec.name = "fluent-plugin-json-in-json-2"
|
4
|
+
spec.version = "1.0.0"
|
5
|
+
spec.authors = ["Gavin M. Roy", "Arcadiy Ivanov", "Alik Khilazhev"]
|
6
|
+
spec.email = ["gavinmroy@gmail.com", "arcadiy@ivanov.biz", "alikhil@mail.ru"]
|
7
|
+
spec.description = %q{Parser plugin that parses JSON attributes with JSON strings in them}
|
8
|
+
spec.summary = %q{Parser plugin that parses JSON attributes with JSON strings in them}
|
9
|
+
spec.homepage = "https://github.com/arcivanov/fluent-plugin-json-in-json"
|
10
|
+
spec.license = "BSD"
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_runtime_dependency 'fluentd', ['>= 0.14.0', '< 2']
|
18
|
+
spec.add_runtime_dependency 'yajl-ruby', '~> 1.0'
|
19
|
+
|
20
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'test-unit', ['~> 3.2']
|
24
|
+
spec.add_development_dependency 'test-unit-rr', ['~> 1.0']
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'fluent/parser'
|
2
|
+
require 'yajl'
|
3
|
+
|
4
|
+
module Fluent
|
5
|
+
module Plugin
|
6
|
+
class JSONInJSONParser < Parser
|
7
|
+
Plugin.register_parser('json_in_json', self)
|
8
|
+
|
9
|
+
config_set_default :time_key, 'time'
|
10
|
+
config_set_default :time_type, :float
|
11
|
+
|
12
|
+
def configure(conf)
|
13
|
+
if conf.has_key?('time_format')
|
14
|
+
conf['time_type'] ||= 'string'
|
15
|
+
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(text)
|
21
|
+
record = Yajl.load(text)
|
22
|
+
|
23
|
+
values = Hash.new
|
24
|
+
|
25
|
+
record.each do |k, v|
|
26
|
+
if v.is_a?(String) && /^\s*(\{|\[)/ =~ v
|
27
|
+
deserialized = Yajl.load(v)
|
28
|
+
if deserialized.is_a?(Hash)
|
29
|
+
values.merge!(deserialized)
|
30
|
+
record.delete k
|
31
|
+
elsif deserialized.is_a?(Array)
|
32
|
+
values[k] = deserialized
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
record.merge!(values)
|
37
|
+
|
38
|
+
time, record = convert_values(parse_time(record), record)
|
39
|
+
|
40
|
+
yield time, record
|
41
|
+
rescue Yajl::ParseError
|
42
|
+
yield nil, nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Some tests use Hash instead of Element for configure.
|
2
|
+
# We should rewrite these tests in the future and remove this ad-hoc code
|
3
|
+
class Hash
|
4
|
+
def corresponding_proxies
|
5
|
+
@corresponding_proxies ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_masked_element
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rr'
|
14
|
+
require 'test/unit'
|
15
|
+
require 'test/unit/rr'
|
16
|
+
require 'fileutils'
|
17
|
+
require 'fluent/config/element'
|
18
|
+
require 'fluent/log'
|
19
|
+
require 'fluent/test'
|
20
|
+
require 'fluent/test/helpers'
|
21
|
+
require 'fluent/plugin/base'
|
22
|
+
require 'fluent/plugin_id'
|
23
|
+
require 'fluent/plugin_helper'
|
24
|
+
require 'fluent/msgpack_factory'
|
25
|
+
require 'fluent/time'
|
26
|
+
require 'serverengine'
|
27
|
+
|
28
|
+
module Fluent
|
29
|
+
module Plugin
|
30
|
+
class TestBase < Base
|
31
|
+
# a base plugin class, but not input nor output
|
32
|
+
# mainly for helpers and owned plugins
|
33
|
+
include PluginId
|
34
|
+
include PluginLoggerMixin
|
35
|
+
include PluginHelper::Mixin
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
unless defined?(Test::Unit::AssertionFailedError)
|
41
|
+
class Test::Unit::AssertionFailedError < StandardError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
include Fluent::Test::Helpers
|
46
|
+
|
47
|
+
def unused_port(num = 1, protocol: :tcp, bind: "0.0.0.0")
|
48
|
+
case protocol
|
49
|
+
when :tcp
|
50
|
+
unused_port_tcp(num)
|
51
|
+
when :udp
|
52
|
+
unused_port_udp(num, bind: bind)
|
53
|
+
else
|
54
|
+
raise ArgumentError, "unknown protocol: #{protocol}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def unused_port_tcp(num = 1)
|
59
|
+
ports = []
|
60
|
+
sockets = []
|
61
|
+
num.times do
|
62
|
+
s = TCPServer.open(0)
|
63
|
+
sockets << s
|
64
|
+
ports << s.addr[1]
|
65
|
+
end
|
66
|
+
sockets.each{|s| s.close }
|
67
|
+
if num == 1
|
68
|
+
return ports.first
|
69
|
+
else
|
70
|
+
return *ports
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
PORT_RANGE_AVAILABLE = (1024...65535)
|
75
|
+
|
76
|
+
def unused_port_udp(num = 1, bind: "0.0.0.0")
|
77
|
+
family = IPAddr.new(IPSocket.getaddress(bind)).ipv4? ? ::Socket::AF_INET : ::Socket::AF_INET6
|
78
|
+
ports = []
|
79
|
+
sockets = []
|
80
|
+
while ports.size < num
|
81
|
+
port = rand(PORT_RANGE_AVAILABLE)
|
82
|
+
u = UDPSocket.new(family)
|
83
|
+
if (u.bind(bind, port) rescue nil)
|
84
|
+
ports << port
|
85
|
+
sockets << u
|
86
|
+
else
|
87
|
+
u.close
|
88
|
+
end
|
89
|
+
end
|
90
|
+
sockets.each{|s| s.close }
|
91
|
+
if num == 1
|
92
|
+
return ports.first
|
93
|
+
else
|
94
|
+
return *ports
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def waiting(seconds, logs: nil, plugin: nil)
|
99
|
+
begin
|
100
|
+
Timeout.timeout(seconds) do
|
101
|
+
yield
|
102
|
+
end
|
103
|
+
rescue Timeout::Error
|
104
|
+
if logs
|
105
|
+
STDERR.print(*logs)
|
106
|
+
elsif plugin
|
107
|
+
STDERR.print(*plugin.log.out.logs)
|
108
|
+
end
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def ipv6_enabled?
|
114
|
+
require 'socket'
|
115
|
+
|
116
|
+
begin
|
117
|
+
TCPServer.open("::1", 0)
|
118
|
+
true
|
119
|
+
rescue
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
dl_opts = {}
|
125
|
+
dl_opts[:log_level] = ServerEngine::DaemonLogger::WARN
|
126
|
+
logdev = Fluent::Test::DummyLogDevice.new
|
127
|
+
logger = ServerEngine::DaemonLogger.new(logdev, dl_opts)
|
128
|
+
$log ||= Fluent::Log.new(logger)
|
data/test/test_parser.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'fluent/test/driver/parser'
|
3
|
+
require 'fluent/plugin/parser_json_in_json'
|
4
|
+
|
5
|
+
class JsonInJsonParserTest < ::Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
Fluent::Test.setup
|
8
|
+
@parser = Fluent::Test::Driver::Parser.new(Fluent::Plugin::JSONInJSONParser)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_parse_float_time()
|
12
|
+
@parser.configure({})
|
13
|
+
@parser.instance.parse('{"time":1362020400,"host":"192.168.0.1","size":777,"method":"PUT","log_json":" {\\"field1\\":\\"field1 value\\",\\"field2\\":40}"}') { |time, record|
|
14
|
+
assert_equal(event_time('2013-02-28 12:00:00 +0900').to_f, time.to_f)
|
15
|
+
assert_equal({
|
16
|
+
'host' => '192.168.0.1',
|
17
|
+
'size' => 777,
|
18
|
+
'method' => 'PUT',
|
19
|
+
'field1' => 'field1 value',
|
20
|
+
'field2' => 40
|
21
|
+
}, record)
|
22
|
+
}
|
23
|
+
@parser.instance.parse('{"time":1362020400,"host":"192.168.0.1","size":777,"method":"PUT","log_json":"{\\"field1\\":"}') { |time, record|
|
24
|
+
assert_equal(event_time('2013-02-28 12:00:00 +0900').to_f, time.to_f)
|
25
|
+
assert_equal({
|
26
|
+
'host' => '192.168.0.1',
|
27
|
+
'size' => 777,
|
28
|
+
'method' => 'PUT',
|
29
|
+
'log_json' => '{"field1":'
|
30
|
+
}, record)
|
31
|
+
}
|
32
|
+
@parser.instance.parse('{"time":1362020400,"host":"192.168.0.1","size":777,"method":"PUT","log_json":"[\\"val1\\",\\"val2\\"]"') { |time, record|
|
33
|
+
assert_equal(event_time('2013-02-28 12:00:00 +0900').to_f, time.to_f)
|
34
|
+
assert_equal({
|
35
|
+
'host' => '192.168.0.1',
|
36
|
+
'size' => 777,
|
37
|
+
'method' => 'PUT',
|
38
|
+
'log_json' => ['val1', 'val2']
|
39
|
+
}, record)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_parse_string_time()
|
44
|
+
@parser.configure('time_format' => '%Y-%m-%dT%H:%M:%S.%NZ', 'keep_time_key' => 'true')
|
45
|
+
@parser.instance.parse('{"log":"2018-06-26 13:20:44.075 INFO --- [pool-8-thread-3] outgoing","stream":"stdout","time":"2018-06-26T13:20:44.076022960Z"}') { |time, record|
|
46
|
+
assert_equal(event_time('2018-06-26 13:20:44.076022960 -0400').to_f, time.to_f)
|
47
|
+
assert_equal({
|
48
|
+
'log'=>'2018-06-26 13:20:44.075 INFO --- [pool-8-thread-3] outgoing',
|
49
|
+
'stream'=>'stdout',
|
50
|
+
'time'=>'2018-06-26T13:20:44.076022960Z'
|
51
|
+
}, record)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-json-in-json-2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gavin M. Roy
|
8
|
+
- Arcadiy Ivanov
|
9
|
+
- Alik Khilazhev
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2019-06-17 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: fluentd
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.14.0
|
22
|
+
- - "<"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '2'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.14.0
|
32
|
+
- - "<"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2'
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yajl-ruby
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.0'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.0'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '12.3'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '12.3'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: bundler
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.16'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '1.16'
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: test-unit
|
79
|
+
requirement: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3.2'
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '3.2'
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: test-unit-rr
|
93
|
+
requirement: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.0'
|
98
|
+
type: :development
|
99
|
+
prerelease: false
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '1.0'
|
105
|
+
description: Parser plugin that parses JSON attributes with JSON strings in them
|
106
|
+
email:
|
107
|
+
- gavinmroy@gmail.com
|
108
|
+
- arcadiy@ivanov.biz
|
109
|
+
- alikhil@mail.ru
|
110
|
+
executables: []
|
111
|
+
extensions: []
|
112
|
+
extra_rdoc_files: []
|
113
|
+
files:
|
114
|
+
- ".gitignore"
|
115
|
+
- Gemfile
|
116
|
+
- README.md
|
117
|
+
- Rakefile
|
118
|
+
- fluent-plugin-json-in-json.gemspec
|
119
|
+
- lib/fluent/plugin/parser_json_in_json.rb
|
120
|
+
- test/helper.rb
|
121
|
+
- test/test_parser.rb
|
122
|
+
homepage: https://github.com/arcivanov/fluent-plugin-json-in-json
|
123
|
+
licenses:
|
124
|
+
- BSD
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.5.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Parser plugin that parses JSON attributes with JSON strings in them
|
146
|
+
test_files:
|
147
|
+
- test/helper.rb
|
148
|
+
- test/test_parser.rb
|