diff --git a/DESTINATIONS.md b/DESTINATIONS.md index 55ef97393..303489c91 100644 --- a/DESTINATIONS.md +++ b/DESTINATIONS.md @@ -20,6 +20,7 @@ | Splunk | ✅ | | | | Lightstep | ✅ | | | | Sentry | ✅ | | | +| Causely | ✅ | ✅ | | ## Open Source diff --git a/common/config/causely.go b/common/config/causely.go new file mode 100644 index 000000000..a7e8b22aa --- /dev/null +++ b/common/config/causely.go @@ -0,0 +1,98 @@ +package config + +import ( + "errors" + "fmt" + "github.com/odigos-io/odigos/common" + "net/url" + "strings" +) + +const ( + causelyUrl = "CAUSELY_URL" +) + +type Causely struct{} + +func (e *Causely) DestType() common.DestinationType { + return common.CauselyDestinationType +} + +func validateCauselyUrlInput(rawUrl string) (string, error) { + urlWithScheme := strings.TrimSpace(rawUrl) + + if !strings.Contains(rawUrl, "://") { + urlWithScheme = "http://" + rawUrl + } + + parsedUrl, err := url.Parse(urlWithScheme) + if err != nil { + return "", err + } + + // Causely does not support paths, so remove it + parsedUrl.Path = "" + + // --- validate the protocol --- + // if scheme is https, convert to "http" (Causely does not currently support TLS export) + if parsedUrl.Scheme == "https" { + parsedUrl.Scheme = "http" + } + // at this point if scheme is not http, it is invalid + if parsedUrl.Scheme != "http" { + return "", fmt.Errorf("Causely endpoint scheme must be http, got %s", parsedUrl.Scheme) + } + + // --- validate host --- + if parsedUrl.Hostname() == "" { + return "", fmt.Errorf("Causely endpoint host is required") + } + + // --- validate port --- + // allow the user specified port, but fallback to Causely default port 4317 if none provided + if parsedUrl.Port() == "" { + parsedUrl.Host = parsedUrl.Hostname() + ":4317" + if err != nil { + return "", err + } + } + + return parsedUrl.String(), nil +} + +func (e *Causely) ModifyConfig(dest ExporterConfigurer, currentConfig *Config) error { + rawUrl, exists := dest.GetConfig()[causelyUrl] + if !exists { + return errors.New("Causely url not specified, gateway will not be configured for Causely") + } + + validatedUrl, err := validateCauselyUrlInput(rawUrl) + if err != nil { + return errors.Join(err, errors.New("failed to parse Causely endpoint, gateway will not be configured for Causely")) + } + + exporterName := "otlp/causely-" + dest.GetName() + + currentConfig.Exporters[exporterName] = GenericMap{ + "endpoint": validatedUrl, + "tls": GenericMap{ + "insecure": true, + }, + } + + if isTracingEnabled(dest) { + tracesPipelineName := "traces/causely-" + dest.GetName() + currentConfig.Service.Pipelines[tracesPipelineName] = Pipeline{ + Exporters: []string{exporterName}, + } + } + + if isMetricsEnabled(dest) { + logsPipelineName := "metrics/causely-" + dest.GetName() + currentConfig.Service.Pipelines[logsPipelineName] = Pipeline{ + Exporters: []string{exporterName}, + } + } + + return nil +} diff --git a/common/config/causely_test.go b/common/config/causely_test.go new file mode 100644 index 000000000..238a284a2 --- /dev/null +++ b/common/config/causely_test.go @@ -0,0 +1,119 @@ +package config + +import ( + "testing" +) + +func TestCauselyUrlFromInput(t *testing.T) { + type args struct { + rawUrl string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "valid url", + args: args{ + rawUrl: "http://mediator.causely:4317", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "remove path from url", + args: args{ + rawUrl: "http://mediator.causely:4317/", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "add http protocol if missing", + args: args{ + rawUrl: "mediator.causely:4317", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "convert https protocol to http", + args: args{ + rawUrl: "https://mediator.causely:4317", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "allow only http and https protocols", + args: args{ + rawUrl: "ftp://mediator.causely:4317", + }, + want: "", + wantErr: true, + }, + { + name: "add default port if missing", + args: args{ + rawUrl: "http://mediator.causely", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "allow non standard port", + args: args{ + rawUrl: "http://mediator.causely:4567", + }, + want: "http://mediator.causely:4567", + wantErr: false, + }, + { + name: "remove whitespaces", + args: args{ + rawUrl: " http://mediator.causely:4317 ", + }, + want: "http://mediator.causely:4317", + wantErr: false, + }, + { + name: "non numeric port", + args: args{ + rawUrl: "http://mediator.causely:a4317/", + }, + want: "", + wantErr: true, + }, + { + name: "missing host", + args: args{ + rawUrl: "http://:4317", + }, + want: "", + wantErr: true, + }, + { + name: "missing host and port", + args: args{ + rawUrl: "http://", + }, + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateCauselyUrlInput(tt.args.rawUrl) + if (err != nil) != tt.wantErr { + t.Errorf("validateCauselyUrlInput() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("validateCauselyUrlInput() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/common/config/root.go b/common/config/root.go index 9ab183241..c3b9a2914 100644 --- a/common/config/root.go +++ b/common/config/root.go @@ -15,7 +15,7 @@ const ( var availableConfigers = []Configer{&Middleware{}, &Honeycomb{}, &GrafanaCloudPrometheus{}, &GrafanaCloudTempo{}, &GrafanaCloudLoki{}, &Datadog{}, &NewRelic{}, &Logzio{}, &Prometheus{}, &Tempo{}, &Loki{}, &Jaeger{}, &GenericOTLP{}, &OTLPHttp{}, &Elasticsearch{}, &Quickwit{}, &Signoz{}, &Qryn{}, &OpsVerse{}, &Splunk{}, &Lightstep{}, &GoogleCloud{}, &GoogleCloudStorage{}, &Sentry{}, &AzureBlobStorage{}, - &AWSS3{}, &Dynatrace{}, &Chronosphere{}, &ElasticAPM{}, &Axiom{}, &SumoLogic{}, &Coralogix{}} + &AWSS3{}, &Dynatrace{}, &Chronosphere{}, &ElasticAPM{}, &Axiom{}, &SumoLogic{}, &Coralogix{}, &Causely{}} type Configer interface { DestType() common.DestinationType diff --git a/common/dests.go b/common/dests.go index cd949f068..f060343a5 100644 --- a/common/dests.go +++ b/common/dests.go @@ -35,4 +35,5 @@ const ( AxiomDestinationType DestinationType = "axiom" SumoLogicDestinationType DestinationType = "sumologic" CoralogixDestinationType DestinationType = "coralogix" + CauselyDestinationType DestinationType = "causely" ) diff --git a/destinations/data/causely.yaml b/destinations/data/causely.yaml new file mode 100644 index 000000000..942c64eef --- /dev/null +++ b/destinations/data/causely.yaml @@ -0,0 +1,20 @@ +apiVersion: internal.odigos.io/v1beta1 +kind: Destination +metadata: + type: causely + displayName: Causely + category: managed +spec: + image: causely.svg + signals: + traces: + supported: true + metrics: + supported: true + fields: + - name: CAUSELY_URL + displayName: Endpoint + componentType: input + componentProps: + type: text + required: true \ No newline at end of file diff --git a/destinations/logos/causely.svg b/destinations/logos/causely.svg new file mode 100644 index 000000000..96b423f87 --- /dev/null +++ b/destinations/logos/causely.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/backends/causely.mdx b/docs/backends/causely.mdx new file mode 100644 index 000000000..8d04750d8 --- /dev/null +++ b/docs/backends/causely.mdx @@ -0,0 +1,23 @@ +--- +title: "Causely" +--- + +## Configuring the Causely Backend + +To complete adding a new Causely backend, input the following information into the Odigos UI + +### Causely Destination Settings + +To send Metrics/Traces to Causely, you need to configure the Causely URL in the Odigos UI. +This destination is for the Causely Mediator Service, so you will need to have a Causely instance running and accessible from the k8s cluster running odigos. + +#### Endpoint + +The endpoint URL is the combined `://:` to access your Causely Mediator service. + +- Protocol should be `http`; using `https` or omitting it will automatically be converted to `http` +- Hostname should typically follow the format: `mediator.` + - `namespace` is the k8s namespace where the Causely Mediator service is deployed +- Default port is `4317`; if no port is specified, it will be appended automatically + +Example: `http://mediator.causely:4317` \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index a9975f633..99698f88e 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -145,7 +145,8 @@ "backends/signoz", "backends/splunk", "backends/sumologic", - "backends/tempo" + "backends/tempo", + "backends/causely" ] }, { diff --git a/docs/quickstart/next-steps.mdx b/docs/quickstart/next-steps.mdx index e1f90f2fe..04e029c10 100644 --- a/docs/quickstart/next-steps.mdx +++ b/docs/quickstart/next-steps.mdx @@ -42,4 +42,5 @@ Select the relevant backend for your use case below to connect it to Odigos. + \ No newline at end of file