confgenerator/otel/ottl/ottl.go (203 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ottl
import (
"fmt"
"slices"
"strings"
)
type Statements []Statement
type Statement string
func statementf(format string, args ...any) Statement {
return Statement(fmt.Sprintf(format, args...))
}
func statementsf(format string, args ...any) Statements {
return Statements{statementf(format, args...)}
}
// Value represents something that has a value in OTTL (either LValue or RValue)
type Value interface {
fmt.Stringer
}
// LValue represents a field (path) that can be written to.
type LValue []string
func (path LValue) String() string {
parts := []string{path[0]}
for _, p := range path[1:] {
parts = append(parts, fmt.Sprintf(`[%q]`, p))
}
return strings.Join(parts, "")
}
// RValue represents an arbitrary expression that can be evaluated to a value.
type RValue string
func (v RValue) String() string {
return string(v)
}
func valuef(format string, args ...any) Value {
return RValue(fmt.Sprintf(format, args...))
}
func StringLiteral(v string) Value {
return valuef(`%q`, v)
}
func IntLiteral(v int) Value {
return valuef(`%d`, v)
}
func False() Value {
return valuef("false")
}
func True() Value {
return valuef("true")
}
func Nil() Value {
return valuef("nil")
}
func (a LValue) Set(b Value) Statements {
return a.SetIf(b, nil)
}
func (a LValue) SetIfNil(b Value) Statements {
return a.SetIf(b, valuef(`%s == nil`, a))
}
func (a LValue) SetIf(b, condition Value) Statements {
var condStr string
if condition != nil {
condStr = fmt.Sprintf(" where %s", condition)
}
var statements Statements
if (slices.Equal(a, LValue{"trace_id.string"})) {
// LogEntry's `trace` field is expected to contain a `projects/$foo/traces/` prefix.
// Remove it if present.
// replace_pattern is a statement, not a value, in OTTL, so we have to mutate a cached copy of the value.
cache := LValue{"cache", "__setif_value"}
statements = statements.Append(
cache.Delete(),
cache.Set(b),
statementsf(
`replace_pattern(%s, %q, %q)`,
cache,
`^projects/([^/]*)/traces/`,
"",
),
)
b = cache
}
statements = statements.Append(
statementsf(`set(%s, %s)%s`, a, b, condStr),
)
if (slices.Equal(a, LValue{"severity_text"})) {
// As a special case for severity_text, we need to zero out severity_number to make sure the text takes effect.
// TODO: Add a unit test for this.
statements = statements.Append(
LValue{"severity_number"}.SetIf(IntLiteral(0), condition),
)
}
return statements
}
func (a LValue) MergeMaps(source Value, strategy string) Statements {
return a.MergeMapsIf(source, strategy, IsNotNil(source))
}
func (a LValue) MergeMapsIf(source Value, strategy string, condition Value) Statements {
var condStr string
if condition != nil {
condStr = fmt.Sprintf(" where %s", condition)
}
return Statements{
statementf(`merge_maps(%s, %s, %q)%s`, a, source, strategy, condStr),
}
}
// IsPresent returns true if the field is recursively present (with any value)
func (a LValue) IsPresent() Value {
var conditions []Value
for i := 1; i <= len(a); i++ {
conditions = append(conditions, IsNotNil(a[:i]))
}
return And(conditions...)
}
func ToString(a Value) Value {
return valuef(`Concat([%s], "")`, a)
}
func ToInt(a Value) Value {
return valuef(`Int(%s)`, a)
}
func ToFloat(a Value) Value {
return valuef(`Double(%s)`, a)
}
func ToTime(a Value, strpformat string) Value {
return valuef(`Time(%s, %q)`, a, strpformat)
}
func ParseJSON(a Value) Value {
return valuef(`ParseJSON(%s)`, a)
}
func ConvertCase(a Value, toCase string) Value {
return valuef(`ConvertCase(%s, %q)`, a, toCase)
}
func IsMatch(target Value, pattern string) Value {
return valuef(`IsMatch(%s, %q)`, target, pattern)
}
func Equals(a, b Value) Value {
return valuef(`%s == %s`, a, b)
}
func Not(a Value) Value {
return valuef(`(not %s)`, a)
}
func And(conditions ...Value) Value {
var out []string
for _, c := range conditions {
out = append(out, c.String())
}
return valuef(`(%s)`, strings.Join(out, " and "))
}
func Or(conditions ...Value) Value {
var out []string
for _, c := range conditions {
out = append(out, c.String())
}
return valuef(`(%s)`, strings.Join(out, " or "))
}
func IsNotNil(a Value) Value {
return valuef(`%s != nil`, a)
}
// ExtractCountMetric creates a new metric based on the count value of a Histogram metric
func ExtractCountMetric(monotonic bool, metricName string) Statements {
monotonicStr := "false"
if monotonic {
monotonicStr = "true"
}
return Statements{
statementf(`extract_count_metric(%s) where name == "%s"`, monotonicStr, metricName),
}
}
func (a LValue) SetToBool(b Value) Statements {
// https://github.com/fluent/fluent-bit/blob/fd402681ad0ca0427395b07bb8a37c7c1c846cca/src/flb_parser.c#L1261
// "true" = true, "false" = false, else error
var out Statements
if a.String() != b.String() {
out = append(out, statementf(`set(%s, %s)`, a, b))
}
out = append(out,
statementf(`set(%s, true) where %s == "true"`, a, b),
statementf(`set(%s, false) where %s == "false"`, a, b),
)
return out
}
// Delete removes a (potentially nested) key from its parent maps, if that key exists.
func (a LValue) Delete() Statements {
parent := a[:len(a)-1]
child := a[len(a)-1]
return Statements{
statementf(`delete_key(%s, %q) where %s`, parent, child, a.IsPresent()),
}
}
// Delete removes a (potentially nested) key from its parent maps, if that key exists.
func (a LValue) DeleteIf(cond Value) Statements {
parent := a[:len(a)-1]
child := a[len(a)-1]
return Statements{
statementf(`delete_key(%s, %q) where %s`, parent, child, And(a.IsPresent(), cond)),
}
}
func (a LValue) KeepKeys(keys ...string) Statements {
var quotedKeys []string
for _, k := range keys {
quotedKeys = append(
quotedKeys,
fmt.Sprintf("%q", k),
)
}
return statementsf(
`keep_keys(%s, [%s])`,
a,
strings.Join(quotedKeys, ", "),
)
}
func NewStatements(a ...Statements) Statements {
return Statements{}.Append(a...)
}
func (a Statements) Append(b ...Statements) Statements {
for _, c := range b {
a = append(a, c...)
}
return a
}