Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
8 changes: 5 additions & 3 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(ctx context.Context) {
logger := healthchecks.CreateHealthChecksLogger(*logsDir)

defaultLogger := logs.NewSimpleLogger()

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

Expand All @@ -60,6 +60,8 @@ func run() error {
return err
}

ctx = uc.ContextWithExperiments(ctx)

// 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 +70,7 @@ func run() error {

switch *service {
case "":
runHealthChecks()
runHealthChecks(ctx)
log.Println("Startup checks finished")
if *healthChecks {
// If healthchecks is set, stop here
Expand Down
21 changes: 12 additions & 9 deletions cmd/ops_agent_uap_plugin/service_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,20 @@ 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,
})
return &pb.StartResponse{}, nil
}

ctx = uc.ContextWithExperiments(ctx)

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

// 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 +209,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 +233,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(ctx context.Context, healthCheckFileLogger logs.StructuredLogger) {
gceHealthChecks := healthchecks.HealthCheckRegistryFactory(ctx)

// Log health check results to health-checks.log log file.
gceHealthChecks.RunAllHealthChecks(healthCheckFileLogger)
Expand Down
5 changes: 3 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,8 @@ func Test_runHealthChecks_LogFileNonEmpty(t *testing.T) {
defer os.Remove(healthCheckLogFile.Name())
mockHealthCheckLogger := &mockHealthCheckLogger{logFile: healthCheckLogFile}

runHealthChecks(mockHealthCheckLogger)
ctx := context.Background()
runHealthChecks(ctx, mockHealthCheckLogger)

// Check if the log file has content
fileInfo, err := os.Stat(healthCheckLogFile.Name())
Expand Down Expand Up @@ -204,7 +205,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
16 changes: 15 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,19 @@ 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")
uc, err := confgenerator.MergeConfFiles(ctx, configPath)
if err == nil {
ctx = uc.ContextWithExperiments(ctx)
} else {
log.Printf("failed to load config (using default experiments): %v", err)
}
healthCheckResults := getHealthCheckResults(ctx)
healthchecks.LogHealthCheckResults(healthCheckResults, infoLog)
infoLog.Println("Health checks finished")
} else {
Expand Down
13 changes: 8 additions & 5 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 All @@ -75,7 +76,8 @@ func (s *service) Execute(args []string, r <-chan svc.ChangeRequest, changes cha
return false, 2
}
s.log.Info(EngineEventID, "generated configuration files")
s.runHealthChecks()
ctx = s.uc.ContextWithExperiments(ctx)
s.runHealthChecks(ctx)

changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
if err := s.startSubagents(); err != nil {
Expand Down Expand Up @@ -141,16 +143,16 @@ func (s *service) checkForStandaloneAgents(unified *confgenerator.UnifiedConfig)
return nil
}

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

return gceHealthChecks.RunAllHealthChecks(logger)
}

