Skip to content

Commit 160fc99

Browse files
authored
Port add operator (#272)
* Add 'Add' operator and the doc for it * Update Changelog * Remove license * Force Update * Update init_common * Update init_common * Update Changelog * Force Ci
1 parent 012b857 commit 160fc99

File tree

5 files changed

+636
-1
lines changed

5 files changed

+636
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
### Fixed
1919
- Fixed TCP Input Operator panic [PR296](https://github.com/observIQ/stanza/pull/296)
2020

21-
## [0.13.20] - 2021-05-06
21+
## [0.13.20] - 2021-05-06
2222

2323
### Added
2424
- Added flatten Operator [PR 286](https://github.com/observIQ/stanza/pull/286)

cmd/stanza/init_common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
_ "github.com/observiq/stanza/operator/builtin/parser/time"
2222
_ "github.com/observiq/stanza/operator/builtin/parser/uri"
2323

24+
_ "github.com/observiq/stanza/operator/builtin/transformer/add"
2425
_ "github.com/observiq/stanza/operator/builtin/transformer/copy"
2526
_ "github.com/observiq/stanza/operator/builtin/transformer/filter"
2627
_ "github.com/observiq/stanza/operator/builtin/transformer/hostmetadata"

docs/operators/add.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
## `add` operator
2+
3+
The `add` operator adds a value to an `entry`'s `record`, `labels`, or `resource`.
4+
5+
### Configuration Fields
6+
7+
| Field | Default | Description |
8+
| --- | --- | --- |
9+
| `id` | `add` | A unique identifier for the operator |
10+
| `output` | Next in pipeline | The connected operator(s) that will receive all outbound entries |
11+
| `field` | required | The [field](/docs/types/field.md) to be added.
12+
| `value` | required | `value` is either a static value or an [expression](https://github.com/open-telemetry/opentelemetry-log-collection/blob/main/docs/types/expression.md). If a value is specified, it will be added to each entry at the field defined by `field`. If an expression is specified, it will be evaluated for each entry and added at the field defined by `field`
13+
| `on_error` | `send` | The behavior of the operator if it encounters an error. See [on_error](/docs/types/on_error.md) |
14+
| `if` | | An [expression](/docs/types/expression.md) that, when set, will be evaluated to determine whether this operator should be used for the given entry. This allows you to do easy conditional parsing without branching logic with routers. |
15+
16+
17+
Example usage:
18+
19+
<hr>
20+
Add a string to the record
21+
22+
```yaml
23+
- type: add
24+
field: key2
25+
value: val2
26+
```
27+
28+
<table>
29+
<tr><td> Input entry </td> <td> Output entry </td></tr>
30+
<tr>
31+
<td>
32+
33+
```json
34+
{
35+
"resource": { },
36+
"labels": { },
37+
"record": {
38+
"key1": "val1",
39+
}
40+
}
41+
```
42+
43+
</td>
44+
<td>
45+
46+
```json
47+
{
48+
"resource": { },
49+
"labels": { },
50+
"record": {
51+
"key1": "val1",
52+
"key2": "val2"
53+
}
54+
}
55+
```
56+
57+
</td>
58+
</tr>
59+
</table>
60+
61+
<hr>
62+
Add a value to the record using an expression
63+
64+
```yaml
65+
- type: add
66+
field: key2
67+
value: EXPR($.key1 + "_suffix")
68+
```
69+
70+
<table>
71+
<tr><td> Input entry </td> <td> Output entry </td></tr>
72+
<tr>
73+
<td>
74+
75+
```json
76+
{
77+
"resource": { },
78+
"labels": { },
79+
"record": {
80+
"key1": "val1",
81+
}
82+
}
83+
```
84+
85+
</td>
86+
<td>
87+
88+
```json
89+
{
90+
"resource": { },
91+
"labels": { },
92+
"record": {
93+
"key1": "val1",
94+
"key2": "val1_suffix"
95+
}
96+
}
97+
```
98+
99+
</td>
100+
</tr>
101+
</table>
102+
103+
<hr>
104+
Add an object to the record
105+
106+
```yaml
107+
- type: add
108+
field: key2
109+
value:
110+
nestedkey: nestedvalue
111+
```
112+
113+
<table>
114+
<tr><td> Input entry </td> <td> Output entry </td></tr>
115+
<tr>
116+
<td>
117+
118+
```json
119+
{
120+
"resource": { },
121+
"labels": { },
122+
"record": {
123+
"key1": "val1",
124+
}
125+
}
126+
```
127+
128+
</td>
129+
<td>
130+
131+
```json
132+
{
133+
"resource": { },
134+
"labels": { },
135+
"record": {
136+
"key1": "val1",
137+
"key2": {
138+
"nestedkey":"nested value"
139+
}
140+
}
141+
}
142+
```
143+
144+
</td>
145+
</tr>
146+
</table>
147+
148+
<hr>
149+
Add a value to labels
150+
151+
```yaml
152+
- type: add
153+
field: $labels.key2
154+
value: val2
155+
```
156+
157+
<table>
158+
<tr><td> Input entry </td> <td> Output entry </td></tr>
159+
<tr>
160+
<td>
161+
162+
```json
163+
{
164+
"resource": { },
165+
"labels": { },
166+
"record": {
167+
"key1": "val1",
168+
}
169+
}
170+
```
171+
172+
</td>
173+
<td>
174+
175+
```json
176+
{
177+
"resource": { },
178+
"labels": {
179+
"key2": "val2"
180+
},
181+
"record": {
182+
"key1": "val1"
183+
}
184+
}
185+
```
186+
187+
</td>
188+
</tr>
189+
</table>
190+
191+
<hr>
192+
Add a value to resource
193+
194+
```yaml
195+
- type: add
196+
field: $resource.key2
197+
value: val2
198+
```
199+
200+
<table>
201+
<tr><td> Input entry </td> <td> Output entry </td></tr>
202+
<tr>
203+
<td>
204+
205+
```json
206+
{
207+
"resource": { },
208+
"labels": { },
209+
"record": {
210+
"key1": "val1",
211+
}
212+
}
213+
```
214+
215+
</td>
216+
<td>
217+
218+
```json
219+
{
220+
"resource": {
221+
"key2": "val2"
222+
},
223+
"labels": { },
224+
"record": {
225+
"key1": "val1"
226+
}
227+
}
228+
```
229+
230+
</td>
231+
</tr>
232+
</table>
233+
234+
Add a value to resource using an expression
235+
236+
```yaml
237+
- type: add
238+
field: $resource.key2
239+
value: EXPR($.key1 + "_suffix")
240+
```
241+
242+
<table>
243+
<tr><td> Input entry </td> <td> Output entry </td></tr>
244+
<tr>
245+
<td>
246+
247+
```json
248+
{
249+
"resource": { },
250+
"labels": { },
251+
"record": {
252+
"key1": "val1",
253+
}
254+
}
255+
```
256+
257+
</td>
258+
<td>
259+
260+
```json
261+
{
262+
"resource": {
263+
"key2": "val_suffix"
264+
},
265+
"labels": { },
266+
"record": {
267+
"key1": "val1",
268+
}
269+
}
270+
```
271+
272+
</td>
273+
</tr>
274+
</table>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package add
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/antonmedv/expr"
9+
"github.com/antonmedv/expr/vm"
10+
11+
"github.com/observiq/stanza/entry"
12+
"github.com/observiq/stanza/operator"
13+
"github.com/observiq/stanza/operator/helper"
14+
)
15+
16+
func init() {
17+
operator.Register("add", func() operator.Builder { return NewAddOperatorConfig("") })
18+
}
19+
20+
// NewAddOperatorConfig creates a new add operator config with default values
21+
func NewAddOperatorConfig(operatorID string) *AddOperatorConfig {
22+
return &AddOperatorConfig{
23+
TransformerConfig: helper.NewTransformerConfig(operatorID, "add"),
24+
}
25+
}
26+
27+
// AddOperatorConfig is the configuration of an add operator
28+
type AddOperatorConfig struct {
29+
helper.TransformerConfig `mapstructure:",squash" yaml:",inline"`
30+
Field entry.Field `mapstructure:"field" json:"field" yaml:"field"`
31+
Value interface{} `mapstructure:"value,omitempty" json:"value,omitempty" yaml:"value,omitempty"`
32+
}
33+
34+
// Build will build an add operator from the supplied configuration
35+
func (c AddOperatorConfig) Build(context operator.BuildContext) ([]operator.Operator, error) {
36+
transformerOperator, err := c.TransformerConfig.Build(context)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
addOperator := &AddOperator{
42+
TransformerOperator: transformerOperator,
43+
Field: c.Field,
44+
}
45+
strVal, ok := c.Value.(string)
46+
if !ok || !isExpr(strVal) {
47+
addOperator.Value = c.Value
48+
return []operator.Operator{addOperator}, nil
49+
}
50+
exprStr := strings.TrimPrefix(strVal, "EXPR(")
51+
exprStr = strings.TrimSuffix(exprStr, ")")
52+
53+
compiled, err := expr.Compile(exprStr, expr.AllowUndefinedVariables())
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to compile expression '%s': %w", c.IfExpr, err)
56+
}
57+
58+
addOperator.program = compiled
59+
return []operator.Operator{addOperator}, nil
60+
}
61+
62+
// AddOperator is an operator that adds a string value or an expression value
63+
type AddOperator struct {
64+
helper.TransformerOperator
65+
66+
Field entry.Field
67+
Value interface{}
68+
program *vm.Program
69+
}
70+
71+
// Process will process an entry with a add transformation.
72+
func (p *AddOperator) Process(ctx context.Context, entry *entry.Entry) error {
73+
return p.ProcessWith(ctx, entry, p.Transform)
74+
}
75+
76+
// Transform will apply the add operations to an entry
77+
func (p *AddOperator) Transform(e *entry.Entry) error {
78+
if p.Value != nil {
79+
return e.Set(p.Field, p.Value)
80+
}
81+
if p.program != nil {
82+
env := helper.GetExprEnv(e)
83+
defer helper.PutExprEnv(env)
84+
85+
result, err := vm.Run(p.program, env)
86+
if err != nil {
87+
return fmt.Errorf("evaluate value_expr: %s", err)
88+
}
89+
return e.Set(p.Field, result)
90+
}
91+
return fmt.Errorf("add: missing required field 'value'")
92+
}
93+
94+
func isExpr(str string) bool {
95+
return strings.HasPrefix(str, "EXPR(") && strings.HasSuffix(str, ")")
96+
}

0 commit comments

Comments
 (0)