Skip to content

Commit ad0f84a

Browse files
authored
Merge pull request #16 from lightstep/add-lightstepreceiver
Initial import of lightstepreceiver
2 parents d40bc8c + b359d47 commit ad0f84a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+6651
-1
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Design
2+
3+
## Summary
4+
5+
This receiver exposes *very* basic functionality, with only http/protobuf support
6+
(initially). Details are:
7+
8+
* `ReportRequest` is the protobuf we send/receive, with `ReportRequest.Report`
9+
being similar to `Resource` (e.g. `Resource` has attributes in its `Tags` attribute).
10+
* Legacy tracers send the service name as `lightstep.component_name` in
11+
`ReportRequest.Report.Tags`, and we derive the actual OTel `service.name`
12+
from it, falling back to `unknown_service`.
13+
* We do a **raw** ingestion/conversion, meaning we don't do any semconv mapping,
14+
other than deriving `service.name` from `lightstep.component_name`. See
15+
TODO below.
16+
* Legacy tracers send 64 bits TraceIds, which we convert to 128 bytes OTel ids.
17+
* Clock correction: Some legacy tracers (Java) perform clock correction, sending
18+
along a timeoffset to be applied, and expecting back Receive/Transmit
19+
timestamps from the microsatellites/collector:
20+
- `ReportRequest`: `TimestampOffsetMicros` includes an offset that MUST
21+
be applied to all timestamps being reported. This value is zero if
22+
no clock correction is required.
23+
- `ReportResponse`: This receiver sends two timestamps, `Receive` and
24+
`Transmit`, with the times at which the latest request was received
25+
and later answered with a response, in order to help the tracers
26+
adjust their offsets.
27+
28+
## TODO
29+
30+
* Use `receiverhelper` mechanism for standard component observability signals.
31+
* Legacy tracers send payloads using the `application/octet-stream` content type and using the
32+
`/api/v2/reports` path. We don't check for it but worth verifying this (at least the
33+
content-type).
34+
* Top level `ReporterId` is not being used at this moment.
35+
* `Baggage` is being sent as part of Lightstep's `SpanContext`, but it is not exported in any way at this point.
36+
* Find all special Tags (e.g. "lightstep.*") and think which ones we should map.
37+
* Implement gRPC support.
38+
* Implement Thrift support.
39+
* Consider mapping semantic conventions:
40+
- Values that can be consumed within the processor, e.g. detect event names from Logs, detect `StatusKind` from error tags.
41+
- Consider using the OpenTracing compatibilty section in the Specification, which states how to process errors and multiple parents.
42+
- Values that affect the entire OT ecosystem. Probably can be offered as a separate processor instead.
43+
- Lightstep-specific tags (attributes) that _may_ need to be mapped to become useful for OTel processors.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Partially copied from github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/idutils
5+
6+
package lightstepreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lightstepreceiver"
7+
8+
import (
9+
"encoding/binary"
10+
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
)
13+
14+
// UInt64ToTraceID converts the pair of uint64 representation of a TraceID to pcommon.TraceID.
15+
func UInt64ToTraceID(high, low uint64) pcommon.TraceID {
16+
traceID := [16]byte{}
17+
binary.BigEndian.PutUint64(traceID[:8], high)
18+
binary.BigEndian.PutUint64(traceID[8:], low)
19+
return traceID
20+
}
21+
22+
// UInt64ToSpanID converts the uint64 representation of a SpanID to pcommon.SpanID.
23+
func UInt64ToSpanID(id uint64) pcommon.SpanID {
24+
spanID := [8]byte{}
25+
binary.BigEndian.PutUint64(spanID[:], id)
26+
return pcommon.SpanID(spanID)
27+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package lightstepreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lightstepreceiver"
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"go.opentelemetry.io/collector/component"
11+
"go.opentelemetry.io/collector/config/confighttp"
12+
"go.opentelemetry.io/collector/confmap"
13+
)
14+
15+
const (
16+
protocolsFieldName = "protocols"
17+
protoHTTP = "http"
18+
)
19+
20+
type HTTPConfig struct {
21+
*confighttp.ServerConfig `mapstructure:",squash"`
22+
}
23+
24+
// Protocols is the configuration for the supported protocols.
25+
type Protocols struct {
26+
HTTP *HTTPConfig `mapstructure:"http"`
27+
}
28+
29+
// Config defines configuration for the Lightstep receiver.
30+
type Config struct {
31+
// Protocols is the configuration for the supported protocols, currently HTTP.
32+
Protocols `mapstructure:"protocols"`
33+
}
34+
35+
var _ component.Config = (*Config)(nil)
36+
37+
// Validate checks the receiver configuration is valid
38+
func (cfg *Config) Validate() error {
39+
if cfg.HTTP == nil {
40+
return errors.New("must specify at least one protocol when using the Lightstep receiver")
41+
}
42+
return nil
43+
}
44+
45+
// Unmarshal a confmap.Conf into the config struct.
46+
func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
47+
if componentParser == nil {
48+
return fmt.Errorf("nil config for lightstepreceiver")
49+
}
50+
51+
err := componentParser.Unmarshal(cfg)
52+
if err != nil {
53+
return err
54+
}
55+
protocols, err := componentParser.Sub(protocolsFieldName)
56+
if err != nil {
57+
return err
58+
}
59+
60+
if !protocols.IsSet(protoHTTP) {
61+
cfg.HTTP = nil
62+
}
63+
return nil
64+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package lightstepreceiver
5+
6+
import (
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"go.opentelemetry.io/collector/component"
14+
"go.opentelemetry.io/collector/config/confighttp"
15+
"go.opentelemetry.io/collector/config/configtls"
16+
"go.opentelemetry.io/collector/confmap"
17+
"go.opentelemetry.io/collector/confmap/confmaptest"
18+
)
19+
20+
func TestUnmarshalDefaultConfig(t *testing.T) {
21+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default.yaml"))
22+
require.NoError(t, err)
23+
factory := NewFactory()
24+
cfg := factory.CreateDefaultConfig()
25+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
26+
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
27+
}
28+
29+
func TestUnmarshalConfigOnlyHTTP(t *testing.T) {
30+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http.yaml"))
31+
require.NoError(t, err)
32+
factory := NewFactory()
33+
cfg := factory.CreateDefaultConfig()
34+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
35+
36+
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
37+
assert.Equal(t, defaultOnlyHTTP, cfg)
38+
}
39+
40+
func TestUnmarshalConfigOnlyHTTPNull(t *testing.T) {
41+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http_null.yaml"))
42+
require.NoError(t, err)
43+
factory := NewFactory()
44+
cfg := factory.CreateDefaultConfig()
45+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
46+
47+
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
48+
assert.Equal(t, defaultOnlyHTTP, cfg)
49+
}
50+
51+
func TestUnmarshalConfigOnlyHTTPEmptyMap(t *testing.T) {
52+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http_empty_map.yaml"))
53+
require.NoError(t, err)
54+
factory := NewFactory()
55+
cfg := factory.CreateDefaultConfig()
56+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
57+
58+
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
59+
assert.Equal(t, defaultOnlyHTTP, cfg)
60+
}
61+
62+
func TestUnmarshalConfig(t *testing.T) {
63+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
64+
require.NoError(t, err)
65+
factory := NewFactory()
66+
cfg := factory.CreateDefaultConfig()
67+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
68+
assert.Equal(t,
69+
&Config{
70+
Protocols: Protocols{
71+
HTTP: &HTTPConfig{
72+
ServerConfig: &confighttp.ServerConfig{
73+
Endpoint: "0.0.0.0:443",
74+
TLSSetting: &configtls.ServerConfig{
75+
Config: configtls.Config{
76+
CertFile: "test.crt",
77+
KeyFile: "test.key",
78+
},
79+
},
80+
CORS: &confighttp.CORSConfig{
81+
AllowedOrigins: []string{"https://*.test.com", "https://test.com"},
82+
MaxAge: 7200,
83+
},
84+
},
85+
},
86+
},
87+
}, cfg)
88+
89+
}
90+
91+
func TestUnmarshalConfigTypoDefaultProtocol(t *testing.T) {
92+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "typo_default_proto_config.yaml"))
93+
require.NoError(t, err)
94+
factory := NewFactory()
95+
cfg := factory.CreateDefaultConfig()
96+
assert.EqualError(t, component.UnmarshalConfig(cm, cfg), "1 error(s) decoding:\n\n* 'protocols' has invalid keys: htttp")
97+
}
98+
99+
func TestUnmarshalConfigInvalidProtocol(t *testing.T) {
100+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "bad_proto_config.yaml"))
101+
require.NoError(t, err)
102+
factory := NewFactory()
103+
cfg := factory.CreateDefaultConfig()
104+
assert.EqualError(t, component.UnmarshalConfig(cm, cfg), "1 error(s) decoding:\n\n* 'protocols' has invalid keys: thrift")
105+
}
106+
107+
func TestUnmarshalConfigEmptyProtocols(t *testing.T) {
108+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "bad_no_proto_config.yaml"))
109+
require.NoError(t, err)
110+
factory := NewFactory()
111+
cfg := factory.CreateDefaultConfig()
112+
assert.NoError(t, component.UnmarshalConfig(cm, cfg))
113+
assert.EqualError(t, component.ValidateConfig(cfg), "must specify at least one protocol when using the Lightstep receiver")
114+
}
115+
116+
func TestUnmarshalConfigEmpty(t *testing.T) {
117+
factory := NewFactory()
118+
cfg := factory.CreateDefaultConfig()
119+
assert.NoError(t, component.UnmarshalConfig(confmap.New(), cfg))
120+
assert.EqualError(t, component.ValidateConfig(cfg), "must specify at least one protocol when using the Lightstep receiver")
121+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package lightstepreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lightstepreceiver"
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/collector/component"
10+
"go.opentelemetry.io/collector/config/confighttp"
11+
"go.opentelemetry.io/collector/consumer"
12+
"go.opentelemetry.io/collector/receiver"
13+
14+
"github.com/lightstep/sn-collector/collector/lightstepreceiver/internal/metadata"
15+
)
16+
17+
// This file implements factory bits for the Lightstep receiver.
18+
19+
const (
20+
// TODO: Define a new port for us to use.
21+
defaultBindEndpoint = "0.0.0.0:443"
22+
)
23+
24+
// NewFactory creates a new Lightstep receiver factory
25+
func NewFactory() receiver.Factory {
26+
return receiver.NewFactory(
27+
metadata.Type,
28+
createDefaultConfig,
29+
receiver.WithTraces(createTracesReceiver, metadata.TracesStability),
30+
)
31+
}
32+
33+
// createDefaultConfig creates the default configuration for Lightstep receiver.
34+
func createDefaultConfig() component.Config {
35+
return &Config{
36+
Protocols: Protocols{
37+
HTTP: &HTTPConfig{
38+
ServerConfig: &confighttp.ServerConfig{
39+
Endpoint: defaultBindEndpoint,
40+
},
41+
},
42+
},
43+
}
44+
}
45+
46+
// createTracesReceiver creates a trace receiver based on provided config.
47+
func createTracesReceiver(
48+
_ context.Context,
49+
set receiver.CreateSettings,
50+
cfg component.Config,
51+
consumer consumer.Traces,
52+
) (receiver.Traces, error) {
53+
rCfg := cfg.(*Config)
54+
return newReceiver(rCfg, consumer, set)
55+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package lightstepreceiver
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
12+
"go.opentelemetry.io/collector/component/componenttest"
13+
"go.opentelemetry.io/collector/consumer/consumertest"
14+
"go.opentelemetry.io/collector/receiver/receivertest"
15+
)
16+
17+
func TestCreateDefaultConfig(t *testing.T) {
18+
cfg := createDefaultConfig()
19+
assert.NotNil(t, cfg, "failed to create default config")
20+
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
21+
}
22+
23+
func TestCreateReceiver(t *testing.T) {
24+
cfg := createDefaultConfig()
25+
26+
tReceiver, err := createTracesReceiver(
27+
context.Background(),
28+
receivertest.NewNopCreateSettings(),
29+
cfg,
30+
consumertest.NewNop())
31+
assert.NoError(t, err, "receiver creation failed")
32+
assert.NotNil(t, tReceiver, "receiver creation failed")
33+
34+
tReceiver, err = createTracesReceiver(
35+
context.Background(),
36+
receivertest.NewNopCreateSettings(),
37+
cfg,
38+
consumertest.NewNop())
39+
assert.NoError(t, err, "receiver creation failed")
40+
assert.NotNil(t, tReceiver, "receiver creation failed")
41+
}

0 commit comments

Comments
 (0)