fluent-plugin-timestream 0.1.0 → 0.2.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: 548e43a61eddcb48d440e888d4708422698da78b5584a7c52d4185aee5aa5c07
4
- data.tar.gz: eddbfba2ca3458b060431ef6bc38ecdbeda4bebf4800f84577d824108c7107eb
3
+ metadata.gz: c6da936981868da70ec9cd3817cc0b386c23b09723bb6b8ec8ccc5f2a8a0140f
4
+ data.tar.gz: d996e437118d2db8f58ed1e80a5a82412c8f08c666dcf827532bbc40ab588454
5
5
  SHA512:
6
- metadata.gz: d8da4123c8e56eaa782a98246002708582d2120acb5a2f9d012d0d4a8624b275d13671868765e4276dfea3e323c90d1b3a6f65ed57e2f23b17845b269770449d
7
- data.tar.gz: ce70a33c5502a452b3b20db2543e8f1dd02e684f68353173405dfc50f7dbed65c108ca5d96c352df770167b1304d7cdc2ea7c028ba7825031045eb0ce078539c
6
+ metadata.gz: d9855ee10803e4bcc1e451182e7ab650df68a7c5eaf62a273f5b8107a824ec23831f98746399ffddf525465e673ba522579a37f167ae16847cb7c830ea06d129
7
+ data.tar.gz: 4c93d1dd0ebe5c46bcf801e4e88a03da873272a1d70ceff454ae1a39f7d2c0d730777a50470e59ff93546d05e06cde298ca07cee5dcdeb840553719610c45bed
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
+ SuggestExtensions: false
3
4
 
4
5
  Style/FrozenStringLiteralComment:
5
6
  Enabled: true
@@ -38,4 +39,4 @@ Style/AndOr:
38
39
  EnforcedStyle: conditionals
39
40
 
40
41
  Layout/EmptyLineAfterGuardClause:
41
- Enabled: false
42
+ Enabled: false
data/README.md CHANGED
@@ -1,21 +1,28 @@
1
1
  # fluent-plugin-timestream
