Skip to content

Commit

Permalink
Draft: feat: Add faro reciever
Browse files Browse the repository at this point in the history
Signed-off-by: dark0dave <[email protected]>
  • Loading branch information
dark0dave committed Feb 26, 2025
1 parent 0e48653 commit 49dfbd2
Show file tree
Hide file tree
Showing 17 changed files with 1,184 additions and 0 deletions.
1 change: 1 addition & 0 deletions receiver/faroreceiver/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
21 changes: 21 additions & 0 deletions receiver/faroreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Faro Receiver

This receiver can receive telemetry data from the [Grafana Faro Web SDK](https://github.com/grafana/faro-web-sdk).
The telemetry data is in JSON format and adheres to the Faro OpenAPI schema, as defined in the [github.com/grafana/faro](https://github.com/grafana/faro)

## Receiver Configuration

The following receiver configuration parameters are supported.

| Name | Description | Default |
|:-----------|:----------------------------------|----------------|
| `endpoint` | Endpoint exposed by this receiver | localhost:8080 |


# Example Configuration

```yaml
receivers:
faro:
endpoint: 'localhost:8081'
```
36 changes: 36 additions & 0 deletions receiver/faroreceiver/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0

package faroreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver"

import (
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/confmap"
)

type Config struct {
*confighttp.ServerConfig `mapstructure:",squash"`
}

var (
_ component.Config = (*Config)(nil)
_ confmap.Unmarshaler = (*Config)(nil)
)

func (cfg *Config) Validate() error {
if cfg.Endpoint == "" {
return errors.New("must specify endpoint")
}
return nil
}

func (cfg *Config) Unmarshal(conf *confmap.Conf) error {
err := conf.Unmarshal(cfg)
if err != nil {
return err
}

return nil
}
55 changes: 55 additions & 0 deletions receiver/faroreceiver/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package faroreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver"

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap/confmaptest"
)

func TestUnmarshalDefaultConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
}

func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
ServerConfig: &confighttp.ServerConfig{
Auth: &confighttp.AuthConfig{
Authentication: configauth.Authentication{
AuthenticatorID: component.MustNewID("test"),
},
},
Endpoint: "localhost:8080",
TLSSetting: &configtls.ServerConfig{
Config: configtls.Config{
CertFile: "test.crt",
KeyFile: "test.key",
},
},
CORS: &confighttp.CORSConfig{
AllowedOrigins: []string{"https://*.test.com", "https://test.com"},
MaxAge: 7200,
},
},
}, cfg)
}
91 changes: 91 additions & 0 deletions receiver/faroreceiver/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Apache-2.0

package faroreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver"

import (
"bytes"

"github.com/gogo/protobuf/jsonpb"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
spb "google.golang.org/genproto/googleapis/rpc/status"
)

const (
jsonContentType = "application/json"
)

var (
jsEncoder = &jsonEncoder{}
jsonPbMarshaler = &jsonpb.Marshaler{}
)

type encoder interface {
unmarshalTracesRequest(buf []byte) (ptraceotlp.ExportRequest, error)
unmarshalMetricsRequest(buf []byte) (pmetricotlp.ExportRequest, error)
unmarshalLogsRequest(buf []byte) (plogotlp.ExportRequest, error)
unmarshalProfilesRequest(buf []byte) (pprofileotlp.ExportRequest, error)

marshalTracesResponse(ptraceotlp.ExportResponse) ([]byte, error)
marshalMetricsResponse(pmetricotlp.ExportResponse) ([]byte, error)
marshalLogsResponse(plogotlp.ExportResponse) ([]byte, error)
marshalProfilesResponse(pprofileotlp.ExportResponse) ([]byte, error)

marshalStatus(rsp *spb.Status) ([]byte, error)

contentType() string
}

type jsonEncoder struct{}

func (jsonEncoder) unmarshalTracesRequest(buf []byte) (ptraceotlp.ExportRequest, error) {
req := ptraceotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}

func (jsonEncoder) unmarshalMetricsRequest(buf []byte) (pmetricotlp.ExportRequest, error) {
req := pmetricotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}

func (jsonEncoder) unmarshalLogsRequest(buf []byte) (plogotlp.ExportRequest, error) {
req := plogotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}

func (jsonEncoder) unmarshalProfilesRequest(buf []byte) (pprofileotlp.ExportRequest, error) {
req := pprofileotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}

func (jsonEncoder) marshalTracesResponse(resp ptraceotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}

