-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to build a custom OTel receiver #106
Comments
Why
|
Created a GitHub repo using the work at https://zmoog.dev/posts/how-to-build-a-custom-open-telemery-collector/ |
The OTel docs contains a detailed guide about how to build a (traces) receiver: https://opentelemetry.io/docs/collector/building/receiver/ Here is my journey. |
Overview
|
I want to focus this note to building and running a working no-op receiver component, leaving all data model design to a dedicated note. |
Set up a Go module for the receiverIn this note, I will scaffold a custom receiver for my solar panel inverter build by ZCS. mkdir -p receiver/zcsreceiver
cd receiver/zcsreceiver
$ go mod init github.com/zmoog/custom-otel-collector/receiver/zcsreceiver
go: creating new go.mod: module github.com/zmoog/custom-otel-collector/receiver/zcsreceiver
cd ../..
go work use receiver/zcsreceiver |
Design the configOpening the collector's receivers:
zcs: # this line represents the ID of your receiver intervalSince the inverter data lives in the ZCS cloud, this receiver will periodically scrape the metrics from the ZCS cloud API. At the moment we probably need at least an receivers:
zcs: # this line represents the ID of your receiver
interval: 1m AuthenticationThe ZCS API requires authentication, so we probably need to also add dedicated options: receivers:
zcs: # this line represents the ID of your receiver
interval: 1m
auth:
client_id: ${env:ZCS_CLIENT_ID}
auth_key: ${env:ZCS_AUTH_KEY} FilterFinally, users may have one or more inverters, so we need to include an option to list the inverter serial numbers thay want to collect data from: receivers:
zcs: # this line represents the ID of your receiver
interval: 1m
auth:
client_id: ${env:ZCS_CLIENT_ID}
auth_key: ${env:ZCS_AUTH_KEY}
thing_ids:
- ${env:ZCS_THING_KEY} |
Design the config (continued)We need to create a touch receiver/zcsreceiver/config.go The // receiver/zcsreceiver/config.go
package zcsreceiver
// Config represents the receiver config settings within the collector's config.yaml
type Config struct {
Interval string `mapstructure:"interval"`
Auth Auth `mapstructure:"auth"`
ThingIds []string `mapstructure:"thing_ids"`
}
type Auth struct {
ClientID string `mapstructure:"client_id"`
AuthKey string `mapstructure:"auth_key"`
} We must also validate the config values by implementing the package zcsreceiver
import "fmt"
// Config represents the receiver config settings within the collector's config.yaml
type Config struct {
Interval string `mapstructure:"interval"`
Auth Auth `mapstructure:"auth"`
ThingIds []string `mapstructure:"thing_ids"`
}
// Auth represents the authentication settings for the ZCS receiver
type Auth struct {
ClientID string `mapstructure:"client_id"`
AuthKey string `mapstructure:"auth_key"`
}
// Validate checks if the configuration is valid
func (cfg *Config) Validate() error {
if cfg.Interval == "" {
return fmt.Errorf("interval is required")
}
if cfg.Auth.ClientID == "" {
return fmt.Errorf("client_id is required")
}
if cfg.Auth.AuthKey == "" {
return fmt.Errorf("auth_key is required")
}
if len(cfg.ThingIds) == 0 {
return fmt.Errorf("thing_ids is required")
}
return nil
} |
Created the PR zmoog/custom-otel-collector#1 for the receiver config. |
FactoryThe factory is responsible to set up the receiver, like defining which signals (logs, metrics, and traces) it is going to support. Add the following code to your factory.go file: package zcsreceiver
import (
"go.opentelemetry.io/collector/receiver"
)
// NewFactory creates a factory for zcs receiver.
func NewFactory() receiver.Factory {
return nil
} Here's the func NewFactory(
cfgType component.Type,
createDefaultConfig component.CreateDefaultConfigFunc,
options ...FactoryOption) Factory
component.TypeTo keep things simple, to provide the var typeStr = component.MustNewType("zcsreceiver") component.CreateDefaultConfigFunc
func createDefaultConfig() component.Config {
return &Config{
Interval: string(defaultInterval),
}
} ...FactoryOption
Given that we want to collect metrics from an inverter, we will enable the zcsreceiver to work with metrics only. The OTel receiver package provides the following function to describe the metrics processing capabilities. func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) FactoryOption The
The
func createMetricsReceiver(ctx context.Context, params receiver.Settings, cfg component.Config, nextConsumer consumer.Metrics) (receiver.Metrics, error) {
return nil, nil
} The Wrapping up the factoryWe now have all the necessary components to instantiate a receiver factory using the package zcsreceiver
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
)
// typeStr is the type string for the zcs receiver.
var typeStr = component.MustNewType("zcsreceiver")
// defaultInterval is the default interval for the zcs receiver.
const defaultInterval = 1 * time.Minute
// createDefaultConfig creates a default config for the zcs receiver.
func createDefaultConfig() component.Config {
return &Config{
Interval: string(defaultInterval),
}
}
// createMetricsReceiver creates a receiver for metrics.
func createMetricsReceiver(
ctx context.Context,
params receiver.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (receiver.Metrics, error) {
// TODO: Implement metrics receiver
return nil, nil
}
// NewFactory creates a factory for zcs receiver.
func NewFactory() receiver.Factory {
return receiver.NewFactory(
typeStr,
createDefaultConfig,
receiver.WithMetrics(createMetricsReceiver, component.StabilityLevelDevelopment),
)
} |
Implementing the receiver componentAll interfaces So our receiver only need to implement the following two methods from component.Component: Start(ctx context.Context, host Host) error
Shutdown(ctx context.Context) error
StartThe collector calls Start(ctx context.Context, host Host) error
context.ContextAbout
Host
Receivers should store a reference to the Host to interact with the collector. For example, you can update the receiver status using the func (fr *functionReceiver) Start(ctx context.Context, host component.Host) error {
// ....
go func() {
defer fr.wg.Done()
if errHTTP := fr.server.Serve(ln); errHTTP != nil && !errors.Is(errHTTP, http.ErrServerClosed) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
}()
return nil
} ShutdownImplementationtouch receiver/zcsreceiver/receiver.go |
Updating the Collector’s initialization process |
Running and debugging the receiver |
I want to learn how to build a custom OpenTelemetry receiver.
The text was updated successfully, but these errors were encountered: