mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-11 10:23:17 +08:00
atecontroller had no OTel at all. Dev-mode zap logger, and its controller-runtime metrics were only available on a :8080 that we don't scrape. After this PR, logs go through the shared slog handler (plus a `--log-level` flag to match the other binaries), and controller-runtime's Prometheus registry is bridged onto the OTLP reader so the reconcile/workqueue metrics actually reach the collector. Filtering these out is a pipeline responsibility. Also added otelgrpc to the ateapi client, which was untraced. This unblocks #564 the workperpool metrics, cc @Angelawork, @JeffLuoo: there's a working `MeterProvider` to use for `ate.workerpool.desired_workers`/`ready_workers`. Couple of things to mention for review: -`InitMetricsPushOnly`, not `InitMetrics`, even though we do serve :8080. That port is controller-runtime's own private registry, not the global one `serverboot.metricsMux` serves, so a pull reader there would collect into something we never expose. - Bridge is pinned to v0.68.0 to match otelgrpc. Wanted to go to v0.70.0, but that requires otel/sdk/metric 1.45.0 and pulls the whole SDK up with it (406 vendor files instead of 81). Happy to do that bump separately. - zap/zapr fall out of go.mod since atecontroller was the last importer. - I included an OTel collector image bump from the early 2024 (!) one to latest, which was breaking exposing native histograms - [x] Tests pass - [x] Appropriate changes to documentation are included in the PR
81 lines
2.5 KiB
Go
81 lines
2.5 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
prombridge "go.opentelemetry.io/contrib/bridges/prometheus"
|
|
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
|
)
|
|
|
|
func TestNewControllerRuntimeLoggerVerbosity(t *testing.T) {
|
|
const probe = "verbosity-probe"
|
|
|
|
tests := []struct {
|
|
name string
|
|
level slog.Level
|
|
verbosity int
|
|
wantLog bool
|
|
}{
|
|
{name: "info keeps V(0)", level: slog.LevelInfo, verbosity: 0, wantLog: true},
|
|
{name: "info drops V(1)", level: slog.LevelInfo, verbosity: 1, wantLog: false},
|
|
{name: "debug keeps V(1)", level: slog.LevelDebug, verbosity: 1, wantLog: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
var buf strings.Builder
|
|
log := newControllerRuntimeLogger(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: tt.level}))
|
|
|
|
log.V(tt.verbosity).Info(probe)
|
|
|
|
if got := strings.Contains(buf.String(), probe); got != tt.wantLog {
|
|
t.Errorf("logged = %v, want %v (output %q)", got, tt.wantLog, buf.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The e2e read-back asserts atecontroller reaches the collector, which holds only
|
|
// because the bridged registry already has series before any manager starts: the
|
|
// controller_runtime_* vectors are empty until controllers register, so the Go and
|
|
// process collectors are what make the first push non-empty.
|
|
func TestBridgedRegistryProducesBeforeManagerStart(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
produced, err := prombridge.NewMetricProducer(prombridge.WithGatherer(ctrlmetrics.Registry)).
|
|
Produce(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Produce: %v", err)
|
|
}
|
|
|
|
var names []string
|
|
for _, sm := range produced {
|
|
for _, m := range sm.Metrics {
|
|
names = append(names, m.Name)
|
|
}
|
|
}
|
|
if len(names) == 0 {
|
|
t.Fatal("bridging controller-runtime's registry produced no metrics")
|
|
}
|
|
if !slices.ContainsFunc(names, func(n string) bool { return strings.HasPrefix(n, "go_") }) {
|
|
t.Errorf("no go_* family in %v", names)
|
|
}
|
|
}
|