func (srv *service) runHealthChecks() {
healthCheckResults := getHealthCheckResults()
func (srv *service) runHealthChecks(ctx context.Context) {
healthCheckResults := getHealthCheckResults(ctx)
logger := logs.WindowsServiceLogger{EventID: EngineEventID, Logger: srv.log}
healthchecks.LogHealthCheckResults(healthCheckResults, logger)
srv.log.Info(EngineEventID, "Startup checks finished")
Expand All @@ -162,6 +164,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
4 changes: 4 additions & 0 deletions confgenerator/confgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ func fileStorageExtension(stateDir string) otel.Component {
}

func (uc *UnifiedConfig) GenerateOtelConfig(ctx context.Context, outDir, stateDir string) (string, error) {
ctx = uc.ContextWithExperiments(ctx)
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 Down Expand Up @@ -495,7 +497,9 @@ func (uc *UnifiedConfig) generateOtelPipelines(ctx context.Context) (map[string]
// GenerateFluentBitConfigs generates configuration file(s) for Fluent Bit.
// It returns a map of filenames to file contents.
func (uc *UnifiedConfig) GenerateFluentBitConfigs(ctx context.Context, logsDir string, stateDir string) (map[string]string, error) {
ctx = uc.ContextWithExperiments(ctx)
userAgent, _ := platform.FromContext(ctx).UserAgent("Google-Cloud-Ops-Agent-Logging")

components, err := uc.generateFluentbitComponents(ctx, userAgent)
if err != nil {
return nil, err
Expand Down
17 changes: 17 additions & 0 deletions confgenerator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ func (uc *UnifiedConfig) HasCombined() bool {
return uc.Combined != nil
}

func (uc *UnifiedConfig) ContextWithExperiments(ctx context.Context) context.Context {
if uc == nil {
return ctx
}
enabledExperiments := experiments.FromContext(ctx)
newExperiments := map[string]bool{}
for k, v := range enabledExperiments {
newExperiments[k] = v
}
if uc.Global.GetOtlpExporter() {

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.

the global config knob should replace the experimental flag.

We should remove any references to the otlp_exporter experimental flag in the confgenerator. Instead, we should rely on verifying whether the global configuration OtlpExporter is set.

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.

Decoupled the new config knob with any experiment relation. IT should now only use the config

newExperiments["otlp_exporter"] = true
}
return experiments.ContextWithExperiments(ctx, newExperiments)
}

const (
ExperimentalFluentBitMetricsPortEnv = "EXPERIMENTAL_OPS_AGENT_FLUENT_BIT_METRICS_PORT"
ExperimentalOtelMetricsPortEnv = "EXPERIMENTAL_OPS_AGENT_OTEL_METRICS_PORT"
Expand Down Expand Up @@ -875,7 +890,9 @@ type TracesService struct {
}

func (uc *UnifiedConfig) Validate(ctx context.Context) error {
ctx = uc.ContextWithExperiments(ctx)
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
3 changes: 3 additions & 0 deletions confgenerator/confmerger.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ func MergeConfFiles(ctx context.Context, userConfPath string) (*UnifiedConfig, e
mergeConfigs(result, overrides)
}

ctx = result.ContextWithExperiments(ctx)

if err := result.Validate(ctx); err != nil {
return nil, err
}

// Ensure the merged config struct fields are valid.
v := newValidator()
if err := v.StructCtx(ctx, result); err != nil {

log.Fatalf("merged config failed to validate: %v", err)
}
return result, nil
Expand Down
2 changes: 2 additions & 0 deletions confgenerator/feature_tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ type CustomFeatures interface {
// Automatic collection of bool or int fields. Any value that exists on tracking
// tag will be used instead of value from UnifiedConfig.
func ExtractFeatures(ctx context.Context, userUc, mergedUc *UnifiedConfig) ([]Feature, error) {
ctx = mergedUc.ContextWithExperiments(ctx)
allFeatures := getOverriddenDefaultPipelines(userUc)

allFeatures = append(allFeatures, getSelfLogCollection(userUc))
allFeatures = append(allFeatures, getOTelLoggingSupportedConfig(ctx, mergedUc))
allFeatures = append(allFeatures, getOtlpExporterExperimentConfig(ctx)...)
Expand Down
2 changes: 2 additions & 0 deletions confgenerator/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ func ReadUnifiedConfigFromFile(ctx context.Context, path string) (*UnifiedConfig
}

func (uc *UnifiedConfig) GenerateFilesFromConfig(ctx context.Context, service, logsDir, stateDir, outDir string) error {
ctx = uc.ContextWithExperiments(ctx)
switch service {

case "": // Validate-only.
return nil
case "fluentbit":
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
otlp_exporter,otlp_logging,otel_logging
otlp_logging,otel_logging
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

global:
otlp_exporter: true
combined:

receivers:
otlp:
type: otlp
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
otlp_exporter,otlp_logging
otlp_logging
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

global:
otlp_exporter: true
combined:

receivers:
otlp:
type: otlp
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

global:
otlp_exporter: true
metrics:

processors:
metrics_filter:
type: exclude_metrics
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

global:
otlp_exporter: true
metrics:

receivers:
prometheus:
type: prometheus
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

function process(tag, timestamp, record)
local v = "ops-agent";
(function(value)
if record["logging.googleapis.com/labels"] == nil
then
record["logging.googleapis.com/labels"] = {}
end
record["logging.googleapis.com/labels"]["agent.googleapis.com/health/agentKind"] = value
end)(v)
local v = "latest";
(function(value)
if record["logging.googleapis.com/labels"] == nil
then
record["logging.googleapis.com/labels"] = {}
end
record["logging.googleapis.com/labels"]["agent.googleapis.com/health/agentVersion"] = value
end)(v)
local v = "v1";
(function(value)
if record["logging.googleapis.com/labels"] == nil
then
record["logging.googleapis.com/labels"] = {}
end
record["logging.googleapis.com/labels"]["agent.googleapis.com/health/schemaVersion"] = value
end)(v)
return 2, timestamp, record
end
Loading
Loading