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 +4 -4
- data/README.md +37 -1
- data/lib/fluent/plugin/in_oceanbase_logs.rb +68 -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: ba675afa62db755245b7619bcba7d112fa609197e17a2720d8ec08f5741b176b
|
|
4
|
+
data.tar.gz: 4d19f3d57e7370de6c0be7b4b9711c1631496ce5bd4592ea4f6340b8a8e4343d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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/#{
|
|
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'] =
|
|
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' =>
|
|
212
|
-
'ob_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/#{
|
|
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'] =
|
|
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
|