原文來自OpenTelemetry 入門指南︰建立全面可觀測性架構
心血來潮打開 TG,看到有大大在詢問關於 OpenTelemetry Collector (contrib) 的 Filter processor 的使用方法。
第一個問題是關於 Metrics,為什麼官方網站上看到的是 metrics
︰
processors:
# Data sources: metrics
filter:
metrics:
include:
match_type: regexp
metric_names:
- prefix/.*
- prefix_.*
而套件網站寫的是 metrics.metric
︰
processors:
filter:
metrics:
metric:
- 'name == "my.metric" and resource.attributes["my_label"] == "abc123"'
- 'type == METRIC_DATA_TYPE_HISTOGRAM'
datapoint:
- 'metric.type == METRIC_DATA_TYPE_SUMMARY'
- 'resource.attributes["service.name"] == "my_service_name"'
第二個問題是關於 Span,一樣官方網站看到的是 trace.span
︰
processors:
filter/ottl:
error_mode: ignore
traces:
span:
- |
resource.attributes["service.name"] != "app1" and
resource.attributes["service.name"] != "app2" and
resource.attributes["service.name"] != "app3"
看對岸的部落格寫的則是 spans
processors:
filter/allowlist: # 保留滿足條件的 span
spans:
include:
match_type: strict
services:
- app1
- app2
- app3
問題是什麼樣的寫法是最正確、齊全的用法。
結論是,全部都是正確的寫法!
如果只是要使用正則表達式來查詢metric name
欄位且為模糊的範圍或該欄位有多種值,查詢會如下。
關於match_type
,在使用正則表達式需要使用match_type: regexp
,而如果該欄位的值要正確比對只是有多種值得可能則match_type: strict
。
又如果是使用內建的 Filter Metric Label 表達式的話,則是使用match_type: expr
。如HasLabel
、Label
。
filter/regexp:
metrics:
include:
match_type: regexp
metric_names: [prefix/.*, .*contains.*, .*_suffix, full_name_match]
filter/regexpincludeoptions:
metrics:
include:
match_type: regexp
metric_names:
- prefix/.*
- prefix_.*
- .*contains.*
- .*_suffix
filter/regexpexcludeoptions:
metrics:
exclude:
match_type: regex
metric_names:
- hello/.*
filter/includestrict:
metrics:
include:
match_type: strict
metric_names:
- hello_world
- hello/world
filter/exprinclude:
metrics:
include:
match_type: expr
expressions:
- Label("foo") == "bar"
- HasLabel("baz")
filter/strict:
spans:
include:
match_type: strict
services:
- app1
- app2
- app3
attributes:
- key: should_include
value: "(true|probably_true)"
但若是要對多個欄位或做更為複雜的查詢操作時會使用到 OTTL ,則會如下
在 Yaml 的表達上就會不同了。且不能正則表達式的比對方法與 OTTL 比對方法同時存在於同一個filter中(cannot use ottl conditions and include/exclude for metrics at the same time
),原因文章後面會提及。
filter/ottldrop:
error_mode: ignore
metrics:
metric:
- type == METRIC_DATA_TYPE_NONE
traces:
span:
- IsMatch(resource.attributes["k8s.pod.name"], "local")
filter/ottlmetricstraces:
metrics:
metric: # 針對 HISTOGRAM 類型的資料,name 和 resource.attributes["my_label"] 滿足條件的就過濾
- 'name == "my.metric" and resource.attributes["my_label"] == "abc123"'
- 'type == METRIC_DATA_TYPE_HISTOGRAM'
datapoint: # 針對 datapoint 做過濾,但只針對 metric.type 是 SUMMARY 類型的資料
- 'metric.type == METRIC_DATA_TYPE_SUMMARY'
traces:
span: # 針對 Span
- 'attributes["container.name"] == "app_container_1"'
- 'resource.attributes["host.name"] == "localhost"'
- 'name == "app_3"'
spanevent: # 針對 Span Event
- 'attributes["grpc"] == true'
- 'IsMatch(name, ".*grpc.*")'
多種混用就要命名多個filter,反正流水線執行順序是依照在各類型遙測資料流水線中安排的順序。記得軟體設計的原則,單一職責的概念,每一個 Filter 盡量只處理單一欄位或比對或單一需求所需的最基本處理。組合排序的部份流給 pipeline。
filter/metrics_mix_config:
metrics:
include:
match_type: expr
expressions:
- Label("foo") == "bar"
- HasLabel("baz")
filter/ottlmetricname:
metric:
- 'attributes["test"] == "pass"'
filter/spans_mix_config:
spans:
include:
match_type: strict
services:
- test
- test2
attributes:
- key: should_include
value: "(true|probably_true)"
traces:
span:
- 'attributes["test"] == "pass"'
關於 OTTL 常見語法
- Paths
- Lists
- Literals
- Enums
- Converters
- Math expression
關於 OTTL 常見函數
- append
- delete
- keep
- flatten
- limit
- merge_maps
- replace
- set
- truncate
先針對config.go
擷取部份定義。
設定檔中對應的就是 Config
,有5種成員屬性,error_mode
、metrics
、logs
、spans
和 traces
。
// Config defines configuration for Resource processor.
type Config struct {
// ErrorMode determines how the processor reacts to errors that occur while processing an OTTL condition.
// Valid values are `ignore` and `propagate`.
// `ignore` means the processor ignores errors returned by conditions and continues on to the next condition. This is the recommended mode.
// `propagate` means the processor returns the error up the pipeline. This will result in the payload being dropped from the collector.
// The default value is `propagate`.
ErrorMode ottl.ErrorMode `mapstructure:"error_mode"`
Metrics MetricFilters `mapstructure:"metrics"`
Logs LogFilters `mapstructure:"logs"`
Spans filterconfig.MatchConfig `mapstructure:"spans"`
Traces TraceFilters `mapstructure:"traces"`
}
接著是關於 metrics 區塊中的設定元素。
對應元素有 include
、exclude
、regexp
、metric
與 datapoint
。
// MetricFilters filters by Metric properties.
type MetricFilters struct {
// Include match properties describe metrics that should be included in the Collector Service pipeline,
// all other metrics should be dropped from further processing.
// If both Include and Exclude are specified, Include filtering occurs first.
Include *filterconfig.MetricMatchProperties `mapstructure:"include"`
// Exclude match properties describe metrics that should be excluded from the Collector Service pipeline,
// all other metrics should be included.
// If both Include and Exclude are specified, Include filtering occurs first.
Exclude *filterconfig.MetricMatchProperties `mapstructure:"exclude"`
// RegexpConfig specifies options for the regexp match type
RegexpConfig *regexp.Config `mapstructure:"regexp"`
// MetricConditions is a list of OTTL conditions for an ottlmetric context.
// If any condition resolves to true, the metric will be dropped.
// Supports `and`, `or`, and `()`
MetricConditions []string `mapstructure:"metric"`
// DataPointConditions is a list of OTTL conditions for an ottldatapoint context.
// If any condition resolves to true, the datapoint will be dropped.
// Supports `and`, `or`, and `()`
DataPointConditions []string `mapstructure:"datapoint"`
}
接著是關於 span 區塊的設定元素。
對應元素有 include
、exclude
。
// MatchConfig has two optional MatchProperties one to define what is processed
// by the processor, captured under the 'include' and the second, exclude, to
// define what is excluded from the processor.
type MatchConfig struct {
// Include specifies the set of input data properties that must be present in order
// for this processor to apply to it.
// Note: If `exclude` is specified, the input data is compared against those
// properties after the `include` properties.
// This is an optional field. If neither `include` and `exclude` are set, all input data
// are processed. If `include` is set and `exclude` isn't set, then all
// input data matching the properties in this structure are processed.
Include *MatchProperties `mapstructure:"include"`
// Exclude specifies when this processor will not be applied to the input data
// which match the specified properties.
// Note: The `exclude` properties are checked after the `include` properties,
// if they exist, are checked.
// If `include` isn't specified, the `exclude` properties are checked against
// all input data.
// This is an optional field. If neither `include` and `exclude` are set, all input data
// is processed. If `exclude` is set and `include` isn't set, then all the
// input data that does not match the properties in this structure are processed.
Exclude *MatchProperties `mapstructure:"exclude"`
}
最後是關於 trace 區塊的設定元素。
對應元素有 span
、spanevent
。
// TraceFilters filters by OTTL conditions
type TraceFilters struct {
// SpanConditions is a list of OTTL conditions for an ottlspan context.
// If any condition resolves to true, the span will be dropped.
// Supports `and`, `or`, and `()`
SpanConditions []string `mapstructure:"span"`
// SpanEventConditions is a list of OTTL conditions for an ottlspanevent context.
// If any condition resolves to true, the span event will be dropped.
// Supports `and`, `or`, and `()`
SpanEventConditions []string `mapstructure:"spanevent"`
}
其中 metrics 算是比較複雜的。
type MetricFilters struct {
Include *filterconfig.MetricMatchProperties `mapstructure:"include"`
Exclude *filterconfig.MetricMatchProperties `mapstructure:"exclude"`
RegexpConfig *regexp.Config `mapstructure:"regexp"`
MetricConditions []string `mapstructure:"metric"`
DataPointConditions []string `mapstructure:"datapoint"`
}
and
、or
和括號 ()
。and
、or
和括號 ()
。type MetricMatchProperties struct {
MatchType MetricMatchType `mapstructure:"match_type"`
RegexpConfig *regexp.Config `mapstructure:"regexp"`
MetricNames []string `mapstructure:"metric_names"`
Expressions []string `mapstructure:"expressions"`
ResourceAttributes []Attribute `mapstructure:"resource_attributes"`
}
type MetricMatchType string
// These are the MetricMatchType that users can specify for filtering
// `pmetric.Metric`s.
const (
MetricRegexp = MetricMatchType(filterset.Regexp)
MetricStrict = MetricMatchType(filterset.Strict)
MetricExpr = "expr"
)
上面的設定配置,被轉成 MetricMatchProperties 後呢就會被載入到 filter processor 的流水線中。
// fsp : filterMetricProcessor
fsp.skipMetricExpr, err = filtermetric.NewSkipExpr(cfg.Metrics.Include, cfg.Metrics.Exclude)
if err != nil {
return nil, err
}
然後在 NewSkipExpr 建構式中會看到以下程式碼。會看到是先把 include 加到 matchers 再來才是添加 exclude。所以上述才會說先進行 include 過濾才進行 exclude。
// NewSkipExpr creates a BoolExpr that on evaluation returns true if a metric should NOT be processed or kept.
// The logic determining if a metric should be processed is based on include and exclude settings.
// Include properties are checked before exclude settings are checked.
func NewSkipExpr(include *filterconfig.MetricMatchProperties, exclude *filterconfig.MetricMatchProperties) (expr.BoolExpr[ottlmetric.TransformContext], error) {
if UseOTTLBridge.IsEnabled() {
return filterottl.NewMetricSkipExprBridge(include, exclude)
}
var matchers []expr.BoolExpr[ottlmetric.TransformContext]
inclExpr, err := newExpr(include)
if err != nil {
return nil, err
}
if inclExpr != nil {
matchers = append(matchers, expr.Not(inclExpr))
}
exclExpr, err := newExpr(exclude)
if err != nil {
return nil, err
}
if exclExpr != nil {
matchers = append(matchers, exclExpr)
}
return expr.Or(matchers...), nil
}
// NewMatcher constructs a metric Matcher. If an 'expr' match type is specified,
// returns an expr matcher, otherwise a name matcher.
func newExpr(mp *filterconfig.MetricMatchProperties) (expr.BoolExpr[ottlmetric.TransformContext], error) {
if mp == nil {
return nil, nil
}
// 這裡就是 match_type: expr
if mp.MatchType == filterconfig.MetricExpr {
if len(mp.Expressions) == 0 {
return nil, nil
}
return newExprMatcher(mp.Expressions)
}
if len(mp.MetricNames) == 0 {
return nil, nil
}
return newNameMatcher(mp)
}
最後 match_type: regexp
的會返回 nameMatcher 類型的matcher。
// nameMatcher matches metrics by metric properties against prespecified values for each property.
type nameMatcher struct {
// 儲存和處理metric名稱的過濾器集合
nameFilters filterset.FilterSet
}
func newNameMatcher(mp *filterconfig.MetricMatchProperties) (*nameMatcher, error) {
nameFS, err := filterset.CreateFilterSet(
mp.MetricNames,
&filterset.Config{
MatchType: filterset.MatchType(mp.MatchType), // 精確匹配或正則表達式匹配
RegexpConfig: mp.RegexpConfig, // 正則表達式
},
)
if err != nil {
return nil, err
}
return &nameMatcher{nameFilters: nameFS}, nil
}
// Eval matches a metric using the metric properties configured on the nameMatcher.
// A metric only matches if every metric property configured on the nameMatcher is a match.
func (m *nameMatcher) Eval(_ context.Context, tCtx ottlmetric.TransformContext) (bool, error) {
// 從上下文中獲取 metric 的名稱來進行比對
return m.nameFilters.Matches(tCtx.GetMetric().Name()), nil
}
然後上述的都會轉成 expr.BoolExpr
的形式,這說明這比對只會回傳(bool, error),且這比對函數稱為 Eval
。
// BoolExpr is an interface that allows matching a context K against a configuration of a match.
type BoolExpr[K any] interface {
Eval(ctx context.Context, tCtx K) (bool, error)
}
最後在執行處理 metric 的主要流程, 會在 ScopeMetrics 區段判斷是不是要對該筆資料 skip。
// processMetrics filters the given metrics based off the filterMetricProcessor's filters.
func (fmp *filterMetricProcessor) processMetrics(ctx context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
if fmp.skipResourceExpr == nil && fmp.skipMetricExpr == nil && fmp.skipDataPointExpr == nil {
return md, nil
}
...
var errors error
md.ResourceMetrics().RemoveIf(func(rmetrics pmetric.ResourceMetrics) bool {
...
rmetrics.ScopeMetrics().RemoveIf(func(smetrics pmetric.ScopeMetrics) bool {
scope := smetrics.Scope()
smetrics.Metrics().RemoveIf(func(metric pmetric.Metric) bool {
if fmp.skipMetricExpr != nil {
skip, err := fmp.skipMetricExpr.Eval(ctx, ottlmetric.NewTransformContext(metric, smetrics.Metrics(), scope, resource, smetrics, rmetrics))
if err != nil {
errors = multierr.Append(errors, err)
}
if skip {
return true
}
}
...
})
return smetrics.Metrics().Len() == 0
})
return rmetrics.ScopeMetrics().Len() == 0
})
...
return md, nil
}
type Config struct {
CacheEnabled bool `mapstructure:"cacheenabled"`
CacheMaxNumEntries int `mapstructure:"cachemaxnumentries"`
}
到這裡我們應該知道 metrics 中的 include 與 exclude 比對的對象就是 metric_name 以及 resource 中的 attributes,此處大同小異就不重複說明。
regexp 中的 cache 設定是能用很少的記憶體空間,暫存過往結果來加速比對。
接著來看看 metric
與 datapoint
。
當設定配置被載入到 filter processor,轉成對應類型的物件後,會呼叫func (cfg *Config) Validate()
檢查設定配置是否正確。
會看到一句錯誤標語cannot use ottl conditions and include/exclude for metrics at the same time
;我們不能把 include/exclude 與 OTTL 條件式放在同一個 filter 物件中。所以我們往後看,它這段也只會檢查 OTTL 的文法配置。也呼應最早提到不能正則表達式的比對方法與 OTTL 比對方法同時存在於同一個filter中(cannot use ottl conditions and include/exclude for metrics at the same time)
// Validate checks if the processor configuration is valid
func (cfg *Config) Validate() error {
...
if (cfg.Metrics.MetricConditions != nil || cfg.Metrics.DataPointConditions != nil) && (cfg.Metrics.Include != nil || cfg.Metrics.Exclude != nil) {
return fmt.Errorf("cannot use ottl conditions and include/exclude for metrics at the same time")
}
...
var errors error
...
if cfg.Metrics.MetricConditions != nil {
_, err := filterottl.NewBoolExprForMetric(cfg.Metrics.MetricConditions, filterottl.StandardMetricFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
errors = multierr.Append(errors, err)
}
if cfg.Metrics.DataPointConditions != nil {
_, err := filterottl.NewBoolExprForDataPoint(cfg.Metrics.DataPointConditions, filterottl.StandardDataPointFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
errors = multierr.Append(errors, err)
}
...
return errors
}
再次來看到 filterMetricProcessor 的建構式。能發現這裡與正則表達式用的newSkipResExpr
不同了。分別是用filterottl.NewBoolExprForMetric
與filterottl.NewBoolExprForDataPoint
。
if cfg.Metrics.MetricConditions != nil || cfg.Metrics.DataPointConditions != nil {
if cfg.Metrics.MetricConditions != nil {
fsp.skipMetricExpr, err = filterottl.NewBoolExprForMetric(cfg.Metrics.MetricConditions, filterottl.StandardMetricFuncs(), cfg.ErrorMode, set.TelemetrySettings)
if err != nil {
return nil, err
}
}
if cfg.Metrics.DataPointConditions != nil {
fsp.skipDataPointExpr, err = filterottl.NewBoolExprForDataPoint(cfg.Metrics.DataPointConditions, filterottl.StandardDataPointFuncs(), cfg.ErrorMode, set.TelemetrySettings)
if err != nil {
return nil, err
}
}
return fsp, nil
}
NewBoolExprForMetric 與 NewBoolExprForDataPoint 這裡就不深入解釋了。反正就是回傳能按照表達式順序依序去執行擷取、轉換、或比對等操作的 OTTL 表達式。
回傳的也是 BoolExpr
跳到上述的 boolExpr。
// NewBoolExprForMetric creates a BoolExpr[ottlmetric.TransformContext] that will return true if any of the given OTTL conditions evaluate to true.
// The passed in functions should use the ottlmetric.TransformContext.
// If a function named `match` is not present in the function map it will be added automatically so that parsing works as expected
func NewBoolExprForMetric(conditions []string, functions map[string]ottl.Factory[ottlmetric.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlmetric.TransformContext], error) {
parser, err := ottlmetric.NewParser(functions, set)
if err != nil {
return nil, err
}
statements, err := parser.ParseConditions(conditions)
if err != nil {
return nil, err
}
c := ottlmetric.NewConditionSequence(statements, set, ottlmetric.WithConditionSequenceErrorMode(errorMode))
return &c, nil
}
// NewBoolExprForDataPoint creates a BoolExpr[ottldatapoint.TransformContext] that will return true if any of the given OTTL conditions evaluate to true.
// The passed in functions should use the ottldatapoint.TransformContext.
// If a function named `match` is not present in the function map it will be added automatically so that parsing works as expected
func NewBoolExprForDataPoint(conditions []string, functions map[string]ottl.Factory[ottldatapoint.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottldatapoint.TransformContext], error) {
parser, err := ottldatapoint.NewParser(functions, set)
if err != nil {
return nil, err
}
statements, err := parser.ParseConditions(conditions)
if err != nil {
return nil, err
}
c := ottldatapoint.NewConditionSequence(statements, set, ottldatapoint.WithConditionSequenceErrorMode(errorMode))
return &c, nil
}
首先我們要知道 metrics data 到底有哪些屬性。 參考自 metrics.proto。
// MetricsData
// └─── ResourceMetrics
// ├── Resource
// ├── SchemaURL
// └── ScopeMetrics
// ├── Scope
// ├── SchemaURL
// └── Metric
// ├── Name
// ├── Description
// ├── Unit
// └── data
// ├── Gauge
// ├── Sum
// ├── Histogram
// ├── ExponentialHistogram
// └── Summary
這裡看到的 在 Metric 以下的都是 metric 的範疇。這裡每種 type 裡面的具體監測數值或是 bucket data point 才是屬於 Data point。
像這裡都是 OTTL metric 能處理的屬性。最常見的就是 type 也就是上面的 Gauge、Sum、Histogram 等,還有 name 這些。
而 OTTL DataPoint 就不會看到 type 與 name 這種,而是各種 Metric point 內有的數值。有關於 Metric Point 能參考書中的 Ch 5-31 檢測點的說明
舉個例子︰
filter/ottlmetricname:
metrics:
metric:
- 'name == "pass"'
filter/ottldatapoint:
metrics:
datapoint:
- 'attributes["test"] == "pass"'
- 'metric.name == "http_client_duration_bucket" and resource.attributes["service.name"] == "notification-sender"'
- 'start_time_unix_nano < 1720881125000000000'
閱讀至此,什麼時候該用 regexp 什麼時候該用 OTTL 來比對 metrics 資料應該有了基本的認知了。
用來建立 traces 區塊的是 TraceFilters。
// TraceFilters filters by OTTL conditions
type TraceFilters struct {
// SpanConditions is a list of OTTL conditions for an ottlspan context.
// If any condition resolves to true, the span will be dropped.
// Supports `and`, `or`, and `()`
SpanConditions []string `mapstructure:"span"`
// SpanEventConditions is a list of OTTL conditions for an ottlspanevent context.
// If any condition resolves to true, the span event will be dropped.
// Supports `and`, `or`, and `()`
SpanEventConditions []string `mapstructure:"spanevent"`
}
而 Spans Matcher 則是 filterconfig.MatchConfig
。從這組結構我們也能看出與上述的 metric MetricMatchProperties雷同。
// MatchConfig has two optional MatchProperties one to define what is processed
// by the processor, captured under the 'include' and the second, exclude, to
// define what is excluded from the processor.
type MatchConfig struct {
Include *MatchProperties `mapstructure:"include"`
Exclude *MatchProperties `mapstructure:"exclude"`
}
// MatchProperties specifies the set of properties in a spans/log/metric to match
// against and if the input data should be included or excluded from the
// processor. At least one of services (spans only), names or
// attributes must be specified. It is supported to have all specified, but
// this requires all the properties to match for the inclusion/exclusion to
// occur.
// The following are examples of invalid configurations:
//
// attributes/bad1:
// # This is invalid because include is specified with neither services or
// # attributes.
// include:
// actions: ...
//
// span/bad2:
// exclude:
// # This is invalid because services, span_names and attributes have empty values.
// services:
// span_names:
// attributes:
// actions: ...
//
// Please refer to processor/attributesprocessor/testdata/config.yaml and
// processor/spanprocessor/testdata/config.yaml for valid configurations.
type MatchProperties struct {
Services []string `mapstructure:"services"`
SpanNames []string `mapstructure:"span_names"`
LogBodies []string `mapstructure:"log_bodies"`
LogSeverityTexts []string `mapstructure:"log_severity_texts"`
LogSeverityNumber *LogSeverityNumberMatchProperties `mapstructure:"log_severity_number"`
MetricNames []string `mapstructure:"metric_names"`
Attributes []Attribute `mapstructure:"attributes"`
Resources []Attribute `mapstructure:"resources"`
Libraries []InstrumentationLibrary `mapstructure:"libraries"`
SpanKinds []string `mapstructure:"span_kinds"`
}
能參考書中的 Ch5-62 Trace Span 與 Span context 的關係圖。
Spans Matcher 只能比對關係圖中的 Span 上的屬性,幾乎就是針對名稱或 Kind 或是特定的 trace/span id 做比對。而 Trace Matcher 則能比對到下一層的 Span Event 的內容。
舉個例子︰
filter/spans:
spans:
include:
match_type: strict
services:
- test
- test2
attributes:
- key: should_include
value: "(true|probably_true)"
exclude:
match_type: regexp
attributes:
- key: should_exclude
value: "(probably_false|false)"
filter/ottl:
error_mode: ignore
traces:
span:
- 'attributes["test"] == "pass"'
spanevent:
- 'attributes["test"] == "pass"'
filter/ottlmultiline:
traces:
span:
- 'attributes["test"] == "pass"'
- 'attributes["test"] == "also pass"'
官網有提供一個易懂的 Span 資料,能讓我們清楚的知道 Span context 與 Span event 的內容。
Span Event 是我們開發者用於紀錄 Span 執行過程中有意義的事件(可能就是業務用,可能是 debug 用)。能參考書中的 Ch5-54 Events 的說明。
{
"name": "hello-greetings",
"context": {
"trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "0x5fb397be34d26b51"
},
"parent_id": "0x051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114304Z",
"end_time": "2022-04-29T22:52:58.114561Z",
"attributes": {
"http.route": "some_route2"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
},
{
"name": "bye now!",
"timestamp": "2022-04-29T18:52:58.114585Z",
"attributes": {
"event_attributes": 1
}
}
]
}
由於 Filter Processor 與 Attribute Processor 會大量運用到 OTTL 以及正則表達式。但 OTTL 目前還沒到正式釋出 Stable 版本的程度。所以蠻多時候要自行解讀程式碼以及測試案例來理解怎使用。搭配書上的結構關係圖會更好理解。