Skip to content

Commit ec2569c

Browse files
pyroscope: trace_id propagation and loging (#4354)
1 parent b512616 commit ec2569c

File tree

7 files changed

+239
-98
lines changed

7 files changed

+239
-98
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ Main (unreleased)
7070

7171
- Fix `pyroscope.write` component's `AppendIngest` method to respect configured timeout and implement retry logic. The method now properly uses the configured `remote_timeout`, includes retry logic with exponential backoff, and tracks metrics for sent/dropped bytes and profiles consistently with the `Append` method. (@korniltsev)
7272

73+
- `pyroscope.write`, `pyroscope.receive_http` components include `trace_id` in logs and propagate it downstream. (@korniltsev)
74+
7375
- Improve logging in `pyroscope.write` component. (@korniltsev)
7476

7577
- Add comprehensive latency metrics to `pyroscope.write` component with endpoint-specific tracking for both push and ingest operations. (@korniltsev, @claude)

internal/component/pyroscope/receive_http/receive_http.go

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111
"sync"
1212

1313
"connectrpc.com/connect"
14-
14+
"github.com/go-kit/log"
1515
"github.com/gorilla/mux"
16+
pyroutil "github.com/grafana/alloy/internal/component/pyroscope/util"
1617
"github.com/prometheus/client_golang/prometheus"
1718
"github.com/prometheus/prometheus/model/labels"
19+
"go.opentelemetry.io/otel/trace"
1820

1921
"github.com/grafana/alloy/internal/component"
2022
fnet "github.com/grafana/alloy/internal/component/common/net"
@@ -35,7 +37,8 @@ func init() {
3537
Stability: featuregate.StabilityGenerallyAvailable,
3638
Args: Arguments{},
3739
Build: func(opts component.Options, args component.Arguments) (component.Component, error) {
38-
return New(opts, args.(Arguments))
40+
tracer := opts.Tracer.Tracer("pyroscope.receive_http")
41+
return New(opts.Logger, tracer, opts.Registerer, args.(Arguments))
3942
},
4043
})
4144
}
@@ -54,20 +57,22 @@ func (a *Arguments) SetToDefault() {
5457
}
5558

5659
type Component struct {
57-
opts component.Options
5860
server *fnet.TargetServer
5961
serverConfig *fnet.HTTPConfig
6062
uncheckedCollector *util.UncheckedCollector
6163
appendables []pyroscope.Appendable
6264
mut sync.Mutex
65+
logger log.Logger
66+
tracer trace.Tracer
6367
}
6468

