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