@@ -3,6 +3,7 @@ package observek8sattributesprocessor
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "errors"
6
7
7
8
"go.opentelemetry.io/collector/component"
8
9
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -21,18 +22,21 @@ type K8sEventsProcessor struct {
21
22
actions []K8sEventProcessorAction
22
23
}
23
24
25
+ type transformFn func (plog.LogRecord ) any
26
+
24
27
type K8sEventProcessorAction struct {
25
- Key string
26
- ValueFn func (plog.LogRecord ) any
27
- FilterFn func (K8sEvent ) bool
28
+ Key string
29
+ Transform transformFn
30
+ FilterFn func (K8sEvent ) bool
31
+ ComputesMultipleFacets bool
28
32
}
29
33
30
34
func newK8sEventsProcessor (logger * zap.Logger , cfg component.Config ) * K8sEventsProcessor {
31
35
return & K8sEventsProcessor {
32
36
cfg : cfg ,
33
37
logger : logger ,
34
38
actions : []K8sEventProcessorAction {
35
- PodStatusAction , NodeStatusAction , NodeRolesAction ,
39
+ PodStatusAction , NodeStatusAction , NodeRolesAction , PodContainersCountsAction ,
36
40
},
37
41
}
38
42
}
@@ -79,49 +83,103 @@ func (kep *K8sEventsProcessor) processLogs(_ context.Context, logs plog.Logs) (p
79
83
facetsMap = facets .Map ()
80
84
} else {
81
85
facetsMap = transformMap .PutEmptyMap ("facets" )
86
+ // Make sure we have capacity for at least as many actions as we have defined
87
+ // Actions could generate more than one facet, that's taken care of afterwards.
88
+ facetsMap .EnsureCapacity (len (kep .actions ))
82
89
}
83
90
84
- // This is where the custom processor actually computes the value
85
- value := action .ValueFn (lr )
86
-
87
- // TODO [eg] we probably want to make this more modular. For
88
- // now using FromRaw for complex types is fine, since we
89
- // don't plan to generate arbitrarily complex/nested facets.
90
- // For some time coming we will produce facets that are at
91
- // most slices of simple types of map of simple types,
92
- // nothing beyond that.
93
- switch typed := value .(type ) {
94
- case string :
95
- facetsMap .PutStr (action .Key , typed )
96
- case int64 :
97
- facetsMap .PutInt (action .Key , typed )
98
- case bool :
99
- facetsMap .PutBool (action .Key , typed )
100
- case float64 :
101
- facetsMap .PutDouble (action .Key , typed )
102
- case []string :
103
- // []string can't fallback to using FromRaw([]any), as
104
- // the default implementation of FromRaw is not smart
105
- // enough to understand that the slice contains all
106
- // string, and inserts them as bytes, instead
107
- slc := facetsMap .PutEmptySlice (action .Key )
108
- slc .EnsureCapacity (len (typed ))
109
- for _ , str := range typed {
110
- slc .AppendEmpty ().SetStr (str )
91
+ // This is where the custom processor actually computes the transformed value(s)
92
+ value := action .Transform (lr )
93
+
94
+ // If the action returns multiple facets, it means that:
95
+ // - it must generate a value of type map[string]any
96
+ // - we put each individual element of such map into the facets
97
+ // - we ignore the key associated with the action itself, since each facet has its own key in the map
98
+ if action .ComputesMultipleFacets {
99
+ if facets , ok := value .(map [string ]any ); ok {
100
+ // Make sure we have enough capacity for the new facets
101
+ facetsMap .EnsureCapacity (facetsMap .Len () + len (facets ))
102
+ for key , val := range facets {
103
+ if err := mapPut (facetsMap , key , val ); err != nil {
104
+ kep .logger .Error (err .Error ())
105
+ }
106
+ }
107
+ } else {
108
+ kep .logger .Error ("Action %s computes multiple facets and the returned value is not of type map[string]any" , zap .String ("name" , action .Key ))
111
109
}
112
- case map [ string ] string :
113
- // Same reasoning as []string
114
- mp := facetsMap . PutEmptyMap ( action . Key )
115
- mp . EnsureCapacity ( len ( typed ))
116
- for k , v := range typed {
117
- mp . PutStr ( k , v )
110
+ } else {
111
+ // Otherwise, whatever is generated by the facet is the value of such facet, and the facet's key is the key of the action itself.
112
+ // This is true even if the action generates map[string]any when ComputesMultipleFacets == false!
113
+ // This way we allow computing facets typed map[string]any
114
+ if err := mapPut ( facetsMap , action . Key , value ); err != nil {
115
+ kep . logger . Error ( err . Error () )
118
116
}
119
- default :
120
- kep .logger .Error ("sending the generated facet to Observe in bytes since no custom serialization logic is implemented" , zap .Error (err ))
121
117
}
122
118
}
123
119
}
124
120
}
125
121
}
126
122
return logs , nil
127
123
}
124
+
125
+ func slicePut (theSlice pcommon.Slice , value any ) error {
126
+ elem := theSlice .AppendEmpty ()
127
+ switch typed := value .(type ) {
128
+ case string :
129
+ elem .SetStr (typed )
130
+ case int64 :
131
+ elem .SetInt (typed )
132
+ case bool :
133
+ elem .SetBool (typed )
134
+ case float64 :
135
+ elem .SetDouble (typed )
136
+ // Let's not complicate things and avoid putting maps/slices into slices There's
137
+ // gotta be an easier way to model the processor's output to avoid this
138
+ default :
139
+ return errors .New ("sending the generated facet to Observe in bytes since no custom serialization logic is implemented" )
140
+ }
141
+
142
+ return nil
143
+ }
144
+
145
+ // puts "anything" into a map, with some assumptions and intentional
146
+ // limitations:
147
+ //
148
+ // - No nested slices: can only put "base types" inside slices (although
149
+ // elements of a slice can be of different [base] types).
150
+ //
151
+ // - Not all "base types" are covered. For instance, numbers are only int64 and float64.
152
+ //
153
+ // - No maps with keys of arbitrary types: only string
154
+ func mapPut (theMap pcommon.Map , key string , value any ) error {
155
+ switch typed := value .(type ) {
156
+ case string :
157
+ theMap .PutStr (key , typed )
158
+ case int64 :
159
+ theMap .PutInt (key , typed )
160
+ case bool :
161
+ theMap .PutBool (key , typed )
162
+ case float64 :
163
+ theMap .PutDouble (key , typed )
164
+ case []any :
165
+ slc := theMap .PutEmptySlice (key )
166
+ slc .EnsureCapacity (len (typed ))
167
+ for _ , elem := range typed {
168
+ slicePut (slc , elem )
169
+ }
170
+ case map [string ]any :
171
+ // This is potentially arbitrarily recursive. We don't care about
172
+ // checking the nesting level since we will never need to define
173
+ // processors with more than one nesting level
174
+ new := theMap .PutEmptyMap (key )
175
+ new .EnsureCapacity (len (typed ))
176
+ for k , v := range typed {
177
+ mapPut (new , k , v )
178
+ }
179
+ default :
180
+ return errors .New ("sending the generated facet to Observe in bytes since no custom serialization logic is implemented" )
181
+ }
182
+
183
+ return nil
184
+
185
+ }
0 commit comments