grpc/grpc-go · error

error reading observability configuration file

Error message

error reading observability configuration file %q: %v

What it means

GRPC_GCP_OBSERVABILITY_CONFIG_FILE was set, but os.ReadFile failed on the referenced path. The %q is the file path and %v is the os error (most often 'no such file or directory' or 'permission denied'). This fires before any parsing.

Solutions

  1. Confirm the path printed in %q exists: `ls -l <path>` from the same user/namespace the process runs as.
  2. Fix permissions (e.g. `chmod 644 <path>`) or run the process under the owning uid.
  3. If running in Kubernetes, verify the ConfigMap/Secret volumeMount and that the container starts after the mount.
  4. Use an absolute path; do not rely on the process working directory.
  5. If the file is optional, unset GRPC_GCP_OBSERVABILITY_CONFIG_FILE (fall back to GRPC_GCP_OBSERVABILITY_CONFIG) or leave both unset.

Example fix

# before
export GRPC_GCP_OBSERVABILITY_CONFIG_FILE=etc/obs.json

# after
export GRPC_GCP_OBSERVABILITY_CONFIG_FILE=/etc/grpc/observability.json
Defensive patterns

Strategy: validation

Validate before calling

if f := os.Getenv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE"); f != "" {
    info, err := os.Stat(f)
    if err != nil { return fmt.Errorf("config file unreachable: %w", err) }
    if info.IsDir() { return fmt.Errorf("config file is a directory: %s", f) }
    if info.Mode().Perm()&0400 == 0 { return fmt.Errorf("config file not readable: %s", f) }
}

Try / catch

if err := observability.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "error reading observability configuration file") {
        log.Fatalf("mount/permission problem for GRPC_GCP_OBSERVABILITY_CONFIG_FILE: %v", err)
    }
    log.Fatalf("observability: %v", err)
}

Prevention

When it happens

Trigger: Setting GRPC_GCP_OBSERVABILITY_CONFIG_FILE to a path that does not exist, is unreadable due to permissions, is a directory, or lives on a mount that is not attached in the container. observability.Start -> parseObservabilityConfig -> os.ReadFile.

Common situations: Container/pod deploy with a wrong mountPath; ConfigMap not mounted; file path relative to a different working directory; permission bits too strict (0600 owned by another uid); typo in the env var value; secret volume not yet attached at process start.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/ae717ea33f73ef84. Report an issue: GitHub.

Appendix: source

Thrown at gcp/observability/config.go:133

	}
	if err := validateLoggingEvents(&config); err != nil {
		return nil, fmt.Errorf("error parsing observability config: %v", err)
	}
	if config.CloudTrace != nil && (config.CloudTrace.SamplingRate > 1 || config.CloudTrace.SamplingRate < 0) {
		return nil, fmt.Errorf("error parsing observability config: invalid cloud trace sampling rate %v", config.CloudTrace.SamplingRate)
	}
	logger.Infof("Parsed ObservabilityConfig: %+v", &config)
	return &config, nil
}

func parseObservabilityConfig() (*config, error) {
	if f := envconfig.ObservabilityConfigFile; f != "" {
		if envconfig.ObservabilityConfig != "" {
			logger.Warning("Ignoring GRPC_GCP_OBSERVABILITY_CONFIG and using GRPC_GCP_OBSERVABILITY_CONFIG_FILE contents.")
		}
		content, err := os.ReadFile(f)
		if err != nil {
			return nil, fmt.Errorf("error reading observability configuration file %q: %v", f, err)
		}
		return unmarshalAndVerifyConfig(content)
	} else if envconfig.ObservabilityConfig != "" {
		return unmarshalAndVerifyConfig([]byte(envconfig.ObservabilityConfig))
	}
	// If the ENV var doesn't exist, do nothing
	return nil, nil
}

func ensureProjectIDInObservabilityConfig(ctx context.Context, config *config) error {
	if config.ProjectID == "" {
		// Try to fetch the GCP project id
		projectID := fetchDefaultProjectID(ctx)
		if projectID == "" {
			return fmt.Errorf("empty destination project ID")
		}
		config.ProjectID = projectID
	}

View on GitHub (pinned to 0c51461d27)