From 89aa4d0a6147ee2d096bf66acd3f6289f52fefcc Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Tue, 3 Dec 2024 17:00:20 +0200 Subject: [PATCH] [GEN-1847]: add AppDynamics as destination (#1905) --- README.md | 80 +++++++++--------- common/config/appdynamics.go | 127 +++++++++++++++++++++++++++++ common/config/root.go | 2 +- common/dests.go | 1 + destinations/data/appdynamics.yaml | 41 ++++++++++ destinations/logos/appdynamics.svg | 10 +++ docs/backends-overview.mdx | 62 ++++++++------ docs/backends/appdynamics.mdx | 71 ++++++++++++++++ docs/mint.json | 3 +- 9 files changed, 331 insertions(+), 66 deletions(-) create mode 100644 common/config/appdynamics.go create mode 100644 destinations/data/appdynamics.yaml create mode 100644 destinations/logos/appdynamics.svg create mode 100644 docs/backends/appdynamics.mdx diff --git a/README.md b/README.md index 8274e8742..95afe4146 100644 --- a/README.md +++ b/README.md @@ -82,44 +82,48 @@ For more details, see our [quickstart guide](https://docs.odigos.io/intro). **For step-by-step instructions detailed for every destination, see these [docs](https://docs.odigos.io/backends).** -### Managed - - -| | Traces | Metrics | Logs | -| ------------------------- | -------- | --------- | ------ | -| New Relic | ✅ | ✅ | ✅ | -| Datadog | ✅ | ✅ | ✅ | -| Grafana Cloud | ✅ | ✅ | ✅ | -| Honeycomb | ✅ | ✅ | ✅ | -| Chronosphere | ✅ | ✅ | | -| Logz.io | ✅ | ✅ | ✅ | -| qryn.cloud | ✅ | ✅ | ✅ | -| OpsVerse | ✅ | ✅ | ✅ | -| Dynatrace | ✅ | ✅ | ✅ | -| AWS S3 | ✅ | ✅ | ✅ | -| Google Cloud Monitoring | ✅ | | ✅ | -| Google Cloud Storage | ✅ | | ✅ | -| Azure Blob Storage | ✅ | | ✅ | -| Splunk | ✅ | | | -| Lightstep | ✅ | | | -| Sentry | ✅ | | | -| Axiom | ✅ | | ✅ | -| Sumo Logic | ✅ | ✅ | ✅ | -| Coralogix | ✅ | ✅ | ✅ | - -### Open Source - - -| | Traces | Metrics | Logs | -| --------------- | -------- | --------- | ------ | -| Prometheus | | ✅ | | -| Tempo | ✅ | | | -| Loki | | | ✅ | -| Jaeger | ✅ | | | -| SigNoz | ✅ | ✅ | ✅ | -| qryn | ✅ | ✅ | ✅ | -| Elasticsearch | ✅ | | ✅ | -| Quickwit | ✅ | | ✅ | +### Managed Destinations + +| Destination | Traces | Metrics | Logs | +|-------------------------|:------:|:-------:|:----:| +| AppDynamics | ✅ | | | +| Axiom | ✅ | | ✅ | +| AWS S3 | ✅ | | ✅ | +| Azure Blob Storage | ✅ | | ✅ | +| Causely | ✅ | | | +| Chronosphere | ✅ | ✅ | | +| Coralogix | ✅ | ✅ | ✅ | +| Datadog | ✅ | ✅ | ✅ | +| Dynatrace | ✅ | ✅ | ✅ | +| Gigapipe | ✅ | | | +| Google Cloud Monitoring | ✅ | ✅ | | +| Google Cloud Storage | ✅ | | ✅ | +| Grafana Cloud | ✅ | ✅ | ✅ | +| Honeycomb | ✅ | ✅ | ✅ | +| Last9 | ✅ | ✅ | | +| Lightstep | ✅ | | | +| Logz.io | ✅ | ✅ | ✅ | +| New Relic | ✅ | ✅ | ✅ | +| OpsVerse | ✅ | ✅ | ✅ | +| Sentry | ✅ | | | +| Splunk | ✅ | | | +| Sumo Logic | ✅ | ✅ | ✅ | + +## Self-Hosted (Open Source) Destinations + +| Destination | Traces | Metrics | Logs | +|---------------|:------:|:-------:|:----:| +| ClickHouse | ✅ | ✅ | ✅ | +| Elasticsearch | ✅ | | ✅ | +| Jaeger | ✅ | | | +| Loki | | | ✅ | +| OTLP | ✅ | ✅ | ✅ | +| OTLP HTTP | ✅ | ✅ | ✅ | +| Prometheus | | ✅ | | +| Quickwit | ✅ | | | +| qryn | ✅ | ✅ | ✅ | +| SigNoz | ✅ | ✅ | ✅ | +| Tempo | ✅ | | | Can't find the destination you need? Help us by following our quick [add new destination](https://docs.odigos.io/adding-new-dest) guide and submitting a PR. diff --git a/common/config/appdynamics.go b/common/config/appdynamics.go new file mode 100644 index 000000000..4459cb5ca --- /dev/null +++ b/common/config/appdynamics.go @@ -0,0 +1,127 @@ +package config + +import ( + "errors" + "strings" + + "github.com/odigos-io/odigos/common" +) + +const ( + APPDYNAMICS_APPLICATION_NAME = "APPDYNAMICS_APPLICATION_NAME" + APPDYNAMICS_ACCOUNT_NAME = "APPDYNAMICS_ACCOUNT_NAME" + APPDYNAMICS_ENDPOINT_URL = "APPDYNAMICS_ENDPOINT_URL" + APPDYNAMICS_API_KEY = "APPDYNAMICS_API_KEY" +) + +type AppDynamics struct{} + +func (m *AppDynamics) DestType() common.DestinationType { + // DestinationType defined in common/dests.go + return common.AppDynamicsDestinationType +} + +func (m *AppDynamics) ModifyConfig(dest ExporterConfigurer, currentConfig *Config) error { + config := dest.GetConfig() + uniqueUri := "appdynamics-" + dest.GetID() + + endpoint, endpointExists := config[APPDYNAMICS_ENDPOINT_URL] + if !endpointExists { + return errors.New("AppDynamics Endpoint URL (\"APPDYNAMICS_ENDPOINT_URL\") not specified, AppDynamics will not be configured") + } + + isHttpEndpoint := strings.HasPrefix(endpoint, "http://") + isHttpsEndpoint := strings.HasPrefix(endpoint, "https://") + + if !isHttpEndpoint && !isHttpsEndpoint { + return errors.New("AppDynamics Endpoint URL (\"APPDYNAMICS_ENDPOINT_URL\") malformed, HTTP prefix is required, AppDynamics will not be configured") + } + + accountName, accountNameExists := config[APPDYNAMICS_ACCOUNT_NAME] + if !accountNameExists { + return errors.New("AppDynamics Account Name (\"APPDYNAMICS_ACCOUNT_NAME\") not specified, AppDynamics will not be configured") + } + + applicationName, applicationNameExists := config[APPDYNAMICS_APPLICATION_NAME] + if !applicationNameExists { + applicationName = "odigos" + } + + endpointParts := strings.Split(endpoint, ".") + if len(endpointParts) > 0 { + // Replace the first part of the endpoint with the account name (instead of collecting another input from the user). + // Example: + // endpoint - "https://.saas.appdynamics.com" + // host - ".saas.appdynamics.com" + endpointParts[0] = accountName + } + host := strings.Join(endpointParts, ".") + + // Create config for exporter + + exporterName := "otlphttp/" + uniqueUri + currentConfig.Exporters[exporterName] = GenericMap{ + "endpoint": endpoint, + "headers": GenericMap{ + "x-api-key": "${APPDYNAMICS_API_KEY}", + }, + } + + // Create config for processor + + processorName := "resource/" + uniqueUri + currentConfig.Processors[processorName] = GenericMap{ + "attributes": []GenericMap{ + { + // This is required by AppDynamics, without it they will accept the data but not display it. + // This key will be used to identify the cluster in AppDynamics. + "key": "service.namespace", + "value": applicationName, + "action": "insert", + }, + { + "key": "appdynamics.controller.account", + "value": accountName, + "action": "insert", + }, + { + "key": "appdynamics.controller.host", + "value": host, + "action": "insert", + }, + { + "key": "appdynamics.controller.port", + "value": 443, + "action": "insert", + }, + }, + } + + // Apply configs to serivce + + if isTracingEnabled(dest) { + tracesPipelineName := "traces/" + uniqueUri + currentConfig.Service.Pipelines[tracesPipelineName] = Pipeline{ + Exporters: []string{exporterName}, + Processors: []string{processorName}, + } + } + + if isMetricsEnabled(dest) { + metricsPipelineName := "metrics/" + uniqueUri + currentConfig.Service.Pipelines[metricsPipelineName] = Pipeline{ + Exporters: []string{exporterName}, + Processors: []string{processorName}, + } + } + + if isLoggingEnabled(dest) { + logsPipelineName := "logs/" + uniqueUri + currentConfig.Service.Pipelines[logsPipelineName] = Pipeline{ + Exporters: []string{exporterName}, + Processors: []string{processorName}, + } + } + + return nil +} diff --git a/common/config/root.go b/common/config/root.go index 643cd55ec..43a0ef7d7 100644 --- a/common/config/root.go +++ b/common/config/root.go @@ -15,7 +15,7 @@ const ( ) var availableConfigers = []Configer{ - &Middleware{}, &Honeycomb{}, &GrafanaCloudPrometheus{}, &GrafanaCloudTempo{}, + &Middleware{}, &AppDynamics{}, &Honeycomb{}, &GrafanaCloudPrometheus{}, &GrafanaCloudTempo{}, &GrafanaCloudLoki{}, &Datadog{}, &NewRelic{}, &Logzio{}, &Last9{}, &Prometheus{}, &Tempo{}, &Loki{}, &Jaeger{}, &GenericOTLP{}, &OTLPHttp{}, &Elasticsearch{}, &Quickwit{}, &Signoz{}, &Qryn{}, &OpsVerse{}, &Splunk{}, &Lightstep{}, &GoogleCloud{}, &GoogleCloudStorage{}, &Sentry{}, &AzureBlobStorage{}, diff --git a/common/dests.go b/common/dests.go index 5c2f1dad5..99a017ab4 100644 --- a/common/dests.go +++ b/common/dests.go @@ -3,6 +3,7 @@ package common type DestinationType string const ( + AppDynamicsDestinationType DestinationType = "appdynamics" AWSS3DestinationType DestinationType = "s3" AxiomDestinationType DestinationType = "axiom" AzureBlobDestinationType DestinationType = "azureblob" diff --git a/destinations/data/appdynamics.yaml b/destinations/data/appdynamics.yaml new file mode 100644 index 000000000..0c59c6ec6 --- /dev/null +++ b/destinations/data/appdynamics.yaml @@ -0,0 +1,41 @@ +apiVersion: internal.odigos.io/v1beta1 +kind: Destination +metadata: + type: appdynamics + displayName: AppDynamics + category: managed +spec: + image: appdynamics.svg + signals: + traces: + supported: true + metrics: + supported: false + logs: + supported: false + fields: + - name: APPDYNAMICS_APPLICATION_NAME + displayName: Application Name + componentType: input + componentProps: + type: text + required: false + - name: APPDYNAMICS_ACCOUNT_NAME + displayName: Account Name + componentType: input + componentProps: + type: text + required: true + - name: APPDYNAMICS_ENDPOINT_URL + displayName: Endpoint URL + componentType: input + componentProps: + type: text + required: true + - name: APPDYNAMICS_API_KEY + displayName: API Key + componentType: input + secret: true + componentProps: + type: password + required: true diff --git a/destinations/logos/appdynamics.svg b/destinations/logos/appdynamics.svg new file mode 100644 index 000000000..9c72f133a --- /dev/null +++ b/destinations/logos/appdynamics.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/docs/backends-overview.mdx b/docs/backends-overview.mdx index 9413fb928..6ae47d8c6 100644 --- a/docs/backends-overview.mdx +++ b/docs/backends-overview.mdx @@ -1,33 +1,43 @@ --- -title: "Overview" +title: 'Overview' --- Odigos has destinations for many observability backends. -| | Traces | Metrics | Logs | Open Source | -| ----------------------- | :---: | :-----: | :--: | :---------: | -| SigNoz | ✅ | ✅ | ✅ | ✅ | -| qryn | ✅ | ✅ | ✅ | ✅ | -| Uptrace | ✅ | ✅ | ✅ | ✅ | -| Elasticsearch | ✅ | | ✅ | ✅ | -| Jaeger | ✅ | | | ✅ | -| Tempo | ✅ | | | ✅ | -| Prometheus | | ✅ | | ✅ | -| Loki | | | ✅ | ✅ | -| Datadog | ✅ | ✅ | ✅ | | -| Grafana Cloud | ✅ | ✅ | ✅ | | -| Honeycomb | ✅ | ✅ | ✅ | | -| Logz.io | ✅ | ✅ | ✅ | | -| New Relic | ✅ | ✅ | ✅ | | -| OpsVerse | ✅ | ✅ | ✅ | | -| qryn.cloud | ✅ | ✅ | ✅ | | -| Causely | ✅ | ✅ | | | -| AWS S3 | ✅ | | ✅ | | -| Azure Blob Storage | ✅ | | ✅ | | -| Google Cloud Monitoring | ✅ | | ✅ | | -| Google Cloud Storage | ✅ | | ✅ | | -| Lightstep | ✅ | | | | -| Sentry | ✅ | | | | -| Splunk | ✅ | | | | +| Destination | Category | Traces | Metrics | Logs | +|-------------------------|------------------|:------:|:-------:|:----:| +| AppDynamics | Managed | ✅ | | | +| Axiom | Managed | ✅ | | ✅ | +| AWS S3 | Managed | ✅ | | ✅ | +| Azure Blob Storage | Managed | ✅ | | ✅ | +| Causely | Managed | ✅ | | | +| Chronosphere | Managed | ✅ | ✅ | | +| ClickHouse | Self-Hosted | ✅ | ✅ | ✅ | +| Coralogix | Managed | ✅ | ✅ | ✅ | +| Datadog | Managed | ✅ | ✅ | ✅ | +| Dynatrace | Managed | ✅ | ✅ | ✅ | +| Elasticsearch | Self-Hosted | ✅ | | ✅ | +| Gigapipe | Managed | ✅ | | | +| Google Cloud Monitoring | Managed | ✅ | ✅ | | +| Google Cloud Storage | Managed | ✅ | | ✅ | +| Grafana Cloud | Managed | ✅ | ✅ | ✅ | +| Honeycomb | Managed | ✅ | ✅ | ✅ | +| Jaeger | Self-Hosted | ✅ | | | +| Last9 | Managed | ✅ | ✅ | | +| Lightstep | Managed | ✅ | | | +| Loki | Self-Hosted | | | ✅ | +| Logz.io | Managed | ✅ | ✅ | ✅ | +| New Relic | Managed | ✅ | ✅ | ✅ | +| OpsVerse | Managed | ✅ | ✅ | ✅ | +| OTLP | Managed | ✅ | ✅ | ✅ | +| OTLP HTTP | Managed | ✅ | ✅ | ✅ | +| Prometheus | Self-Hosted | | ✅ | | +| Quickwit | Self-Hosted | ✅ | | | +| qryn | Self-Hosted | ✅ | ✅ | ✅ | +| Sentry | Managed | ✅ | | | +| SigNoz | Self-Hosted | ✅ | ✅ | ✅ | +| Splunk | Managed | ✅ | | | +| Sumo Logic | Managed | ✅ | ✅ | ✅ | +| Tempo | Self-Hosted | ✅ | | | Can't find the destination you need? Help us by following our quick [adding new destination](/adding-new-dest) guide and submit a PR. diff --git a/docs/backends/appdynamics.mdx b/docs/backends/appdynamics.mdx new file mode 100644 index 000000000..08b9576af --- /dev/null +++ b/docs/backends/appdynamics.mdx @@ -0,0 +1,71 @@ +--- +title: 'AppDynamics' +--- + +## Configuring AppDynamics Backend + +1. [Register](https://accounts.appdynamics.com/trial)/[Login](https://login.appdynamics.com/sso/authenticate) to AppDynamics. +2. Navigate to the OpenTelemetry Configuration page: + +```bash +https://{APPDYNAMICS_ACCOUNT_NAME}.saas.appdynamics.com/controller/#/apm/otel/keyManagement +``` + +3. Click on `Processors`, you'll find 3 attributes, locate `appdynamics.controller.account` and copy it's value, this is your `Account Name`. +4. Click on `Exporters`, copy the `Endpoint`, and generate an `API Key`. + +- **APPDYNAMICS_APPLICATION_NAME** - Application Name (will define a namespace in AppDynamics) +- **APPDYNAMICS_ACCOUNT_NAME** - Account Name from above (step 3). +- **APPDYNAMICS_ENDPOINT_URL** - OTLP HTTP Endpoint URL from above (step 4). +- **APPDYNAMICS_API_KEY** - API Key from above (step 4). + +## Adding a destination to Odigos + +Odigos makes it simple to add and configure destinations, allowing you to select the specific signals [traces/logs/metrics] that you want to send to each destination. There are two primary methods for configuring destinations in Odigos: + +1. **Using the UI** + +- Use the [Odigos CLI](https://docs.odigos.io/cli/odigos_ui) to access the UI: + +```bash +odigos ui +``` + +2. **Using kubernetes manifests** + +Save the YAML below to a file (e.g., `destination.yaml`) and apply it using `kubectl`: + +```bash +kubectl apply -f destination.yaml +``` + + +```yaml +apiVersion: odigos.io/v1alpha1 +kind: Destination +metadata: + name: appdynamics-example + namespace: odigos-system +spec: + data: + APPDYNAMICS_ACCOUNT_NAME: + # APPDYNAMICS_APPLICATION_NAME: + # Note: The commented fields above are optional. + APPDYNAMICS_ENDPOINT_URL: + destinationName: appdynamics + secretRef: + name: appdynamics-secret + signals: + - TRACES + type: appdynamics + +--- +apiVersion: v1 +data: + APPDYNAMICS_API_KEY: +kind: Secret +metadata: + name: appdynamics-secret + namespace: odigos-system +type: Opaque +``` \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 32718b0c8..c30d9ac29 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -200,6 +200,7 @@ { "group": "Supported Backends", "pages": [ + "backends/appdynamics", "backends/s3", "backends/azureblob", "backends/causely", @@ -217,8 +218,8 @@ "backends/grafanacloudtempo", "backends/honeycomb", "backends/jaeger", - "backends/lightstep", "backends/last9", + "backends/lightstep", "backends/logzio", "backends/loki", "backends/newrelic",