grpc/grpc-go · warning

failed to close exporter. Flush failed: %v; Close failed: %v

Error message

failed to close exporter. Flush failed: %v; Close failed: %v

What it means

cloudLoggingExporter.Close() ran and BOTH the logger Flush and the client Close returned an error. This is a best-effort cleanup path (stopLogging -> lExporter.Close); Flush pushes buffered log entries upstream and Close tears down the HTTP connection to Cloud Logging. Seeing both means telemetry was likely lost and the connection did not shut down cleanly.

Source

Thrown at gcp/observability/exporting.go:96

}

func (cle *cloudLoggingExporter) EmitGcpLoggingEntry(entry gcplogging.Entry) {
	cle.logger.Log(entry)
	if logger.V(2) {
		logger.Infof("Uploading event to CloudLogging: %+v", entry)
	}
}

func (cle *cloudLoggingExporter) Close() error {
	var errFlush, errClose error
	if cle.logger != nil {
		errFlush = cle.logger.Flush()
	}
	if cle.client != nil {
		errClose = cle.client.Close()
	}
	if errFlush != nil && errClose != nil {
		return fmt.Errorf("failed to close exporter. Flush failed: %v; Close failed: %v", errFlush, errClose)
	}
	if errFlush != nil {
		return errFlush
	}
	if errClose != nil {
		return errClose
	}
	cle.logger = nil
	cle.client = nil
	logger.Infof("Closed CloudLogging exporter")
	return nil
}

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Log both inner errors and treat them as best-effort: shutdown telemetry loss is usually acceptable; document it.
  2. If Flush consistently fails, call Flush explicitly before End with a still-valid network path (don't wait for shutdown).
  3. Refresh Application Default Credentials so Close does not get a 401.
  4. For tests, ensure the exporter is closed exactly once and the mock simulates Flush success.
  5. Increase gcplogging DelayThreshold / BufferedByteLimit tradeoffs so less data is pending at shutdown.

Example fix

// before
defer observability.End()

// after
defer func() {
    if err := observability.End(); err != nil {
        log.Printf("observability shutdown (non-fatal): %v", err)
    }
}()
Defensive patterns

Strategy: try-catch

Try / catch

// End/Close is best-effort; never fail shutdown on telemetry flush.
defer func() {
    if err := observability.End(); err != nil {
        log.Printf("observability shutdown error (non-fatal): %v", err)
    }
}()

Prevention

When it happens

Trigger: Calling observability.End() (or a failed Start's deferred End) while the network is down, ADC has expired, the project is gone, or the process is being torn down mid-flush. Either side alone returns its own error; only when both fail do you see this combined message.

Common situations: Process killed during shutdown so Flush races a closing transport; ADC token expired between Start and End; Cloud Logging API disabled/quota exhausted mid-run; long-lived process whose connection was idle-closed; unit test that force-closes the mock exporter twice.

Related errors


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