65-
func New(opts component.Options, args Arguments) (*Component, error) {
69+
func New(logger log.Logger, tracer trace.Tracer, reg prometheus.Registerer, args Arguments) (*Component, error) {
6670
uncheckedCollector := util.NewUncheckedCollector(nil)
67-
opts.Registerer.MustRegister(uncheckedCollector)
71+
reg.MustRegister(uncheckedCollector)
6872

6973
c := &Component{
70-
opts: opts,
74+
logger: logger,
75+
tracer: tracer,
7176
uncheckedCollector: uncheckedCollector,
7277
appendables: args.ForwardTo,
7378
}
@@ -87,7 +92,7 @@ func (c *Component) Run(ctx context.Context) error {
8792
}()
8893

8994
<-ctx.Done()
90-
level.Info(c.opts.Logger).Log("msg", "terminating due to context done")
95+
level.Info(c.logger).Log("msg", "terminating due to context done")
9196
return nil
9297
}
9398

@@ -122,7 +127,7 @@ func (c *Component) update(args component.Arguments) (bool, error) {
122127
serverRegistry := prometheus.NewRegistry()
123128
c.uncheckedCollector.SetCollector(serverRegistry)
124129

125-
srv, err := fnet.NewTargetServer(c.opts.Logger, "pyroscope_receive_http", serverRegistry, newArgs.Server)
130+
srv, err := fnet.NewTargetServer(c.logger, "pyroscope_receive_http", serverRegistry, newArgs.Server)
126131
if err != nil {
127132
return shutdown, fmt.Errorf("failed to create server: %w", err)
128133
}
@@ -163,6 +168,10 @@ func (c *Component) Push(ctx context.Context, req *connect.Request[pushv1.PushRe
163168

164169
appendables := c.getAppendables()
165170

171+
ctx, sp := c.tracer.Start(ctx, "/push.v1.PusherService/Push")
172+
defer sp.End()
173+
l := pyroutil.TraceLog(c.logger, sp)
174+
166175
var wg sync.WaitGroup
167176
var errs error
168177
var errorMut sync.Mutex
@@ -182,7 +191,7 @@ func (c *Component) Push(ctx context.Context, req *connect.Request[pushv1.PushRe
182191
lbls := ensureServiceName(lb.Labels())
183192
err := appendable.Append(ctx, lbls, apiToAlloySamples(req.Msg.Series[idx].Samples))
184193
if err != nil {
185-
util.ErrorsJoinConcurrent(
194+
pyroutil.ErrorsJoinConcurrent(
186195
&errs,
187196
fmt.Errorf("unable to append series %s to appendable %d: %w", lb.Labels().String(), i, err),
188197
&errorMut,
@@ -193,11 +202,10 @@ func (c *Component) Push(ctx context.Context, req *connect.Request[pushv1.PushRe
193202
}
194203
wg.Wait()
195204
if errs != nil {
196-
level.Error(c.opts.Logger).Log("msg", "Failed to forward profiles requests", "err", errs)
205+
level.Warn(l).Log("msg", "Failed to forward profiles requests", "err", errs)
197206
return nil, connect.NewError(connect.CodeInternal, errs)
198207
}
199208

200-
level.Debug(c.opts.Logger).Log("msg", "Profiles successfully forwarded")
201209
return connect.NewResponse(&pushv1.PushResponse{}), nil
202210
}
203211

@@ -211,12 +219,19 @@ func (c *Component) getAppendables() []pyroscope.Appendable {
211219
func (c *Component) handleIngest(w http.ResponseWriter, r *http.Request) {
212220
appendables := c.getAppendables()
213221

222+
ctx := r.Context()
223+
224+
ctx, sp := c.tracer.Start(ctx, "/ingest")
225+
defer sp.End()
226+
227+
l := pyroutil.TraceLog(c.logger, sp)
228+
214229
// Parse labels early
215230
var lbls labels.Labels
216231
if nameParam := r.URL.Query().Get("name"); nameParam != "" {
217232
ls, err := labelset.Parse(nameParam)
218233
if err != nil {
219-
level.Warn(c.opts.Logger).Log(
234+
level.Warn(l).Log(
220235
"msg", "Failed to parse labels from name parameter",
221236
"name", nameParam,
222237
"err", err,
@@ -229,7 +244,7 @@ func (c *Component) handleIngest(w http.ResponseWriter, r *http.Request) {
229244
}
230245
lbls = labels.New(labelPairs...)
231246
}
232-
}
247+
} // todo this is a required parameter, treat absence as error
233248

234249
// Ensure service_name label is set
235250
lbls = ensureServiceName(lbls)
@@ -239,7 +254,7 @@ func (c *Component) handleIngest(w http.ResponseWriter, r *http.Request) {
239254
// but means the entire profile will be held in memory
240255
var buf bytes.Buffer
241256
if _, err := io.Copy(&buf, r.Body); err != nil {
242-
level.Error(c.opts.Logger).Log("msg", "Failed to read request body", "err", err)
257+
level.Warn(l).Log("msg", "Failed to read request body", "err", err)
243258
w.WriteHeader(http.StatusInternalServerError)
244259
return
245260
}
@@ -260,23 +275,21 @@ func (c *Component) handleIngest(w http.ResponseWriter, r *http.Request) {
260275
Labels: lbls,
261276
}
262277

263-
if err := appendable.Appender().AppendIngest(r.Context(), profile); err != nil {
264-
level.Error(c.opts.Logger).Log("msg", "Failed to append profile", "appendable", i, "err", err)
265-
util.ErrorsJoinConcurrent(&errs, err, &errorMut)
278+
if err := appendable.Appender().AppendIngest(ctx, profile); err != nil {
279+
err = fmt.Errorf("failed to ingest profile to appendable %d: %w", i, err)
280+
pyroutil.ErrorsJoinConcurrent(&errs, err, &errorMut)
266281
}
267-
268-
level.Debug(c.opts.Logger).Log("msg", "Profile appended successfully", "appendable", i)
269282
}()
270283
}
271284

272285
wg.Wait()
273286

274287
if errs != nil {
288+
level.Warn(l).Log("msg", "Failed to ingest profiles", "err", errs)
275289
var writeErr *write.PyroscopeWriteError
276290
if errors.As(errs, &writeErr) {
277291
http.Error(w, http.StatusText(writeErr.StatusCode), writeErr.StatusCode)
278292
} else {
279-
level.Error(c.opts.Logger).Log("msg", "Failed to process request", "err", errs)
280293
http.Error(w, "Failed to process request", http.StatusInternalServerError)
281294
}
282295
return

internal/component/pyroscope/receive_http/receive_http_test.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import (
1313
"time"
1414

1515
"connectrpc.com/connect"
16+
"go.opentelemetry.io/otel/trace/noop"
1617

1718
"github.com/phayes/freeport"
1819
"github.com/prometheus/client_golang/prometheus"
1920
"github.com/prometheus/prometheus/model/labels"
2021
"github.com/stretchr/testify/require"
2122

22-
"github.com/grafana/alloy/internal/component"
2323
fnet "github.com/grafana/alloy/internal/component/common/net"
2424
"github.com/grafana/alloy/internal/component/pyroscope"
2525
"github.com/grafana/alloy/internal/util"
@@ -425,7 +425,12 @@ func startComponent(t *testing.T, appendables []pyroscope.Appendable) int {
425425
ForwardTo: appendables,
426426
}
427427

428-
comp, err := New(testOptions(t), args)
428+
comp, err := New(
429+
util.TestAlloyLogger(t),
430+
noop.Tracer{},
431+
prometheus.NewRegistry(),
432+
args,
433+
)
429434
require.NoError(t, err)
430435

431436
ctx, cancel := context.WithCancel(t.Context())
@@ -540,14 +545,6 @@ func (a *testAppender) AppendIngest(_ context.Context, profile *pyroscope.Incomi
540545
return a.appendErr
541546
}
542547

543-
func testOptions(t *testing.T) component.Options {
544-
return component.Options{
545-
ID: "pyroscope.receive_http.test",
546-
Logger: util.TestAlloyLogger(t),
547-
Registerer: prometheus.NewRegistry(),
548-
}
549-
}
550-
551548
// TestUpdateArgs verifies that the component can be updated with new arguments. This explicitly also makes sure that the server is restarted when the server configuration changes. And there are no metric registration conflicts.
552549
func TestUpdateArgs(t *testing.T) {
553550
ports, err := freeport.GetFreePorts(2)
@@ -565,7 +562,12 @@ func TestUpdateArgs(t *testing.T) {
565562
ForwardTo: forwardTo,
566563
}
567564

568-
comp, err := New(testOptions(t), args)
565+
comp, err := New(
566+
util.TestAlloyLogger(t),
567+
noop.Tracer{},
568+
prometheus.NewRegistry(),
569+
args,
570+
)
569571
require.NoError(t, err)
570572

571573
ctx, cancel := context.WithCancel(t.Context())
File renamed without changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package util
2+
3+
import (
4+
"github.com/go-kit/log"
5+
"go.opentelemetry.io/otel/trace"
6+
)
7+
8+
func TraceLog(l log.Logger, sp trace.Span) log.Logger {
9+
return log.With(l,
10+
"trace_id", sp.SpanContext().TraceID().String(),
11+
)
12+
}

0 commit comments

Comments
 (0)