Skip to content

Make yace embeddable in OTel Collectors - #1837

Closed
ArthurSens wants to merge 15 commits into
prometheus-community:masterfrom
ArthurSens:embed-in-collector
Closed

Make yace embeddable in OTel Collectors#1837
ArthurSens wants to merge 15 commits into
prometheus-community:masterfrom
ArthurSens:embed-in-collector

Conversation

@ArthurSens

@ArthurSens ArthurSens commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

This PR makes YACE embeddable in custom OTel Collector distributions through OCB. Implementing prometheus/proposals#69.

To facilitate this PR's review, I've opened prometheus/prometheus-opentelemetry-collector#4, which embeds YACE into a custom collector distribution. That repository's Makefile should contain everything needed to build and run the collector with YACE embedded.

Comment thread otelcollector/testdata/config.yml Outdated
@tristanburgess

Copy link
Copy Markdown
Contributor

Thanks for submitting this! This is really cool. I'll plan on taking a look at this tomorrow for a thorough review.

Comment thread pkg/exporter.go
Comment thread pkg/runtime_config.go Outdated
// See the License for the specific language governing permissions and
// limitations under the License.

package otelcollector

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about adding a godoc comment here or in a separate doc.go file as described here to convey that this package is experimental and subject to changes?

Comments on package declarations should provide general package documentation. These comments can be short, like the sort package’s brief description:

// Package sort provides primitives for sorting slices and user-defined
// collections.
package sort

They can also be detailed like the gob package’s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, doc.go, which contains only those comments and a package clause.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread otelcollector/go.mod
return nil, err
}

interval, err := cfg.awsScrapeInterval()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you think this parameter should play with https://github.com/prometheus/opentelemetry-collector-bridge/blob/5e92765d01563b43d343bc0054c2bc65c6bb6e8e/config.go#L32?

Typically users expect would expect a default AWS scrape interval of 5 minutes/300s. Can we set the default for the AWS scrape interval for this as well?

What should happen if someone sets the AWS scrape interval to less than the configured bridge scrape interval? I would think that's not a desirable situation as there could be data loss and wasted AWS calls so perhaps we should validate against it?

Comment thread otelcollector/factory.go
"go.opentelemetry.io/collector/receiver"
)

var receiverType = component.MustNewType("yace_exporter")

@tristanburgess tristanburgess Apr 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if you think the _exporter naming has a chance of confusing users with https://opentelemetry.io/docs/collector/components/exporter/? Should we do yace_receiver instead? Or perhaps just yace? Looking at https://opentelemetry.io/docs/collector/components/receiver/ it seems a lot of them are named in the config files as a single word.


registry := prometheus.NewRegistry()
for _, metric := range exporter.Metrics {
if err := registry.Register(metric); err != nil {

@tristanburgess tristanburgess Apr 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have to consider tightening the scope of the YACE-wide metrics that get collected about the run health

CloudwatchAPIErrorCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "yace_cloudwatch_request_errors",
Help: "Help is not implemented yet.",
}, []string{"api_name"})
CloudwatchAPICounter = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "yace_cloudwatch_requests_total",
Help: "Number of calls made to the CloudWatch APIs",
}, []string{"api_name"})
CloudwatchGetMetricDataAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_getmetricdata_requests_total",
Help: "DEPRECATED: replaced by yace_cloudwatch_requests_total with api_name label",
})
CloudwatchGetMetricDataAPIMetricsCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_getmetricdata_metrics_requested_total",
Help: "Number of metrics requested from the CloudWatch GetMetricData API which is how AWS bills",
})
CloudwatchGetMetricStatisticsAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_getmetricstatistics_requests_total",
Help: "DEPRECATED: replaced by yace_cloudwatch_requests_total with api_name label",
})
ResourceGroupTaggingAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_resourcegrouptaggingapi_requests_total",
Help: "Help is not implemented yet.",
})
AutoScalingAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_autoscalingapi_requests_total",
Help: "Help is not implemented yet.",
})
TargetGroupsAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_targetgroupapi_requests_total",
Help: "Help is not implemented yet.",
})
APIGatewayAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_apigatewayapi_requests_total",
})
APIGatewayAPIV2Counter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_apigatewayapiv2_requests_total",
})
Ec2APICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_ec2api_requests_total",
Help: "Help is not implemented yet.",
})
ShieldAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_shieldapi_requests_total",
Help: "Help is not implemented yet.",
})
ManagedPrometheusAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_managedprometheusapi_requests_total",
Help: "Help is not implemented yet.",
})
StoragegatewayAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_storagegatewayapi_requests_total",
Help: "Help is not implemented yet.",
})
DmsAPICounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_dmsapi_requests_total",
Help: "Help is not implemented yet.",
})
DuplicateMetricsFilteredCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "yace_cloudwatch_duplicate_metrics_filtered",
Help: "Help is not implemented yet.",
})

