fluent-plugin-oceanbase-logs 0.1.3 → 0.1.5

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: ff677af2c1882b27e86cde4cbfef8ef79039f45eae20e2a7c8eebd4c2fdbd98e
4
- data.tar.gz: 2fbe0571f3c1e1b840ab6714faff44e5a7396cad011c4845fb710964db9a490b
3
+ metadata.gz: ba675afa62db755245b7619bcba7d112fa609197e17a2720d8ec08f5741b176b
4
+ data.tar.gz: 4d19f3d57e7370de6c0be7b4b9711c1631496ce5bd4592ea4f6340b8a8e4343d
5
5
  SHA512:
6
- metadata.gz: 380099690cb075af74c720a6eb147fdb5a1234b428bac1d54e1ea3a36d191f2b47b2a7962ed97ee7f5f2c9f576aad55a4cf025bba39bd8152747fdc6439242bf
7
- data.tar.gz: 36cd5b13ca5fd22159451bc2c15c45c7861ad1122a62d9e95a4184c2e5e0a5f49c08183609e7767125d65330b69adf506060b9c59b228f785b641c56c41add20
6
+ metadata.gz: '07788a07945ed06c2bff1ef4c3519b88bcd9baf9f7ad9227b8ff922c767726da664ed95bc3b3e43290d56c6a5fd3fae64b30faf77b4eeed666927c76fa6c1c74'
7
+ data.tar.gz: e6db9efc176ec88627f9aaf030e598fae9a487c6f09ff82a8fe0d75cbddb1330811e8553ba71f23c1572e3867fe3eb5d5227f76ff1612c5e468107dbebf08662
data/README.md CHANGED
@@ -13,7 +13,7 @@ Every record includes **`ob_log_type`** (`slow_sql` or `top_sql`). With `include
13
13
 
14
14
  | Gem | Fluentd | Ruby |
15
15
  | --- | --- | --- |
16
- | >= 0.1.2 | >= 1.8.0 | >= 2.4 |
16
+ | >= 0.1.5 | >= 1.8.0 | >= 2.4 |
17
17
 
18
18
  For **Grafana Loki** output you additionally need [fluent-plugin-grafana-loki](https://github.com/grafana/fluent-plugin-grafana-loki).
19
19
 
@@ -44,6 +44,42 @@ tenant_id "#{ENV['OCEANBASE_TENANT_ID']}"
44
44
 
45
45
  Optional: `OCEANBASE_ENDPOINT`, `OCEANBASE_FETCH_INTERVAL`, `OCEANBASE_LOOKBACK_SECONDS`, `OCEANBASE_DB_NAME`, `OCEANBASE_SEARCH_KEYWORD`, `OCEANBASE_PROJECT_ID` — see `.env.example` and the Docker table below.
46
46
 
47
+ ### Multiple instances, tenants, and databases
48
+
49
+ Use one or more `<target>` blocks. Each block sets **`instance_id`** and **`tenant_id`** (required). Optional **`db_name`** applies the same `dbName` filter as the top-level parameter, for that scope only.
50
+
51
+ When any `<target>` is present, the plugin **only** uses those scopes; top-level `instance_id` / `tenant_id` / `db_name` are ignored (you may leave them empty). Global options such as `access_key_id`, `log_type`, `search_keyword`, `endpoint`, and `fetch_interval` still apply to every target.
52
+
53
+ Example: two clusters, one tenant each, and a second scope that filters one database on another tenant:
54
+
55
+ ```conf
56
+ <source>
57
+ @type oceanbase_logs
58
+ tag oceanbase.slow_sql
59
+ log_type slow_sql
60
+ access_key_id "#{ENV['OCEANBASE_ACCESS_KEY_ID']}"
61
+ access_key_secret "#{ENV['OCEANBASE_ACCESS_KEY_SECRET']}"
62
+ endpoint "#{ENV['OCEANBASE_ACCESS_KEY_SECRET']}"
63
+ fetch_interval 60
64
+ lookback_seconds 600
65
+ deduplicate true
66
+ include_metadata true
67
+ <target>
68
+ instance_id "OCEANBASE_INSTANCE_1"
69
+ tenant_id "OCEANBASE_TENANT_A"
70
+ </target>
71
+ <target>
72
+ instance_id "OCEANBASE_INSTANCE_2"
73
+ tenant_id "OCEANBASE_TENANT_B"
74
+ </target>
75
+ <storage>
76
+ @type local
77
+ persistent true
78
+ path /var/log/fluentd/slow_sql_seen
79
+ </storage>
80
+ </source>
81
+ ```
82
+
47
83
  ### Example: Slow SQL → JSON file
48
84
 
49
85
  Full sample: [`example/fluentd.conf`](example/fluentd.conf).
@@ -13,6 +13,8 @@ module Fluent::Plugin
13
13
 
14
14
  helpers :thread, :storage
15
15
 
16
+ FetchScope = Struct.new(:instance_id, :tenant_id, :db_name)
17
+
16
18
  DEFAULT_STORAGE_TYPE = 'local'
17
19
 
18
20
  LOG_TYPE_PATHS = {
@@ -28,10 +30,20 @@ module Fluent::Plugin
28
30
  config_param :access_key_secret, :string, secret: true,
29
31
  desc: "OceanBase Cloud AccessKey Secret."
30
32
 
31
- config_param :instance_id, :string,
32
- desc: "OceanBase cluster instance ID."
33
- config_param :tenant_id, :string,
34
- desc: "OceanBase tenant ID."
33
+ config_param :instance_id, :string, default: '',
34
+ desc: "OceanBase cluster instance ID (legacy single scope; ignored when <target> sections are present)."
35
+ config_param :tenant_id, :string, default: '',
36
+ desc: "OceanBase tenant ID (legacy single scope; ignored when <target> sections are present)."
37
+
38
+ config_section :target, param_name: :targets, multi: true, required: false do
39
+ # Defaults allow Fluentd to parse <target> blocks; build_fetch_scopes validates non-empty.
40
+ config_param :instance_id, :string, default: '',
41
+ desc: "Instance ID for this scope."
42
+ config_param :tenant_id, :string, default: '',
43
+ desc: "Tenant ID for this scope."
44
+ config_param :db_name, :string, default: nil,
45
+ desc: "Optional database name filter for this scope."
46
+ end
35
47
  config_param :project_id, :string, default: nil,
36
48
  desc: "OceanBase Cloud project ID (X-Ob-Project-Id header)."
37
49
 
@@ -82,13 +94,14 @@ module Fluent::Plugin
82
94
  end
83
95
  raise Fluent::ConfigError, 'access_key_id is required and cannot be empty' if @access_key_id.empty?
84
96
  raise Fluent::ConfigError, 'access_key_secret is required and cannot be empty' if @access_key_secret.empty?
85
- raise Fluent::ConfigError, 'instance_id is required and cannot be empty (e.g. set OCEANBASE_INSTANCE_ID)' if @instance_id.empty?
86
- raise Fluent::ConfigError, 'tenant_id is required and cannot be empty (e.g. set OCEANBASE_TENANT_ID)' if @tenant_id.empty?
87
97
 
88
98
  %i[@db_name @search_keyword @node_ip @filter_condition @project_id].each do |iv|
89
99
  v = instance_variable_get(iv)
90
100
  instance_variable_set(iv, nil) if v.is_a?(String) && v.strip.empty?
91
101
  end
102
+
103
+ @fetch_scopes = build_fetch_scopes
104
+
92
105
  @api_path_segment = LOG_TYPE_PATHS[@log_type.to_s]
93
106
  if @deduplicate
94
107
  @seen_storage = storage_create(
@@ -112,6 +125,36 @@ module Fluent::Plugin
112
125
 
113
126
  private
114
127
 
128
+ def build_fetch_scopes
129
+ scopes = []
130
+ if @targets && !@targets.empty?
131
+ @targets.each_with_index do |t, idx|
132
+ iid = t.instance_id.to_s.strip
133
+ tid = t.tenant_id.to_s.strip
134
+ raise Fluent::ConfigError, "target[#{idx}]: instance_id is required and cannot be empty" if iid.empty?
135
+ raise Fluent::ConfigError, "target[#{idx}]: tenant_id is required and cannot be empty" if tid.empty?
136
+ db = normalize_optional_db_name(t.db_name)
137
+ scopes << FetchScope.new(iid, tid, db)
138
+ end
139
+ else
140
+ raise Fluent::ConfigError, 'instance_id is required and cannot be empty (e.g. set OCEANBASE_INSTANCE_ID), or define one or more <target> sections' if @instance_id.empty?
141
+ raise Fluent::ConfigError, 'tenant_id is required and cannot be empty (e.g. set OCEANBASE_TENANT_ID), or define one or more <target> sections' if @tenant_id.empty?
142
+ scopes << FetchScope.new(@instance_id, @tenant_id, @db_name)
143
+ end
144
+ scopes
145
+ end
146
+
147
+ def normalize_optional_db_name(value)
148
+ return nil if value.nil?
149
+ s = value.to_s.strip
150
+ s.empty? ? nil : s
151
+ end
152
+
153
+ def dedup_storage_key(scope, dedup_id)
154
+ db = scope.db_name.to_s
155
+ "trace:#{scope.instance_id}:#{scope.tenant_id}:#{db}:#{dedup_id}"
156
+ end
157
+
115
158
  def run
116
159
  until @finished
117
160
  begin
@@ -136,24 +179,26 @@ module Fluent::Plugin
136
179
  now = Time.now.utc
137
180
  start_time = (now - @lookback_seconds).strftime('%Y-%m-%dT%H:%M:%SZ')
138
181
  end_time = now.strftime('%Y-%m-%dT%H:%M:%SZ')
139
- fetch_and_emit_samples(start_time, end_time)
182
+ @fetch_scopes.each do |scope|
183
+ fetch_and_emit_samples(start_time, end_time, scope)
184
+ end
140
185
  end
141
186
 
142
187
  # Fetch list then per-execution samples (one record per trace)
143
- def fetch_and_emit_samples(start_time, end_time)
144
- list_response = call_list_api(start_time, end_time)
188
+ def fetch_and_emit_samples(start_time, end_time, scope)
189
+ list_response = call_list_api(start_time, end_time, scope)
145
190
  return unless list_response
146
191
 
147
192
  sql_records = extract_records(list_response)
148
193
  return if sql_records.nil? || sql_records.empty?
149
194
 
150
195
  sql_ids = sql_records.map { |r| r['sqlId'] }.compact.uniq
151
- log.debug "Found #{sql_ids.size} unique SQL IDs, fetching samples..."
196
+ log.debug "Found #{sql_ids.size} unique SQL IDs for instance=#{scope.instance_id} tenant=#{scope.tenant_id} db=#{scope.db_name.inspect}, fetching samples..."
152
197
 
153
198
  total_emitted = 0
154
199
 
155
200
  sql_ids.each do |sql_id|
156
- samples = fetch_samples_for_sql(sql_id, start_time, end_time)
201
+ samples = fetch_samples_for_sql(sql_id, start_time, end_time, scope)
157
202
  next if samples.nil? || samples.empty?
158
203
 
159
204
  es = Fluent::MultiEventStream.new
@@ -163,13 +208,13 @@ module Fluent::Plugin
163
208
  dedup_id = trace_id || "#{sql_id}_#{sample['requestTime']}"
164
209
 
165
210
  if @deduplicate
166
- dedup_key = :"trace_#{dedup_id}"
211
+ dedup_key = dedup_storage_key(scope, dedup_id)
167
212
  next if @seen_storage.get(dedup_key)
168
213
  @seen_storage.put(dedup_key, Time.now.to_i.to_s)
169
214
  end
170
215
 
171
216
  sample['ob_log_type'] = @log_type.to_s
172
- sample = attach_metadata(sample, start_time, end_time) if @include_metadata
217
+ sample = attach_metadata(sample, start_time, end_time, scope) if @include_metadata
173
218
 
174
219
  event_time = if sample['requestTime']
175
220
  begin
@@ -190,26 +235,26 @@ module Fluent::Plugin
190
235
  end
191
236
  end
192
237
 
193
- log.info "Emitted #{total_emitted} #{@log_type} sample events (#{start_time} ~ #{end_time})" if total_emitted > 0
238
+ log.info "Emitted #{total_emitted} #{@log_type} sample events for instance=#{scope.instance_id} tenant=#{scope.tenant_id} (#{start_time} ~ #{end_time})" if total_emitted > 0
194
239
  end
195
240
 
196
- def fetch_samples_for_sql(sql_id, start_time, end_time)
197
- path = "/api/v2/instances/#{@instance_id}/tenants/#{@tenant_id}/sqls/#{sql_id}/samples"
241
+ def fetch_samples_for_sql(sql_id, start_time, end_time, scope)
242
+ path = "/api/v2/instances/#{scope.instance_id}/tenants/#{scope.tenant_id}/sqls/#{sql_id}/samples"
198
243
  params = {
199
244
  'startTime' => start_time,
200
245
  'endTime' => end_time,
201
246
  }
202
- params['dbName'] = @db_name if @db_name
247
+ params['dbName'] = scope.db_name if scope.db_name
203
248
 
204
249
  response = call_api_raw(path, params)
205
250
  return nil unless response
206
251
  extract_records(response)
207
252
  end
208
253
 
209
- def attach_metadata(record, start_time, end_time)
254
+ def attach_metadata(record, start_time, end_time, scope)
210
255
  record.merge(
211
- 'ob_instance_id' => @instance_id,
212
- 'ob_tenant_id' => @tenant_id,
256
+ 'ob_instance_id' => scope.instance_id,
257
+ 'ob_tenant_id' => scope.tenant_id,
213
258
  'query_start_time' => start_time,
214
259
  'query_end_time' => end_time
215
260
  )
@@ -235,13 +280,13 @@ module Fluent::Plugin
235
280
 
236
281
  # ---- API calls ----
237
282
 
238
- def call_list_api(start_time, end_time)
239
- path = "/api/v2/instances/#{@instance_id}/tenants/#{@tenant_id}/#{@api_path_segment}"
283
+ def call_list_api(start_time, end_time, scope)
284
+ path = "/api/v2/instances/#{scope.instance_id}/tenants/#{scope.tenant_id}/#{@api_path_segment}"
240
285
  params = {
241
286
  'startTime' => start_time,
242
287
  'endTime' => end_time,
243
288
  }
244
- params['dbName'] = @db_name if @db_name
289
+ params['dbName'] = scope.db_name if scope.db_name
245
290
  params['searchKeyWord'] = @search_keyword if @search_keyword
246
291
  params['nodeIp'] = @node_ip if @node_ip
247
292
  params['filterCondition'] = @filter_condition if @filter_condition
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module OceanBase
4
4
  module Logs
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.5"
6
6
  end
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-oceanbase-logs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - OceanBase Integrations