There are tens if not hundreds of different observability destinations. Odigos goal is to provide a seamless and easy way to ship observability data to any one of them.

In this guide, you will learn how to contribute a new destination to Odigos. We will create a new dummy destination called mydest. Creating a new destination involves two steps:

  1. Extending the UI for the new destination
  2. Adding the collector configuration for the new destination

User Interface

For our new destination to be visible in the UI, we need to make several changes to the UI code:

  1. Go to destinations/logos/ directory and add your logo file, for example mydest.svg. Please use svg format for the logo.
  2. Go to destinations/data/ directory and create a new file called mydest.yaml by duplicating one of the others. That file makes setup page for set destination available and is quite self-explanatory to use:
mydest.yaml
apiVersion: internal.odigos.io/v1beta1
kind: Destination
metadata:
  type: mydest
  displayName: My Destination
  category: managed
spec:
  image: mydest.svg
  signals:
    traces:
      supported: true
    metrics:
      supported: true
    logs:
      supported: false
  fields:
    - name: MYDEST_URL
      displayName: URL
      componentType: input
      componentProps:
        required: true
    - name: MYDEST_REGION
      displayName: Region
      componentType: input
      componentProps:
        required: true
    - name: MYDEST_API_KEY
      displayName: API Key
      componentType: input
      componentProps:
        type: password
        required: true
      secret: true

Collector Configuration

Now that our new vendor can be persisted/loaded in the Kubernetes data store, we need to implement the collector configuration.

  1. Go to common/dests.go and add your new destination to the DestinationType enum. Make sure the value is the same as the type property of the destination UI class - mydest in the current case.
  2. Go to autoscaler/controllers/gateway/config directory and create a new file called mydest.go with the following content:
mydest.go
package config

import (
  "errors"
  "fmt"

  "github.com/odigos-io/odigos/common"
)

const (
  regionKey = "MYDEST_REGION"
  urlKey    = "MYDEST_URL"
  apiKeyKey = "MYDEST_API_KEY"
)

type MyDest struct{}

func (m *MyDest) DestType() common.DestinationType {
  // DestinationType defined in common/dests.go
  return common.MyDestDestinationType
}

func (m *MyDest) ModifyConfig(dest ExporterConfigurer, currentConfig *Config) error {
  config := dest.GetConfig()
  apiKey, exists := config[apiKeyKey]
  if !exists {
    return errors.New("My Destination API key(\"MYDEST_API_KEY\") not specified, MyDest will not be configured")
  }

  region, exists := config[regionKey]
  if !exists {
    region = "us"
  }

  endpoint, exists := config[urlKey]
  if !exists {
    endpoint = fmt.Sprintf("https://%s.mydest.com:4317", region)
  }

  // to make sure that the exporter name is unique, we'll ask a ID from destination
  exporterName := "otlp/mydest-" + dest.GetID()
  currentConfig.Exporters["otlp/mydest"] = GenericMap{
    "endpoint": endpoint,
    "headers": GenericMap{
      "x-mydest-header-apikey": apiKey,
    },
  }

  // Modify the config here
  if isTracingEnabled(dest) {
    tracesPipelineName := "traces/mydest-" + dest.GetID()
    currentConfig.Service.Pipelines[tracesPipelineName] = Pipeline{
      Exporters: []string{exporterName},
    }
  }

  if isMetricsEnabled(dest) {
    metricsPipelineName := "metrics/mydest-" + dest.GetID()
    currentConfig.Service.Pipelines[metricsPipelineName] = Pipeline{
      Exporters: []string{exporterName},
    }
  }

  return nil
}
  • The method DestType returns the enum value of the destination added earlier.
  • The method ModifyConfig is called with the dest object which holds the data received from the UI and the currentConfig object. The currentConfig object contains the current configuration of the gateway collector. Modify this object to include the OpenTelemetry configuration needed by your destination. Make sure to give any exporter or pipeline a unique name in order to avoid conflicts (use the convention traces/<dest-name>-<dest.GetID()> for traces pipelines and otlp/<dest-name>-<dest.GetID()> for OpenTelemetry exporters). You can assume a basic configuration is already provided in the currentConfig object, for details see getBasicConfig method in common/config/root.go file.
  • You can use the utility methods isTracingEnabled, isMetricsEnabled and isLoggingEnabled to determine which signals are selected by the user for the destination and configure the collector accordingly.
  • The last step is to register the new destination struct in the common/config/root.go file:
var availableConfigers = []Configer{/* List of existing destinations  */, &MyDest{}}

That’s it! Now you can use your new destination in the UI and send data to it.

Please submit a PR to the odigos git repository, we are happy to accept new destinations.