{"record":{"id":"5961baa2822f37bb","repo":"grpc/grpc-go","slug":"failed-to-close-exporter-flush-failed-v-close","errorCode":null,"errorMessage":"failed to close exporter. Flush failed: %v; Close failed: %v","messagePattern":"failed to close exporter\\. Flush failed: (.+?); Close failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gcp/observability/exporting.go","lineNumber":96,"sourceCode":"}\n\nfunc (cle *cloudLoggingExporter) EmitGcpLoggingEntry(entry gcplogging.Entry) {\n\tcle.logger.Log(entry)\n\tif logger.V(2) {\n\t\tlogger.Infof(\"Uploading event to CloudLogging: %+v\", entry)\n\t}\n}\n\nfunc (cle *cloudLoggingExporter) Close() error {\n\tvar errFlush, errClose error\n\tif cle.logger != nil {\n\t\terrFlush = cle.logger.Flush()\n\t}\n\tif cle.client != nil {\n\t\terrClose = cle.client.Close()\n\t}\n\tif errFlush != nil && errClose != nil {\n\t\treturn fmt.Errorf(\"failed to close exporter. Flush failed: %v; Close failed: %v\", errFlush, errClose)\n\t}\n\tif errFlush != nil {\n\t\treturn errFlush\n\t}\n\tif errClose != nil {\n\t\treturn errClose\n\t}\n\tcle.logger = nil\n\tcle.client = nil\n\tlogger.Infof(\"Closed CloudLogging exporter\")\n\treturn nil\n}\n","sourceCodeStart":78,"sourceCodeEnd":109,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/gcp/observability/exporting.go#L78-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log both inner errors and treat them as best-effort: shutdown telemetry loss is usually acceptable; document it.","If Flush consistently fails, call Flush explicitly before End with a still-valid network path (don't wait for shutdown).","Refresh Application Default Credentials so Close does not get a 401.","For tests, ensure the exporter is closed exactly once and the mock simulates Flush success.","Increase gcplogging DelayThreshold / BufferedByteLimit tradeoffs so less data is pending at shutdown."],"exampleFix":"// before\ndefer observability.End()\n\n// after\ndefer func() {\n    if err := observability.End(); err != nil {\n        log.Printf(\"observability shutdown (non-fatal): %v\", err)\n    }\n}()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// End/Close is best-effort; never fail shutdown on telemetry flush.\ndefer func() {\n    if err := observability.End(); err != nil {\n        log.Printf(\"observability shutdown error (non-fatal): %v\", err)\n    }\n}()","preventionTips":["Treat exporter Close failures as warnings, not fatal.","Call Flush earlier in the lifecycle so shutdown has less to drain.","Refresh ADC before long-running processes to avoid expired-token errors at Close."],"tags":["observability","cloud-logging","shutdown","cleanup"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}