Skip to content
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f1bc179
Initial Implementation of global knob for otlp_exporter
rafaelwestphal May 6, 2026
149c203
Fix Windows compilation and UAP plugin tests for health checks refact…
rafaelwestphal May 6, 2026
eeda003
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 6, 2026
6d204ed
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 13, 2026
32a833d
Update goldens for otlp_exporter_global_config after merging master
rafaelwestphal May 13, 2026
9e476c9
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 15, 2026
0f2ad89
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 15, 2026
bdd0aa4
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 16, 2026
3087f88
Update goldens for otlp_exporter_global_config after merging master
rafaelwestphal May 16, 2026
078dd45
Address PR #2292 code review comments
rafaelwestphal May 21, 2026
ef051c9
Fix healthcheck OTLP exporter check and decouple from experiments
rafaelwestphal May 21, 2026
c7fb0ed
Remove extra blank line from test-otlp-exporter-only/input.yaml
rafaelwestphal May 21, 2026
03c1060
Remove rogue empty lines from various test input.yaml files
rafaelwestphal May 21, 2026
7d6b575
Assimilate shared context keys into internal/experiments package
rafaelwestphal May 21, 2026
8901d4a
Simplify health check design and remove unnecessary context passing
rafaelwestphal May 22, 2026
e02348b
Revert redundant ContextWithExperiments calls and experiments.go styling
rafaelwestphal May 22, 2026
bd5ccef
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal May 22, 2026
a6801d6
Update goldens for otlp_exporter_global_config after merging master
rafaelwestphal May 22, 2026
9a3e332
Fix Windows compilation and test errors after health check refactoring
rafaelwestphal May 22, 2026
57751e2
Remove dead ContextWithExperiments call in service_windows.go Start
rafaelwestphal May 22, 2026
52fd3da
Decouple OTLP exporter from context and implement post-processing con…
rafaelwestphal May 25, 2026
6e2653e
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob a…
rafaelwestphal May 25, 2026
01aac52
Format imports in config.go to satisfy gci linter
rafaelwestphal May 25, 2026
d9915cd
Fix config.go formatting to satisfy golangci-lint (gci)
rafaelwestphal May 25, 2026
bd0ab0e
Merge master into westphalrafael/otlp_exporter_config_knob and resolv…
rafaelwestphal Jun 10, 2026
0cd6255
Fix non-constant format string errors in integration_test/agents
rafaelwestphal Jun 10, 2026
ab4dbac
Merge branch 'master' into westphalrafael/otlp_exporter_config_knob
rafaelwestphal Jun 10, 2026
4b7407c
otlp: switch to UTR endpoint (#2186)
ridwanmsharif Jun 11, 2026
9e50094
feat(healthchecks): add trace health check for OTLP endpoint (#2381)
rafaelwestphal Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions cmd/google_cloud_ops_agent_engine/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ var (
healthChecks = flag.Bool("healthchecks", false, "run health checks and exit")
)

func runHealthChecks() {
func runHealthChecks(otlpExporterEnabled bool) {
logger := healthchecks.CreateHealthChecksLogger(*logsDir)

defaultLogger := logs.NewSimpleLogger()

healthCheckResults := healthchecks.HealthCheckRegistryFactory().RunAllHealthChecks(logger)
healthCheckResults := healthchecks.HealthCheckRegistryFactory(otlpExporterEnabled).RunAllHealthChecks(logger)
healthchecks.LogHealthCheckResults(healthCheckResults, defaultLogger)
}

Expand All @@ -59,7 +59,6 @@ func run() error {
if err != nil {
return err
}

// Log the built-in and merged config files to STDOUT. These are then written
// by journald to var/log/syslog and so to Cloud Logging once the ops-agent is
// running.
Expand All @@ -68,7 +67,7 @@ func run() error {

switch *service {
case "":
runHealthChecks()
runHealthChecks(uc.Global.GetOtlpExporter())
log.Println("Startup checks finished")
if *healthChecks {
// If healthchecks is set, stop here
Expand Down
19 changes: 10 additions & 9 deletions cmd/ops_agent_uap_plugin/service_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ func (ps *OpsAgentPluginServer) Start(ctx context.Context, msg *pb.StartRequest)
}

// Subagents config validation and generation.
if err := generateSubAgentConfigs(ctx, OpsAgentConfigLocationWindows, pluginStateDir); err != nil {
uc, err := generateSubAgentConfigs(ctx, OpsAgentConfigLocationWindows, pluginStateDir)
if err != nil {
ps.cancelAndSetPluginError(&OpsAgentPluginError{
Message: fmt.Sprintf("Start() failed to validate the custom Ops Agent config, and generate sub-agents config: %s", err),
ShouldRestart: false,
Expand All @@ -122,7 +123,7 @@ func (ps *OpsAgentPluginServer) Start(ctx context.Context, msg *pb.StartRequest)

// Trigger Healthchecks.
healthCheckFileLogger := healthchecks.CreateHealthChecksLogger(filepath.Join(pluginStateDir, LogsDirectory))
runHealthChecks(healthCheckFileLogger)
runHealthChecks(healthCheckFileLogger, uc.Global.GetOtlpExporter())

// Create a Windows Job object and stores its handle, to ensure that all child processes are killed when the parent process exits.
_, err = createWindowsJobHandle()
Expand Down Expand Up @@ -206,18 +207,18 @@ func findPreExistentAgents(mgr serviceManager, agentWindowsServiceNames []string
return alreadyInstalledAgentServiceNames, nil
}

func generateSubAgentConfigs(ctx context.Context, userConfigPath string, pluginStateDir string) error {
func generateSubAgentConfigs(ctx context.Context, userConfigPath string, pluginStateDir string) (*confgenerator.UnifiedConfig, error) {
uc, err := confgenerator.MergeConfFiles(ctx, userConfigPath)
if err != nil {
return err
return nil, err
}

log.Printf("Built-in config:\n%s\n", confgenerator.BuiltInConfStructs["windows"])
log.Printf("Merged config:\n%s\n", uc)

// The generated otlp metric json files are used only by the otel service.
if err = self_metrics.GenerateOpsAgentSelfMetricsOTLPJSON(ctx, userConfigPath, filepath.Join(pluginStateDir, GeneratedConfigsOutDir, "otel")); err != nil {
return err
return nil, err
}

for _, subagent := range []string{
Expand All @@ -230,14 +231,14 @@ func generateSubAgentConfigs(ctx context.Context, userConfigPath string, pluginS
filepath.Join(pluginStateDir, LogsDirectory),
filepath.Join(pluginStateDir, RuntimeDirectory),
filepath.Join(pluginStateDir, GeneratedConfigsOutDir, subagent)); err != nil {
return err
return nil, err
}
}
return nil
return uc, nil
}

func runHealthChecks(healthCheckFileLogger logs.StructuredLogger) {
gceHealthChecks := healthchecks.HealthCheckRegistryFactory()
func runHealthChecks(healthCheckFileLogger logs.StructuredLogger, otlpExporterEnabled bool) {
gceHealthChecks := healthchecks.HealthCheckRegistryFactory(otlpExporterEnabled)

// Log health check results to health-checks.log log file.
gceHealthChecks.RunAllHealthChecks(healthCheckFileLogger)
Expand Down
4 changes: 2 additions & 2 deletions cmd/ops_agent_uap_plugin/service_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func Test_runHealthChecks_LogFileNonEmpty(t *testing.T) {
defer os.Remove(healthCheckLogFile.Name())
mockHealthCheckLogger := &mockHealthCheckLogger{logFile: healthCheckLogFile}

runHealthChecks(mockHealthCheckLogger)
runHealthChecks(mockHealthCheckLogger, false)

// Check if the log file has content
fileInfo, err := os.Stat(healthCheckLogFile.Name())
Expand Down Expand Up @@ -204,7 +204,7 @@ func Test_generateSubAgentConfigs(t *testing.T) {
}
userConfigFile.Close()

err = generateSubAgentConfigs(ctx, userConfigFile.Name(), tc.pluginStateDir)
_, err = generateSubAgentConfigs(ctx, userConfigFile.Name(), tc.pluginStateDir)
if (err != nil) != tc.wantError {
t.Errorf("generateSubAgentConfigs() returned error: %v, want error: %v", err, tc.wantError)
}
Expand Down
17 changes: 16 additions & 1 deletion cmd/ops_agent_windows/main_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"path/filepath"

"github.com/GoogleCloudPlatform/ops-agent/confgenerator"
"github.com/GoogleCloudPlatform/ops-agent/internal/healthchecks"
"github.com/GoogleCloudPlatform/ops-agent/internal/logs"
"github.com/kardianos/osext"
Expand Down Expand Up @@ -63,7 +65,20 @@ func main() {
}
infoLog.Printf("uninstalled services")
} else if *healthChecks {
healthCheckResults := getHealthCheckResults()
ctx := context.Background()
base, err := osext.ExecutableFolder()
if err != nil {
log.Fatalf("failed to determine executable folder: %v", err)
}
configPath := filepath.Join(base, "../config/config.yaml")
otlpExporterEnabled := false
uc, err := confgenerator.MergeConfFiles(ctx, configPath)
if err == nil {
otlpExporterEnabled = uc.Global.GetOtlpExporter()
} else {
log.Printf("failed to load config: %v", err)
}
healthCheckResults := getHealthCheckResults(otlpExporterEnabled)
healthchecks.LogHealthCheckResults(healthCheckResults, infoLog)
infoLog.Println("Health checks finished")
} else {
Expand Down
8 changes: 5 additions & 3 deletions cmd/ops_agent_windows/run_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type service struct {
log debug.Log
userConf string
outDirectory string
uc *confgenerator.UnifiedConfig
}

func (s *service) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
Expand Down Expand Up @@ -141,16 +142,16 @@ func (s *service) checkForStandaloneAgents(unified *confgenerator.UnifiedConfig)
return nil
}

func getHealthCheckResults() []healthchecks.HealthCheckResult {
func getHealthCheckResults(otlpExporterEnabled bool) []healthchecks.HealthCheckResult {
logsDir := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, "log")
gceHealthChecks := healthchecks.HealthCheckRegistryFactory()
gceHealthChecks := healthchecks.HealthCheckRegistryFactory(otlpExporterEnabled)
logger := healthchecks.CreateHealthChecksLogger(logsDir)

return gceHealthChecks.RunAllHealthChecks(logger)
}

func (srv *service) runHealthChecks() {
healthCheckResults := getHealthCheckResults()
healthCheckResults := getHealthCheckResults(srv.uc.Global.GetOtlpExporter())
logger := logs.WindowsServiceLogger{EventID: EngineEventID, Logger: srv.log}
healthchecks.LogHealthCheckResults(healthCheckResults, logger)
srv.log.Info(EngineEventID, "Startup checks finished")
Expand All @@ -162,6 +163,7 @@ func (s *service) generateConfigs(ctx context.Context) error {
if err != nil {
return err
}
s.uc = uc

s.log.Info(EngineEventID, fmt.Sprintf("Built-in config:\n%s\n", confgenerator.BuiltInConfStructs["windows"]))
s.log.Info(EngineEventID, fmt.Sprintf("Merged config:\n%s\n", uc))
Expand Down
6 changes: 3 additions & 3 deletions confgenerator/agentmetrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (

"github.com/GoogleCloudPlatform/ops-agent/confgenerator/otel"
"github.com/GoogleCloudPlatform/ops-agent/confgenerator/otel/ottl"
"github.com/GoogleCloudPlatform/ops-agent/internal/experiments"
)

// AgentSelfMetrics provides the agent.googleapis.com/agent/ metrics.
Expand All @@ -34,6 +33,7 @@ type AgentSelfMetrics struct {
FluentBitPort int
OtelPort int
OtelRuntimeDir string
OtlpExporterEnabled bool
}

// Following reference : https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
Expand Down Expand Up @@ -182,7 +182,7 @@ func (r AgentSelfMetrics) OtelPipelineProcessors(ctx context.Context) []otel.Com
`metric.name == "` + durationCountMetric + `" and (not IsMatch(datapoint.attributes["grpc.target"], "monitoring.googleapis"))`,
})

expOtlpExporter := experiments.FromContext(ctx)["otlp_exporter"]
expOtlpExporter := r.OtlpExporterEnabled
var extraTransforms []map[string]interface{}
if expOtlpExporter {
durationMetric = "rpc.client.call.duration"
Expand Down Expand Up @@ -293,7 +293,7 @@ func (r AgentSelfMetrics) LoggingMetricsPipelineProcessors(ctx context.Context)
otel.AggregateLabels("sum", "response_code"),
)

expOtlpExporter := experiments.FromContext(ctx)["otlp_exporter"]
expOtlpExporter := r.OtlpExporterEnabled
if expOtlpExporter {
durationMetric = "rpc.client.call.duration"
durationCountMetric = "rpc.client.call.duration_count"
Expand Down
54 changes: 33 additions & 21 deletions confgenerator/confgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/GoogleCloudPlatform/ops-agent/confgenerator/fluentbit"
"github.com/GoogleCloudPlatform/ops-agent/confgenerator/otel"
"github.com/GoogleCloudPlatform/ops-agent/confgenerator/resourcedetector"
"github.com/GoogleCloudPlatform/ops-agent/internal/experiments"
"github.com/GoogleCloudPlatform/ops-agent/internal/platform"
)

Expand Down Expand Up @@ -87,39 +86,45 @@ func googleCloudLoggingExporter() otel.Component {
}

func ConvertPrometheusExporterToOtlpExporter(pipeline otel.ReceiverPipeline, ctx context.Context) otel.ReceiverPipeline {
return ConvertToOtlpExporter(pipeline, ctx, true, false)
return pipeline
}

func ConvertGCMOtelExporterToOtlpExporter(pipeline otel.ReceiverPipeline, ctx context.Context) otel.ReceiverPipeline {
return ConvertToOtlpExporter(pipeline, ctx, false, false)
return pipeline
}

func ConvertGCMSystemExporterToOtlpExporter(pipeline otel.ReceiverPipeline, ctx context.Context) otel.ReceiverPipeline {
return ConvertToOtlpExporter(pipeline, ctx, false, true)
return pipeline
}

func ConvertToOtlpExporter(pipeline otel.ReceiverPipeline, ctx context.Context, isPrometheus bool, isSystem bool) otel.ReceiverPipeline {
expOtlpExporter := experiments.FromContext(ctx)["otlp_exporter"]
if !expOtlpExporter {
return pipeline
}

if _, ok := pipeline.ExporterTypes["metrics"]; ok {
if isPrometheus {
pipeline.ExporterTypes["metrics"] = otel.GMP
} else {
pipeline.ExporterTypes["metrics"] = otel.OTLP_Metrics
if isSystem {
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"], otel.MetricsRemoveInstrumentationLibraryLabelsAttributes())
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"], otel.MetricsRemoveServiceAttributes())
return pipeline
}

func ConvertPipelinesToOtlp(receiverPipelines map[string]otel.ReceiverPipeline) {
for rID, pipeline := range receiverPipelines {
if _, ok := pipeline.ExporterTypes["metrics"]; ok {
if pipeline.ExporterTypes["metrics"] == otel.System {
pipeline.ExporterTypes["metrics"] = otel.OTLP_Metrics
if pipeline.Processors == nil {
pipeline.Processors = make(map[string][]otel.Component)
}
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"],
otel.MetricsRemoveInstrumentationLibraryLabelsAttributes(),
otel.MetricsRemoveServiceAttributes(),
)
} else if pipeline.ExporterTypes["metrics"] == otel.OTel {
pipeline.ExporterTypes["metrics"] = otel.OTLP_Metrics
}
}
}

if _, ok := pipeline.ExporterTypes["logs"]; ok {
pipeline.ExporterTypes["logs"] = otel.OTLP_Logs
if _, ok := pipeline.ExporterTypes["logs"]; ok {
if pipeline.ExporterTypes["logs"] == otel.Logging {

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 dont think this if clause is necessary here. All Otel logging pipielines use otel.Logging as the exporter.

pipeline.ExporterTypes["logs"] = otel.OTLP_Logs
}
}
receiverPipelines[rID] = pipeline
}
return pipeline
}

func otlpExporterForMetrics(userAgent string) otel.Component {
Expand Down Expand Up @@ -208,6 +213,7 @@ func fileStorageExtension(stateDir string) otel.Component {

func (uc *UnifiedConfig) GenerateOtelConfig(ctx context.Context, outDir, stateDir string) (string, error) {
p := platform.FromContext(ctx)

userAgent, _ := p.UserAgent("Google-Cloud-Ops-Agent-Metrics")
metricVersionLabel, _ := p.VersionLabel("google-cloud-ops-agent-metrics")
loggingVersionLabel, _ := p.VersionLabel("google-cloud-ops-agent-logging")
Expand All @@ -223,8 +229,13 @@ func (uc *UnifiedConfig) GenerateOtelConfig(ctx context.Context, outDir, stateDi
FluentBitPort: int(uc.GetFluentBitMetricsPort()),
OtelPort: int(uc.GetOtelMetricsPort()),
OtelRuntimeDir: outDir,
OtlpExporterEnabled: uc.Global.GetOtlpExporter(),
}
agentSelfMetrics.AddSelfMetricsPipelines(receiverPipelines, pipelines, ctx)

if uc.Global.GetOtlpExporter() {
ConvertPipelinesToOtlp(receiverPipelines)
}
resource, err := p.GetResource()
if err != nil {
return "", err
Expand Down Expand Up @@ -486,6 +497,7 @@ func (uc *UnifiedConfig) generateOtelPipelines(ctx context.Context) (map[string]
// It returns a map of filenames to file contents.
func (uc *UnifiedConfig) GenerateFluentBitConfigs(ctx context.Context, logsDir string, stateDir string) (map[string]string, error) {
userAgent, _ := platform.FromContext(ctx).UserAgent("Google-Cloud-Ops-Agent-Logging")

components, err := uc.generateFluentbitComponents(ctx, userAgent)
if err != nil {
return nil, err
Expand Down
9 changes: 7 additions & 2 deletions confgenerator/confgenerator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,7 @@ func generateConfigs(pc platformConfig, testDir string) (got map[string]string,

func generateOtelConfigWithOtlpExporterEnabled(got map[string]string, pc platformConfig, testDir string, otelGeneratedConfig string) {
experimentsOtlp := map[string]bool{
"otlp_exporter": true,
"otel_logging": true,
"otel_logging": true,
}
ctxOtlp := experiments.ContextWithExperiments(pc.platform.TestContext(context.Background()), experimentsOtlp)

Expand All @@ -353,6 +352,12 @@ func generateOtelConfigWithOtlpExporterEnabled(got map[string]string, pc platfor
filepath.Join("testdata", testDir, inputFileName),
)
if err == nil {
if mergedUcOtlp.Global == nil {
mergedUcOtlp.Global = &confgenerator.Global{}
}
enabled := true
mergedUcOtlp.Global.OtlpExporter = &enabled

otelGeneratedConfigOtlp, err := mergedUcOtlp.GenerateOtelConfig(ctxOtlp, "", "")
if err == nil {
got["otel_otlp_exporter.yaml"] = otelGeneratedConfigOtlp
Expand Down
1 change: 1 addition & 0 deletions confgenerator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ type TracesService struct {

func (uc *UnifiedConfig) Validate(ctx context.Context) error {
if uc.Logging != nil {

if err := uc.ValidateLogging(); err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions confgenerator/config_global.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ package confgenerator
type Global struct {
DefaultSelfLogFileCollection *bool `yaml:"default_self_log_file_collection,omitempty"`
DefaultLogFileRotation *LogFileRotation `yaml:"default_self_log_file_rotation,omitempty"`
OtlpExporter *bool `yaml:"otlp_exporter,omitempty"`
}

func (g *Global) GetOtlpExporter() bool {
if g != nil && g.OtlpExporter != nil {
return *g.OtlpExporter
}
return false
}

// Get whether self log collection should be enabled. Defaults to true if unset.
Expand Down
9 changes: 4 additions & 5 deletions confgenerator/feature_tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"sort"
"strconv"
"strings"

"github.com/GoogleCloudPlatform/ops-agent/internal/experiments"
)

var (
Expand Down Expand Up @@ -76,9 +74,10 @@ type CustomFeatures interface {
// tag will be used instead of value from UnifiedConfig.
func ExtractFeatures(ctx context.Context, userUc, mergedUc *UnifiedConfig) ([]Feature, error) {
allFeatures := getOverriddenDefaultPipelines(userUc)

allFeatures = append(allFeatures, getSelfLogCollection(userUc))
allFeatures = append(allFeatures, getOTelLoggingSupportedConfig(ctx, mergedUc))
allFeatures = append(allFeatures, getOtlpExporterExperimentConfig(ctx)...)
allFeatures = append(allFeatures, getOtlpExporterFeatureConfig(mergedUc)...)

var err error
var tempTrackedFeatures []Feature
Expand Down Expand Up @@ -506,9 +505,9 @@ func getSelfLogCollection(uc *UnifiedConfig) Feature {
return feature
}

func getOtlpExporterExperimentConfig(ctx context.Context) []Feature {
func getOtlpExporterFeatureConfig(uc *UnifiedConfig) []Feature {
featureEnabled := "false"
if experiments.FromContext(ctx)["otlp_exporter"] {
if uc.Global.GetOtlpExporter() {
featureEnabled = "true"
}

Expand Down
Loading
Loading