confgenerator/otel/modular.go (76 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 otel provides data structures to represent and generate otel configuration.
package otel
import (
"fmt"
"github.com/mitchellh/mapstructure"
"gopkg.in/yaml.v2"
)
// ReceiverPipeline represents a single OT receiver and zero or more processors that must be chained after that receiver.
type ReceiverPipeline struct {
Receiver Component
Processors []Component
}
// Component represents a single OT component (receiver, processor, exporter, etc.)
type Component struct {
// Type is the string type needed to instantiate the OT component.
Type string
// Config is an object which can be serialized by mapstructure into the configuration for the component.
// This can either be a map[string]interface{} or a Config struct from OT.
Config interface{}
}
func (c Component) name(suffix string) string {
if suffix != "" {
return fmt.Sprintf("%s/%s", c.Type, suffix)
}
return c.Type
}
type ModularConfig struct {
LogLevel string
ReceiverPipelines map[string]ReceiverPipeline
Exporter Component
SelfMetricsPort int
}
func (c ModularConfig) Generate() (string, error) {
receivers := map[string]interface{}{}
processors := map[string]interface{}{}
exporters := map[string]interface{}{}
pipelines := map[string]interface{}{}
service := map[string]map[string]interface{}{
"pipelines": pipelines,
"telemetry": {
"metrics": map[string]interface{}{
"address": fmt.Sprintf("0.0.0.0:%d", c.SelfMetricsPort),
},
},
}
configMap := map[string]interface{}{
"receivers": receivers,
"processors": processors,
"exporters": exporters,
"service": service,
}
for key, receiverPipeline := range c.ReceiverPipelines {
receiverName := receiverPipeline.Receiver.name(key)
var receiverProcessorNames []string
for i, processor := range receiverPipeline.Processors {
name := processor.name(fmt.Sprintf("%s_%d", key, i))
receiverProcessorNames = append(receiverProcessorNames, name)
processors[name] = processor.Config
}
receivers[receiverName] = receiverPipeline.Receiver.Config
// Keep track of all the processors we're adding to the config.
var processorNames []string
processorNames = append(processorNames, receiverProcessorNames...)
exporters["googlemanagedprometheus"] = c.Exporter.Config
pipelines["metrics/"+key] = map[string]interface{}{
"receivers": []string{receiverName},
"processors": processorNames,
"exporters": []string{"googlemanagedprometheus"},
}
}
out, err := configToYaml(configMap)
// TODO: Return []byte
if err != nil {
return "", err
}
return string(out), nil
}
// configToYaml converts a tree of structs into a YAML file.
// To match OT's built-in config parsing, we use mapstructure to convert the tree of structs into a tree of maps.
// This allows the direct use of OT's config types at any level of the hierarchy.
func configToYaml(config interface{}) ([]byte, error) {
outMap := make(map[string]interface{})
if err := mapstructure.Decode(config, &outMap); err != nil {
return nil, err
}
return yaml.Marshal(outMap)
}