If I configure an OTel collector process with say

receivers:                                                                                                         
  yace/prod:                                                       
    config_file: /etc/yace/prod.yml                                                                                
    aws_scrape_interval: 300s                                                                                      
  yace/staging:                                                                                                    
    config_file: /etc/yace/staging.yml                                                                             
    aws_scrape_interval: 300s                                      
                                                                                                                      
exporters:                                                         
  otlphttp:                                          
    endpoint: https://otlp.example.com                                                                             
  
service:                                                                                                           
  pipelines:                                                       
    metrics:                                         
      receivers: [yace/prod, yace/staging]                
      exporters: [otlphttp]

Then the resulting metrics e.g.

Name: "yace_cloudwatch_requests_total",
are package-global counters. There would result two duplicate series of the sum of the two receivers counter values with no differentiation and thus I think not useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's so interesting! This sounds like the classic trade-off between Push vs Pull. If we used separate exporters for prod/staging, the Prometheus scrape process would add the job/instance labels, and that would be enough to differentiate the two. Now that we're enabling push, the scrape-related labels are being lost!

I think we need to solve this in the bridge library, since this will be a problem to all exporters that are being made embeddable 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread pkg/exporter.go
Comment thread otelcollector/config.go
if _, err := c.Options(); err != nil {
return err
}
_, err := c.jobsConfig(discardLogger())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this discards warning validation logs right? Such as this one. I think it makes sense to keep them for caller's FYI but open to thoughts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm, that's true... but all these functions are also being called during Start() and in Start() we are passing a real logger.

This made me think... why do we need to call those in Validate() and also during Start()? 🤔

Comment thread otelcollector/factory.go Outdated
func NewFactory() receiver.Factory {
return prombridge.NewFactory(
receiverType,
newLifecycleManager(slog.Default()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What actually sets the default logger in this case? Like I want to configure debug level YACE logging in the OTel receiver config, how does that look and does this actually build the receiver with the logger hooked back into the collector?

Comment thread otelcollector/factory.go Outdated
Comment thread otelcollector/lifecycle.go
Comment thread otelcollector/config.go Outdated
Comment thread .github/dependabot.yml
Comment on lines +17 to +20
groups:
aws-sdk-v2:
patterns:
- "github.com/aws/aws-sdk-go-v2*"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this applies in this module as well 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah AFAIK it just groups related updates together into one PR, instead of opening a PR for each individual package. Since go.mod includes many github.com/aws/aws-sdk-go-v2 packages it will help

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
@ArthurSens
ArthurSens marked this pull request as ready for review April 23, 2026 18:37
@ArthurSens

ArthurSens commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

prometheus/opentelemetry-collector-bridge#28 was merged to address the log wiring and fc15644 implements it

Comment thread otelcollector/go.mod
@@ -0,0 +1,100 @@
module github.com/prometheus-community/yet-another-cloudwatch-exporter/otelcollector

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this package. There is no good reason to have to maintain multiple Go modules within Prometheus repos.

@ArthurSens

Copy link
Copy Markdown
Contributor Author

I'm closing this PR since we have agreed on keeping the new module outside of this repo :)

@ArthurSens ArthurSens closed this May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants