1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package obsreport
16
17import (
18	"context"
19
20	"go.opencensus.io/stats"
21	"go.opencensus.io/tag"
22	"go.opentelemetry.io/otel"
23	"go.opentelemetry.io/otel/attribute"
24	"go.opentelemetry.io/otel/trace"
25
26	"go.opentelemetry.io/collector/config"
27	"go.opentelemetry.io/collector/config/configtelemetry"
28	"go.opentelemetry.io/collector/internal/obsreportconfig"
29	"go.opentelemetry.io/collector/internal/obsreportconfig/obsmetrics"
30	"go.opentelemetry.io/collector/receiver/scrapererror"
31)
32
33// ScraperContext adds the keys used when recording observability metrics to
34// the given context returning the newly created context. This context should
35// be used in related calls to the obsreport functions so metrics are properly
36// recorded.
37func ScraperContext(
38	ctx context.Context,
39	receiverID config.ComponentID,
40	scraper config.ComponentID,
41) context.Context {
42	ctx, _ = tag.New(
43		ctx,
44		tag.Upsert(obsmetrics.TagKeyReceiver, receiverID.String(), tag.WithTTL(tag.TTLNoPropagation)),
45		tag.Upsert(obsmetrics.TagKeyScraper, scraper.String(), tag.WithTTL(tag.TTLNoPropagation)))
46
47	return ctx
48}
49
50// Scraper is a helper to add observability to a component.Scraper.
51type Scraper struct {
52	receiverID config.ComponentID
53	scraper    config.ComponentID
54	tracer     trace.Tracer
55}
56
57// ScraperSettings are settings for creating a Scraper.
58type ScraperSettings struct {
59	ReceiverID config.ComponentID
60	Scraper    config.ComponentID
61}
62
63// NewScraper creates a new Scraper.
64func NewScraper(cfg ScraperSettings) *Scraper {
65	return &Scraper{
66		receiverID: cfg.ReceiverID,
67		scraper:    cfg.Scraper,
68		tracer:     otel.GetTracerProvider().Tracer(cfg.Scraper.String()),
69	}
70}
71
72// StartMetricsOp is called when a scrape operation is started. The
73// returned context should be used in other calls to the obsreport functions
74// dealing with the same scrape operation.
75func (s *Scraper) StartMetricsOp(
76	scraperCtx context.Context,
77) context.Context {
78	spanName := obsmetrics.ScraperPrefix + s.receiverID.String() + obsmetrics.NameSep + s.scraper.String() + obsmetrics.ScraperMetricsOperationSuffix
79	ctx, _ := s.tracer.Start(scraperCtx, spanName)
80	return ctx
81}
82
83// EndMetricsOp completes the scrape operation that was started with
84// StartMetricsOp.
85func (s *Scraper) EndMetricsOp(
86	scraperCtx context.Context,
87	numScrapedMetrics int,
88	err error,
89) {
90	numErroredMetrics := 0
91	if err != nil {
92		if partialErr, isPartial := err.(scrapererror.PartialScrapeError); isPartial {
93			numErroredMetrics = partialErr.Failed
94		} else {
95			numErroredMetrics = numScrapedMetrics
96			numScrapedMetrics = 0
97		}
98	}
99
100	span := trace.SpanFromContext(scraperCtx)
101
102	if obsreportconfig.Level != configtelemetry.LevelNone {
103		stats.Record(
104			scraperCtx,
105			obsmetrics.ScraperScrapedMetricPoints.M(int64(numScrapedMetrics)),
106			obsmetrics.ScraperErroredMetricPoints.M(int64(numErroredMetrics)))
107	}
108
109	// end span according to errors
110	if span.IsRecording() {
111		span.SetAttributes(
112			attribute.String(obsmetrics.FormatKey, string(config.MetricsDataType)),
113			attribute.Int64(obsmetrics.ScrapedMetricPointsKey, int64(numScrapedMetrics)),
114			attribute.Int64(obsmetrics.ErroredMetricPointsKey, int64(numErroredMetrics)),
115		)
116		recordError(span, err)
117	}
118
119	span.End()
120}
121