fluent-plugin-oceanbase-logs 0.1.3 → 0.1.4
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 +4 -4
- data/README.md +41 -0
- data/lib/fluent/plugin/in_oceanbase_logs.rb +67 -23
- data/lib/fluent/plugin/oceanbase/logs/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b21a8c2fad6676c5f9b5b8f00cd849091b8c4077c745b2fa7a462995f9b7b07
|
|
4
|
+
data.tar.gz: 0f1a70d45968dc28815fb3c507e85b7115cc6b3682f0a5a762a0bdc5467f9019
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02b0aaf81f118afdcc215f377f7c13ca2ca63d2a1cd28008f778b902e9277b614f28464973fbec4d5ce4a7550fbc3eca9a9f43833367379f58a7f967398f1a05
|
|
7
|
+
data.tar.gz: 2dfdd6d0bbe2ac20532ef99e1ef1e764d7d8fa9244043865370e9b1c77378766503ae19680f50266085e39a0ad8af0784ee00eeb19f904e4ee6523de902692f8
|
data/README.md
CHANGED
|
@@ -44,6 +44,47 @@ 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
|
+
instance_id ""
|
|
63
|
+
tenant_id ""
|
|
64
|
+
endpoint api-cloud-cn.oceanbase.com
|
|
65
|
+
fetch_interval 60
|
|
66
|
+
lookback_seconds 600
|
|
67
|
+
deduplicate true
|
|
68
|
+
include_metadata true
|
|
69
|
+
<target>
|
|
70
|
+
instance_id "#{ENV['OCEANBASE_INSTANCE_1']}"
|
|
71
|
+
tenant_id "#{ENV['OCEANBASE_TENANT_A']}"
|
|
72
|
+
</target>
|
|
73
|
+
<target>
|
|
74
|
+
instance_id "#{ENV['OCEANBASE_INSTANCE_2']}"
|
|
75
|
+
tenant_id "#{ENV['OCEANBASE_TENANT_B']}"
|
|
76
|
+
db_name "#{ENV['OCEANBASE_DB_ANALYTICS']}"
|
|
77
|
+
</target>
|
|
78
|
+
<storage>
|
|
79
|
+
@type local
|
|
80
|
+
persistent true
|
|
81
|
+
path /var/log/fluentd/slow_sql_seen
|
|
82
|
+
</storage>
|
|
83
|
+
</source>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The same tenant with multiple databases needs one `<target>` per database (or a target without `db_name` to pull all DBs for that tenant, if your API usage allows omitting `dbName`).
|
|
87
|
+
|
|
47
88
|
### Example: Slow SQL → JSON file
|
|
48
89
|
|
|
49
90
|
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,19 @@ 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
|
+
config_param :instance_id, :string,
|
|
40
|
+
desc: "Instance ID for this scope."
|
|
41
|
+
config_param :tenant_id, :string,
|
|
42
|
+
desc: "Tenant ID for this scope."
|
|
43
|
+
config_param :db_name, :string, default: nil,
|
|
44
|
+
desc: "Optional database name filter for this scope."
|
|
45
|
+
end
|
|
35
46
|
config_param :project_id, :string, default: nil,
|
|
36
47
|
desc: "OceanBase Cloud project ID (X-Ob-Project-Id header)."
|
|
37
48
|
|
|
@@ -82,13 +93,14 @@ module Fluent::Plugin
|
|
|
82
93
|
end
|
|
83
94
|
raise Fluent::ConfigError, 'access_key_id is required and cannot be empty' if @access_key_id.empty?
|
|
84
95
|
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
96
|
|
|
88
97
|
%i[@db_name @search_keyword @node_ip @filter_condition @project_id].each do |iv|
|
|
89
98
|
v = instance_variable_get(iv)
|
|
90
99
|
instance_variable_set(iv, nil) if v.is_a?(String) && v.strip.empty?
|
|
91
100
|
end
|
|
101
|
+
|
|
102
|
+
@fetch_scopes = build_fetch_scopes
|
|
103
|
+
|
|
92
104
|
@api_path_segment = LOG_TYPE_PATHS[@log_type.to_s]
|
|
93
105
|
if @deduplicate
|
|
94
106
|
@seen_storage = storage_create(
|
|
@@ -112,6 +124,36 @@ module Fluent::Plugin
|
|
|
112
124
|
|
|
113
125
|
private
|
|
114
126
|
|
|
127
|
+
def build_fetch_scopes
|
|
128
|
+
scopes = []
|
|
129
|
+
if @targets && !@targets.empty?
|
|
130
|
+
@targets.each_with_index do |t, idx|
|
|
131
|
+
iid = t.instance_id.to_s.strip
|
|
132
|
+
tid = t.tenant_id.to_s.strip
|
|
133
|
+
raise Fluent::ConfigError, "target[#{idx}]: instance_id is required and cannot be empty" if iid.empty?
|
|
134
|
+
raise Fluent::ConfigError, "target[#{idx}]: tenant_id is required and cannot be empty" if tid.empty?
|
|
135
|
+
db = normalize_optional_db_name(t.db_name)
|
|
136
|
+
scopes << FetchScope.new(iid, tid, db)
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
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?
|
|
140
|
+
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?
|
|
141
|
+
scopes << FetchScope.new(@instance_id, @tenant_id, @db_name)
|
|
142
|
+
end
|
|
143
|
+
scopes
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def normalize_optional_db_name(value)
|
|
147
|
+
return nil if value.nil?
|
|
148
|
+
s = value.to_s.strip
|
|
149
|
+
s.empty? ? nil : s
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def dedup_storage_key(scope, dedup_id)
|
|
153
|
+
db = scope.db_name.to_s
|
|
154
|
+
"trace:#{scope.instance_id}:#{scope.tenant_id}:#{db}:#{dedup_id}"
|
|
155
|
+
end
|
|
156
|
+
|
|
115
157
|
def run
|
|
116
158
|
until @finished
|
|
117
159
|
begin
|
|
@@ -136,24 +178,26 @@ module Fluent::Plugin
|
|
|
136
178
|
now = Time.now.utc
|
|
137
179
|
start_time = (now - @lookback_seconds).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
138
180
|
end_time = now.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
139
|
-
|
|
181
|
+
@fetch_scopes.each do |scope|
|
|
182
|
+
fetch_and_emit_samples(start_time, end_time, scope)
|
|
183
|
+
end
|
|
140
184
|
end
|
|
141
185
|
|
|
142
186
|
# 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)
|
|
187
|
+
def fetch_and_emit_samples(start_time, end_time, scope)
|
|
188
|
+
list_response = call_list_api(start_time, end_time, scope)
|
|
145
189
|
return unless list_response
|
|
146
190
|
|
|
147
191
|
sql_records = extract_records(list_response)
|
|
148
192
|
return if sql_records.nil? || sql_records.empty?
|
|
149
193
|
|
|
150
194
|
sql_ids = sql_records.map { |r| r['sqlId'] }.compact.uniq
|
|
151
|
-
log.debug "Found #{sql_ids.size} unique SQL IDs, fetching samples..."
|
|
195
|
+
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
196
|
|
|
153
197
|
total_emitted = 0
|
|
154
198
|
|
|
155
199
|
sql_ids.each do |sql_id|
|
|
156
|
-
samples = fetch_samples_for_sql(sql_id, start_time, end_time)
|
|
200
|
+
samples = fetch_samples_for_sql(sql_id, start_time, end_time, scope)
|
|
157
201
|
next if samples.nil? || samples.empty?
|
|
158
202
|
|
|
159
203
|
es = Fluent::MultiEventStream.new
|
|
@@ -163,13 +207,13 @@ module Fluent::Plugin
|
|
|
163
207
|
dedup_id = trace_id || "#{sql_id}_#{sample['requestTime']}"
|
|
164
208
|
|
|
165
209
|
if @deduplicate
|
|
166
|
-
dedup_key =
|
|
210
|
+
dedup_key = dedup_storage_key(scope, dedup_id)
|
|
167
211
|
next if @seen_storage.get(dedup_key)
|
|
168
212
|
@seen_storage.put(dedup_key, Time.now.to_i.to_s)
|
|
169
213
|
end
|
|
170
214
|
|
|
171
215
|
sample['ob_log_type'] = @log_type.to_s
|
|
172
|
-
sample = attach_metadata(sample, start_time, end_time) if @include_metadata
|
|
216
|
+
sample = attach_metadata(sample, start_time, end_time, scope) if @include_metadata
|
|
173
217
|
|
|
174
218
|
event_time = if sample['requestTime']
|
|
175
219
|
begin
|
|
@@ -190,26 +234,26 @@ module Fluent::Plugin
|
|
|
190
234
|
end
|
|
191
235
|
end
|
|
192
236
|
|
|
193
|
-
log.info "Emitted #{total_emitted} #{@log_type} sample events (#{start_time} ~ #{end_time})" if total_emitted > 0
|
|
237
|
+
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
238
|
end
|
|
195
239
|
|
|
196
|
-
def fetch_samples_for_sql(sql_id, start_time, end_time)
|
|
197
|
-
path = "/api/v2/instances/#{
|
|
240
|
+
def fetch_samples_for_sql(sql_id, start_time, end_time, scope)
|
|
241
|
+
path = "/api/v2/instances/#{scope.instance_id}/tenants/#{scope.tenant_id}/sqls/#{sql_id}/samples"
|
|
198
242
|
params = {
|
|
199
243
|
'startTime' => start_time,
|
|
200
244
|
'endTime' => end_time,
|
|
201
245
|
}
|
|
202
|
-
params['dbName'] =
|
|
246
|
+
params['dbName'] = scope.db_name if scope.db_name
|
|
203
247
|
|
|
204
248
|
response = call_api_raw(path, params)
|
|
205
249
|
return nil unless response
|
|
206
250
|
extract_records(response)
|
|
207
251
|
end
|
|
208
252
|
|
|
209
|
-
def attach_metadata(record, start_time, end_time)
|
|
253
|
+
def attach_metadata(record, start_time, end_time, scope)
|
|
210
254
|
record.merge(
|
|
211
|
-
'ob_instance_id' =>
|
|
212
|
-
'ob_tenant_id' =>
|
|
255
|
+
'ob_instance_id' => scope.instance_id,
|
|
256
|
+
'ob_tenant_id' => scope.tenant_id,
|
|
213
257
|
'query_start_time' => start_time,
|
|
214
258
|
'query_end_time' => end_time
|
|
215
259
|
)
|
|
@@ -235,13 +279,13 @@ module Fluent::Plugin
|
|
|
235
279
|
|
|
236
280
|
# ---- API calls ----
|
|
237
281
|
|
|
238
|
-
def call_list_api(start_time, end_time)
|
|
239
|
-
path = "/api/v2/instances/#{
|
|
282
|
+
def call_list_api(start_time, end_time, scope)
|
|
283
|
+
path = "/api/v2/instances/#{scope.instance_id}/tenants/#{scope.tenant_id}/#{@api_path_segment}"
|
|
240
284
|
params = {
|
|
241
285
|
'startTime' => start_time,
|
|
242
286
|
'endTime' => end_time,
|
|
243
287
|
}
|
|
244
|
-
params['dbName'] =
|
|
288
|
+
params['dbName'] = scope.db_name if scope.db_name
|
|
245
289
|
params['searchKeyWord'] = @search_keyword if @search_keyword
|
|
246
290
|
params['nodeIp'] = @node_ip if @node_ip
|
|
247
291
|
params['filterCondition'] = @filter_condition if @filter_condition
|