2
+
2
3
  [![Status](https://circleci.com/gh/StudistCorporation/fluent-plugin-timestream.svg?style=shield)](https://circleci.com/gh/StudistCorporation/fluent-plugin-timestream)
3
4
 
4
5
  Fluentd output plugin for Amazon Timestream.
5
6
 
6
-
7
7
  ## Installation
8
+
8
9
  You can install it as follows:
9
10
 
10
11
  $ fluent-gem install fluent-plugin-timestream
11
12
 
12
13
  ## Configuration
14
+
13
15
  Please refer to the [sample config file](https://github.com/StudistCorporation/fluent-plugin-timestream/blob/main/fluent.conf.sample)
14
16
 
15
17
  ## Note
16
- The plugin converts `null` values in the log to empty string.
17
- e.g. `{key_name: null}` => `{key_name: ""}`
18
-
19
- When writing Timestream records, `TimeUnit` is always set to `SECONDS`
20
-
18
+
19
+ The plugin ignores dimension when it has `null` or empty string value.
20
+ e.g. `{dimension1: null, dimension2: "value", dimension3: ""}` => `{dimension2: "value"}`
21
+
22
+ The plugin ignores record when it has no dimensions.
23
+ e.g. `{dimension1: null, dimension2: "", measure: "value"}` => ignores this record
24
+
25
+ The plugin ignores record when measure specified in the config has `null` or empty value.
26
+ e.g. `{dimension1: "value", measure: ""}` => ignores this record
27
+
21
28
  Configuring multiple `MeasureName`s is not supported.
data/fluent.conf.sample CHANGED
@@ -1,11 +1,35 @@
1
1
  <source>
2
2
  @type tail
3
- format ltsv
3
+ format json
4
4
  path /path/to/sample.log
5
5
  pos_file /tmp/sample.pos
6
6
  tag "sample.log"
7
+ # set keep_time_key true if need to convert time.
8
+ # keep_time_key true
7
9
  </source>
8
10
 
11
+ # If more than seconds accuracy is needed,
12
+ # convert time and configure time_unit and time_key.
13
+ #
14
+ # e.g.)
15
+ # If record is as follows:
16
+ # {"key": "value", "time":1620000000.123456789}
17
+ #
18
+ # convert it to:
19
+ # when time unit is MILLISECONDS: 1620000000123
20
+ # when time unit is MICROSECONDS: 1620000000123456
21
+ # when time unit is NANOSECONDS: 1620000000123456789
22
+ # <filter sample.log>
23
+ # @type record_transformer
24
+ # enable_ruby true
25
+ # <record>
26
+ # milliseconds_time ${(record["time"].to_f * 1000).to_i}
27
+ # </record>
28
+ # Remove time key or plugin sends it as a dimension.
29
+ # Especially, if time key name is 'time', the plugin fails to write record because 'time' is reserved keyword.
30
+ # remove_keys time
31
+ #</filter>
32
+
9
33
  <match sample.log>
10
34
  @type timestream
11
35
 
@@ -24,6 +48,17 @@
24
48
  aws_sec_key "XXXXXXXX"
25
49
  region "us-east-1"
26
50
 
51
+ # If more than seconds accuracy is needed,
52
+ # convert time and configure time_unit and time_key.
53
+ # time_unit: The granularity of the timestamp unit.
54
+ # This value is used for 'TimeUnit' in Timestream record
55
+ # Default value: SECONDS
56
+ # Valid values: SECONDS MILLISECONDS MICROSECONDS NANOSECONDS
57
+ # time_key: Specify record key which contains integer epoch time corresponding to time_unit
58
+ # Plugin uses specified epoch time for 'Time' in Timestream record and does not send it as dimension
59
+ # time_unit "MILLISECONDS"
60
+ # time_key "milliseconds_time"
61
+
27
62
  # Specify which key should be 'measure'.
28
63
  # If not, plugin sends dummy measure and writes all keys and values as dimensions.
29
64
  # Dummy measure is as follows:
@@ -6,8 +6,32 @@ require_relative 'timestream/version'
6
6
 
7
7
  module Fluent
8
8
  module Plugin
9
+ # rubocop: disable Metrics/ClassLength
9
10
  # Fluent plugin for Amazon Timestream
10
11
  class TimestreamOutput < Fluent::Plugin::Output
12
+
13
+ VALID_TIME_UNIT =
14
+ %w[
15
+ SECONDS
16
+ MILLISECONDS
17
+ MICROSECONDS
18
+ NANOSECONDS
19
+ ].freeze
20
+
21
+ # Raise when measure has empty value
22
+ class EmptyValueError < StandardError
23
+ def initialize(key_name = '')
24
+ super("measure has empty value. key name: #{key_name}")
25
+ end
26
+ end
27
+
28
+ # Raise when record has no dimensions
29
+ class NoDimensionsError < StandardError
30
+ def initialize
31
+ super('record has no dimensions.')
32
+ end
33
+ end
34
+
11
35
  Fluent::Plugin.register_output('timestream', self)
12
36
 
13
37
  config_param :region, :string, default: nil
@@ -22,6 +46,8 @@ module Fluent
22
46
  config_param :name, :string
23
47
  config_param :type, :string
24
48
  end
49
+ config_param :time_unit, :string, default: 'SECONDS'
50
+ config_param :time_key, default: nil
25
51
 
26
52
  config_param :endpoint, :string, default: nil
27
53
  config_param :ssl_verify_peer, :bool, default: true
@@ -36,6 +62,7 @@ module Fluent
36
62
 
37
63
  @database = ENV['AWS_TIMESTREAM_DATABASE'] if @database.nil?
38
64
  @table = ENV['AWS_TIMESTREAM_TABLE'] if @table.nil?
65
+ validate_time_unit
39
66
  end
40
67
 
41
68
  def credential_options
@@ -49,6 +76,11 @@ module Fluent
49
76
  end
50
77
  end
51
78
 
79
+ def validate_time_unit
80
+ return if VALID_TIME_UNIT.include?(@time_unit)
81
+ raise Fluent::ConfigError, "Invalid time_unit: #{@time_unit}"
82
+ end
83
+
52
84
  def formatted_to_msgpack_binary
53
85
  true
54
86
  end
@@ -58,60 +90,96 @@ module Fluent
58
90
  end
59
91
 
60
92
  def create_timestream_record(dimensions, time, measure)
93
+ raise NoDimensionsError if dimensions.empty?
61
94
  measure = { name: '-', value: '-', type: 'VARCHAR' } if measure.empty?
62
95
  {
63
96
  dimensions: dimensions,
64
97
  time: time.to_s,
65
- time_unit: 'SECONDS',
98
+ time_unit: @time_unit,
66
99
  measure_name: measure[:name],
67
- measure_value: measure[:value].to_s,
100
+ measure_value: measure[:value],
68
101
  measure_value_type: measure[:type]
69
102
  }
70
103
  end
71
104
 
72
105
  def create_timestream_dimension(key, value)
106
+ value = value.to_s
107
+
108
+ # Timestream does not accept empty string.
109
+ # Ignore this dimension.
110
+ return nil if value.empty?
111
+
73
112
  {
74
113
  dimension_value_type: 'VARCHAR',
75
114
  name: key,
76
- value: value.to_s
115
+ value: value
116
+ }
117
+ end
118
+
119
+ def create_timestream_measure(key, value)
120
+ value = value.to_s
121
+
122
+ # Timestream does not accept empty string.
123
+ # By raising error, ignore entire record.
124
+ raise EmptyValueError, key if value.empty?
125
+
126
+ {
127
+ name: key,
128
+ value: value,
129
+ type: @target_measure[:type]
77
130
  }
78
131
  end
79
132
 
80
133
  def create_timestream_dimensions_and_measure(record)
81
- dimensions = []
82
134
  measure = {}
83
- record.each do |k, v|
135
+ dimensions = record.each_with_object([]) do |(k, v), result|
84
136
  if @target_measure && k == @target_measure[:name]
85
- measure = { name: k, value: v, type: @target_measure[:type] }
137
+ measure = create_timestream_measure(k, v)
86
138
  next
87
139
  end
88
- dimensions.push(create_timestream_dimension(k, v))
140
+ dimension = create_timestream_dimension(k, v)
141
+ result.push(dimension) unless dimension.nil?
89
142
  end
90
143
  return [dimensions, measure]
91
144
  end
92
145
 
146
+ # rubocop:disable Metrics/MethodLength
93
147
  def create_timestream_records(chunk)
94
148
  timestream_records = []
95
149
  chunk.each do |time, record|
150
+ time = record.delete(@time_key) unless @time_key.nil?
96
151
  dimensions, measure = create_timestream_dimensions_and_measure(record)
97
152
  timestream_records.push(create_timestream_record(dimensions, time, measure))
153
+ rescue EmptyValueError, NoDimensionsError => e
154
+ log.warn("ignored record due to (#{e})")
155
+ log.debug("ignored record details: #{record}")
156
+ next
98
157
  end
99
158
 
100
- log.info("read #{timestream_records.length} records from chunk")
101
159
  timestream_records
102
160
  end
161
+ # rubocop:enable Metrics/MethodLength
103
162
 
104
163
  def write(chunk)
164
+ records = create_timestream_records(chunk)
165
+ log.info("read #{records.length} records from chunk")
166
+ write_records(records)
167
+ end
168
+
169
+ def write_records(records)
170
+ return if records.empty?
105
171
  @client.write_records(
106
172
  database_name: @database,
107
173
  table_name: @table,
108
- records: create_timestream_records(chunk)
174
+ records: records
109
175
  )
110
176
  rescue Aws::TimestreamWrite::Errors::RejectedRecordsException => e
111
177
  log.error(e.rejected_records)
112
178
  rescue StandardError => e
113
179
  log.error(e.message)
114
180
  end
181
+
115
182
  end
183
+ # rubocop: enable Metrics/ClassLength
116
184
  end
117
185
  end
@@ -3,7 +3,7 @@
3
3
  module Fluent
4
4
  module Plugin
5
5
  module Timestream
6
- VERSION = '0.1.0'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-timestream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Studist Corporation
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-19 00:00:00.000000000 Z
11
+ date: 2021-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-timestreamwrite