Skip to content

Commit a5b1bbc

Browse files
committed
service: remove otelconftelemetry dependency
1 parent ebcdbd6 commit a5b1bbc

27 files changed

+490
-541
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: breaking
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
7+
component: pkg/service
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Replace service/telemetry.MeterSettings.DefaultViews
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [13809]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
The `service/telemetry.MeterSettings.DefaultViews` field has been removed, and replaced with
20+
`service/telemetry.MeterSettings.DefaultDroppedInstruments`. This new field is a function that
21+
returns a slice of `InstrumentSelector`s, each of which identifies a meter name and/or instrument
22+
name to be dropped by default. These are directly translated to views by otelconftelemetry.
23+
24+
# Optional: The change log or logs in which this entry should be included.
25+
# e.g. '[user]' or '[user, api]'
26+
# Include 'user' if the change is relevant to end users.
27+
# Include 'api' if there is a change to a library API.
28+
# Default: '[user]'
29+
change_logs: [api]

internal/e2e/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.24.0
44

55
require (
66
github.com/google/go-cmp v0.7.0
7+
github.com/google/uuid v1.6.0
8+
github.com/prometheus/client_model v0.6.2
79
github.com/prometheus/common v0.67.1
810
github.com/stretchr/testify v1.11.1
911
go.opentelemetry.io/collector/component v1.48.0
@@ -68,7 +70,6 @@ require (
6870
github.com/gobwas/glob v0.2.3 // indirect
6971
github.com/golang/snappy v1.0.0 // indirect
7072
github.com/google/go-tpm v0.9.7 // indirect
71-
github.com/google/uuid v1.6.0 // indirect
7273
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
7374
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
7475
github.com/hashicorp/go-version v1.8.0 // indirect
@@ -89,7 +90,6 @@ require (
8990
github.com/pmezard/go-difflib v1.0.0 // indirect
9091
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
9192
github.com/prometheus/client_golang v1.23.2 // indirect
92-
github.com/prometheus/client_model v0.6.2 // indirect
9393
github.com/prometheus/otlptranslator v0.0.2 // indirect
9494
github.com/prometheus/procfs v0.17.0 // indirect
9595
github.com/rs/cors v1.11.1 // indirect
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"bytes"
8+
"encoding/json"
9+
"fmt"
10+
"io"
11+
"log"
12+
"net/http"
13+
"net/http/httptest"
14+
"net/url"
15+
"os"
16+
"path/filepath"
17+
"slices"
18+
"sync"
19+
"testing"
20+
"time"
21+
22+
"github.com/google/uuid"
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
"go.uber.org/zap"
26+
27+
"go.opentelemetry.io/collector/component"
28+
"go.opentelemetry.io/collector/confmap"
29+
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
30+
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
31+
"go.opentelemetry.io/collector/consumer/consumertest"
32+
"go.opentelemetry.io/collector/exporter"
33+
"go.opentelemetry.io/collector/exporter/exportertest"
34+
"go.opentelemetry.io/collector/otelcol"
35+
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
36+
"go.opentelemetry.io/collector/receiver"
37+
"go.opentelemetry.io/collector/receiver/otlpreceiver"
38+
"go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
39+
)
40+
41+
// TestInternalTelemetry_ServiceInstanceID verifies that the service.instance.id
42+
// attribute is generated by default (unless overridden), and is is consistent
43+
// across all internal telemetry providers.
44+
func TestInternalTelemetry_ServiceInstanceID(t *testing.T) {
45+
type testcase struct {
46+
extraYamlConfig string
47+
checkServiceInstanceID func(t *testing.T, serviceInstanceID string)
48+
}
49+
50+
for name, tt := range map[string]testcase{
51+
"default": {
52+
checkServiceInstanceID: func(t *testing.T, serviceInstanceID string) {
53+
// By default, service.instance.id should be a generated UUIDv4
54+
_, err := uuid.Parse(serviceInstanceID)
55+
require.NoError(t, err)
56+
},
57+
},
58+
"service.instance.id set in config": {
59+
extraYamlConfig: `
60+
service:
61+
telemetry:
62+
resource:
63+
service.instance.id: "my-custom-instance-id"`,
64+
checkServiceInstanceID: func(t *testing.T, serviceInstanceID string) {
65+
assert.Equal(t, "my-custom-instance-id", serviceInstanceID)
66+
},
67+
},
68+
} {
69+
t.Run(name, func(t *testing.T) {
70+
// Set up HTTP server to capture traces from collector's internal telemetry
71+
traceSink := new(consumertest.TracesSink)
72+
traceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73+
body, err := io.ReadAll(r.Body)
74+
if err != nil {
75+
http.Error(w, err.Error(), http.StatusBadRequest)
76+
return
77+
}
78+
otlpReq := ptraceotlp.NewExportRequest()
79+
if err := otlpReq.UnmarshalProto(body); err != nil {
80+
http.Error(w, err.Error(), http.StatusBadRequest)
81+
return
82+
}
83+
_ = traceSink.ConsumeTraces(r.Context(), otlpReq.Traces())
84+
}))
85+
defer traceServer.Close()
86+
87+
logSink := registerTestLogSink(t)
88+
89+
// Create temporary directory for the config file
90+
tempdir := t.TempDir()
91+
configFile := filepath.Join(tempdir, "config.yaml")
92+
93+
// Create YAML config
94+
otlphttpPort := getFreePort(t)
95+
metricsPort := getFreePort(t)
96+
require.NoError(t, os.WriteFile(configFile, []byte(fmt.Sprintf(`
97+
receivers:
98+
otlp:
99+
protocols:
100+
http:
101+
endpoint: localhost:%s
102+
103+
exporters:
104+
nop:
105+
106+
service:
107+
telemetry:
108+
logs:
109+
level: info
110+
encoding: json
111+
output_paths: [%q]
112+
metrics:
113+
level: normal
114+
readers:
115+
- pull:
116+
exporter:
117+
prometheus:
118+
host: localhost
119+
port: %s
120+
traces:
121+
level: normal
122+
processors:
123+
- simple:
124+
exporter:
125+
otlp:
126+
protocol: http/protobuf
127+
endpoint: %s
128+
pipelines:
129+
traces:
130+
receivers: [otlp]
131+
exporters: [nop]
132+
`, otlphttpPort, logSink.url, metricsPort, traceServer.URL)[1:]), 0o600))
133+
134+
// Create collector
135+
configURIs := []string{configFile}
136+
if tt.extraYamlConfig != "" {
137+
configURIs = append(configURIs, "yaml:"+tt.extraYamlConfig)
138+
}
139+
collector, err := otelcol.NewCollector(otelcol.CollectorSettings{
140+
BuildInfo: component.NewDefaultBuildInfo(),
141+
Factories: func() (otelcol.Factories, error) {
142+
otlpreceiverFactory := otlpreceiver.NewFactory()
143+
return otelcol.Factories{
144+
Receivers: map[component.Type]receiver.Factory{
145+
otlpreceiverFactory.Type(): otlpreceiverFactory,
146+
},
147+
Exporters: map[component.Type]exporter.Factory{
148+
nopType: exportertest.NewNopFactory(),
149+
},
150+
Telemetry: otelconftelemetry.NewFactory(),
151+
}, nil
152+
},
153+
ConfigProviderSettings: otelcol.ConfigProviderSettings{
154+
ResolverSettings: confmap.ResolverSettings{
155+
URIs: configURIs,
156+
ProviderFactories: []confmap.ProviderFactory{
157+
fileprovider.NewFactory(),
158+
yamlprovider.NewFactory(),
159+
},
160+
},
161+
},
162+
})
163+
require.NoError(t, err)
164+
165+
// Start collector
166+
go func() {
167+
assert.NoError(t, collector.Run(t.Context()))
168+
}()
169+
waitMetricsReady(t, metricsPort)
170+
171+
// Send some data through the pipeline to trigger internal telemetry
172+
err = sendTestTraces(otlphttpPort)
173+
require.NoError(t, err)
174+
175+
// Capture service.instance.id from the Prometheus endpoint
176+
var metricInstanceID string
177+
parsed := readMetrics(t, metricsPort)
178+
targetInfo := parsed["target_info"]
179+
require.NotNil(t, targetInfo, "target_info metric not found")
180+
require.Len(t, targetInfo.Metric, 1)
181+
for _, label := range targetInfo.Metric[0].Label {
182+
if label.GetName() == "service_instance_id" {
183+
metricInstanceID = label.GetValue()
184+
break
185+
}
186+
}
187+
tt.checkServiceInstanceID(t, metricInstanceID)
188+
189+
// Wait for traces, verify service.instance.id matches the one from metrics
190+
require.EventuallyWithT(t, func(t *assert.CollectT) {
191+
allTraces := traceSink.AllTraces()
192+
require.NotEmpty(t, allTraces)
193+
194+
// Find service.instance.id in resource attributes
195+
for _, td := range allTraces {
196+
for i := 0; i < td.ResourceSpans().Len(); i++ {
197+
rs := td.ResourceSpans().At(i)
198+
if attr, ok := rs.Resource().Attributes().Get("service.instance.id"); ok {
199+
traceInstanceID := attr.AsString()
200+
require.Equal(t, metricInstanceID, traceInstanceID)
201+
}
202+
}
203+
}
204+
}, 10*time.Second, 500*time.Millisecond)
205+
206+
// Check service.instance.id in logs matches the one from metrics
207+
var logsCount int
208+
logContent := logSink.Bytes()
209+
for line := range bytes.Lines(bytes.TrimSpace(logContent)) {
210+
var logEntry map[string]any
211+
if err := json.Unmarshal(line, &logEntry); err != nil {
212+
continue
213+
}
214+
// Check for resource field with service.instance.id
215+
// Resource attributes are nested under "resource" key as a dictionary
216+
resource, ok := logEntry["resource"].(map[string]any)
217+
require.True(t, ok, "log entry should have resource field")
218+
logInstanceID, ok := resource["service.instance.id"].(string)
219+
require.True(t, ok, "resource should have service.instance.id")
220+
require.Equal(t, metricInstanceID, logInstanceID)
221+
logsCount++
222+
}
223+
assert.NotZero(t, logsCount)
224+
})
225+
}
226+
}
227+
228+
// Test-specific zap sink to capture logs as close as possible to logs being written to file.
229+
// The reason we don't actually write to files is because Zap provides no way of closing file
230+
// sinks created by zap.Config.Build.
231+
232+
var (
233+
testSinksMu sync.Mutex
234+
testSinks = make(map[string]*testSink)
235+
)
236+
237+
type testSink struct {
238+
url string
239+
240+
mu sync.RWMutex
241+
buf bytes.Buffer
242+
}
243+
244+
func (s *testSink) Write(p []byte) (n int, err error) {
245+
s.mu.Lock()
246+
defer s.mu.Unlock()
247+
return s.buf.Write(p)
248+
}
249+
250+
func (s *testSink) Bytes() []byte {
251+
s.mu.RLock()
252+
defer s.mu.RUnlock()
253+
return slices.Clone(s.buf.Bytes())
254+
}
255+
256+
func (*testSink) Sync() error {
257+
return nil
258+
}
259+
260+
func (*testSink) Close() error {
261+
return nil
262+
}
263+
264+
func registerTestLogSink(tb testing.TB) *testSink {
265+
sink := &testSink{}
266+
sink.url = fmt.Sprintf("test://%s.%p", tb.Name(), sink)
267+
268+
testSinksMu.Lock()
269+
defer testSinksMu.Unlock()
270+
271+
testSinks[sink.url] = sink
272+
tb.Cleanup(func() {
273+
testSinksMu.Lock()
274+
defer testSinksMu.Unlock()
275+
delete(testSinks, sink.url)
276+
})
277+
return sink
278+
}
279+
280+
func init() {
281+
if err := zap.RegisterSink("test", func(u *url.URL) (zap.Sink, error) {
282+
testSinksMu.Lock()
283+
defer testSinksMu.Unlock()
284+
sink, ok := testSinks[u.String()]
285+
if !ok {
286+
return nil, fmt.Errorf("no test sink registered for URL %q", u.String())
287+
}
288+
return sink, nil
289+
}); err != nil {
290+
log.Fatal(err)
291+
}
292+
}

0 commit comments

Comments
 (0)