func (jsonEncoder) marshalMetricsResponse(resp pmetricotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}

func (jsonEncoder) marshalLogsResponse(resp plogotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}

func (jsonEncoder) marshalProfilesResponse(resp pprofileotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}

func (jsonEncoder) marshalStatus(resp *spb.Status) ([]byte, error) {
buf := new(bytes.Buffer)
err := jsonPbMarshaler.Marshal(buf, resp)
return buf.Bytes(), err
}

func (jsonEncoder) contentType() string {
return jsonContentType
}
100 changes: 100 additions & 0 deletions receiver/faroreceiver/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: Apache-2.0

package faroreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver"

import (
"context"
"fmt"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"

"github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver/internal/metadata"
)

const (
defaultFaroEndpoint = "localhost:8080"
)

// This is the map of already created Faro receivers for particular configurations.
// We maintain this map because the receiver.Factory is asked trace and metric receivers separately
// when it gets createFaroReceiverTraces() and createFaroReceiverLogs() but they must not
// create separate objects, they must use one faroReceiver object per configuration.
// When the receiver is shutdown it should be removed from this map so the same configuration
// can be recreated successfully.
var receivers = sharedcomponent.NewSharedComponents()

func createDefaultConfig() component.Config {
return &Config{
ServerConfig: &confighttp.ServerConfig{
Endpoint: defaultFaroEndpoint,
},
}
}

func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
createDefaultConfig,
receiver.WithTraces(createFaroReceiverTraces, metadata.TracesStability),
receiver.WithLogs(createFaroReceiverLogs, metadata.LogsStability))
}

func createFaroReceiverTraces(
_ context.Context,
set receiver.Settings,
cfg component.Config,
nextTraces consumer.Traces,
) (receiver.Traces, error) {
fCfg, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("invalid configuration: %T", cfg)
}
var err error
receiver := receivers.GetOrAdd(
fCfg,
func() component.Component {
var rcv component.Component
rcv, err = newFaroReceiver(fCfg, &set)
return rcv
},
)
if err != nil {
return nil, err
}

receiver.Unwrap().(*faroReceiver).RegisterTracesConsumer(nextTraces)

return receiver, nil
}

func createFaroReceiverLogs(
_ context.Context,
set receiver.Settings,
cfg component.Config,
nextLogs consumer.Logs,
) (receiver.Logs, error) {
fCfg, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("invalid configuration: %T", cfg)
}
var err error
receiver := receivers.GetOrAdd(
fCfg,
func() component.Component {
var rcv component.Component
rcv, err = newFaroReceiver(fCfg, &set)
return rcv
},
)
if err != nil {
return nil, err
}

receiver.Unwrap().(*faroReceiver).RegisterLogsConsumer(nextLogs)

return receiver, nil
}
81 changes: 81 additions & 0 deletions receiver/faroreceiver/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver

go 1.23.3

require (
github.com/grafana/faro/pkg/go v0.0.0-20250212082855-b99ea3b24d33
github.com/grafana/faro/pkg/translator/faro v0.0.0-20250212082855-b99ea3b24d33
github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.120.1
go.opentelemetry.io/collector/config/confighttp v0.119.0
go.opentelemetry.io/collector/pdata/pprofile v0.119.0
)

require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
github.com/knadh/koanf/v2 v2.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.119.0 // indirect
go.opentelemetry.io/collector/pipeline v0.120.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.119.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/collector/client v1.25.0 // indirect
go.opentelemetry.io/collector/component v0.120.0
go.opentelemetry.io/collector/component/componentstatus v0.120.0
go.opentelemetry.io/collector/component/componenttest v0.120.0
go.opentelemetry.io/collector/config/configauth v0.119.0
go.opentelemetry.io/collector/config/configcompression v1.25.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.25.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.25.0
go.opentelemetry.io/collector/confmap v1.25.0
go.opentelemetry.io/collector/consumer v1.25.0
go.opentelemetry.io/collector/consumer/consumertest v0.119.0
go.opentelemetry.io/collector/consumer/xconsumer v0.119.0 // indirect
go.opentelemetry.io/collector/extension v0.119.0 // indirect
go.opentelemetry.io/collector/extension/auth v0.119.0 // indirect
go.opentelemetry.io/collector/pdata v1.26.0
go.opentelemetry.io/collector/receiver v0.119.0
go.opentelemetry.io/collector/receiver/receivertest v0.119.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5 // indirect
)
Loading

0 comments on commit 49dfbd2

Please sign in to comment.