Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ receiver/datadogreceiver/ @open-telemetry
receiver/dockerstatsreceiver/ @open-telemetry/collector-contrib-approvers @jamesmoessis
receiver/envoyalsreceiver/ @open-telemetry/collector-contrib-approvers @evan-bradley @zirain
receiver/expvarreceiver/ @open-telemetry/collector-contrib-approvers @jamesmoessis @MovieStoreGuy
receiver/faroreceiver/ @open-telemetry/collector-contrib-approvers @dehaansa @rlankfo @mar4uk
receiver/filelogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski
receiver/filestatsreceiver/ @open-telemetry/collector-contrib-approvers @atoulme
receiver/flinkmetricsreceiver/ @open-telemetry/collector-contrib-approvers @JonathanWamsley
Expand Down
1 change: 1 addition & 0 deletions cmd/otelcontribcol/builder-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ receivers:
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/elasticsearchreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/envoyalsreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/expvarreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/faroreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filestatsreceiver v0.120.1
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/flinkmetricsreceiver v0.120.1
Expand Down
1 change: 1 addition & 0 deletions internal/tidylist/tidylist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ receiver/couchdbreceiver
receiver/elasticsearchreceiver
receiver/envoyalsreceiver
receiver/expvarreceiver
receiver/faroreceiver
receiver/filestatsreceiver
receiver/flinkmetricsreceiver
receiver/fluentforwardreceiver
Expand Down
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
29 changes: 29 additions & 0 deletions receiver/faroreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Faro Receiver

This receiver can receive telemetry data from the [Grafana Faro Web SDK](https://github.com/grafana/faro-web-sdk).
The telemetry data must be in JSON format and adhere to the [Faro OpenAPI schema](https://github.com/grafana/faro/blob/main/spec/gen/faro.gen.yaml).

## Receiver Configuration

Faro follows the [confighttp] configuration, some examples are shown below


### Example Configuration

```yaml
receivers:
faro:
endpoint: 'localhost:8081'
```

### Advanced Configuration

```yaml
receivers:
faro:
endpoint: 'localhost:8081'
cors:
allowed_origins: "*"
```

[confighttp]: https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/confighttp#server-configuration
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 log 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
}
Loading