001package io.prometheus.metrics.exporter.httpserver; 002 003import io.prometheus.metrics.annotations.StableApi; 004import java.io.PrintWriter; 005import java.io.StringWriter; 006import java.nio.charset.StandardCharsets; 007import java.util.function.Consumer; 008import java.util.logging.Level; 009import java.util.logging.Logger; 010import javax.annotation.Nullable; 011 012/** 013 * Controls how the {@link HTTPServer} handles exceptions raised while scraping metrics. 014 * 015 * <p>The default policy built by {@link #builder()} does not expose exception details and does not 016 * report the exception. Configure the builder to route diagnostic details to an 017 * application-appropriate sink. 018 */ 019@StableApi 020public final class HttpErrorHandlingPolicy { 021 022 private static final Logger logger = Logger.getLogger(HttpErrorHandlingPolicy.class.getName()); 023 024 private static final byte[] GENERIC_RESPONSE = 025 ("An internal error occurred while scraping metrics. " 026 + "Configure an HTTP error reporter for details.\n") 027 .getBytes(StandardCharsets.UTF_8); 028 029 private final boolean unsafeDebugResponse; 030 @Nullable private final Consumer<? super Exception> errorReporter; 031 032 private HttpErrorHandlingPolicy( 033 boolean unsafeDebugResponse, @Nullable Consumer<? super Exception> errorReporter) { 034 this.unsafeDebugResponse = unsafeDebugResponse; 035 this.errorReporter = errorReporter; 036 } 037 038 /** 039 * Returns a builder for configuring scrape error handling. 040 * 041 * <p>The builder defaults to a generic HTTP 500 response with no error reporter. This avoids 042 * exposing exception details to scrape clients or adding an implicit dependency on an 043 * application's logging configuration. 044 */ 045 public static Builder builder() { 046 return new Builder(); 047 } 048 049 byte[] getErrorResponse(Exception exception) { 050 if (!unsafeDebugResponse) { 051 return GENERIC_RESPONSE; 052 } 053 StringWriter stringWriter = new StringWriter(); 054 PrintWriter printWriter = new PrintWriter(stringWriter); 055 printWriter.write("An Exception occurred while scraping metrics: "); 056 exception.printStackTrace(printWriter); 057 return stringWriter.toString().getBytes(StandardCharsets.UTF_8); 058 } 059 060 void report(Exception error) { 061 if (errorReporter != null) { 062 errorReporter.accept(error); 063 } 064 } 065 066 boolean hasErrorReporter() { 067 return errorReporter != null; 068 } 069 070 /** 071 * Returns a synchronous reporter that logs scrape exceptions at {@link Level#SEVERE} using JUL. 072 * 073 * <p>Reporting is opt-in; the default policy does not log scrape exceptions. 074 */ 075 public static Consumer<Throwable> julReporter() { 076 return error -> logger.log(Level.SEVERE, "Prometheus scrape failed", error); 077 } 078 079 /** Builder for {@link HttpErrorHandlingPolicy}. */ 080 public static final class Builder { 081 082 private boolean unsafeDebugResponse = false; 083 @Nullable private Consumer<? super Exception> errorReporter; 084 085 private Builder() {} 086 087 /** 088 * Pass scrape exceptions to {@code errorReporter}. 089 * 090 * <p>The reporter runs synchronously on the HTTP request thread. It should return promptly and 091 * must be safe to call concurrently. Runtime exceptions thrown by the reporter are isolated 092 * from HTTP response handling. 093 */ 094 public Builder errorReporter(Consumer<? super Exception> errorReporter) { 095 if (errorReporter == null) { 096 throw new NullPointerException("errorReporter"); 097 } 098 this.errorReporter = errorReporter; 099 return this; 100 } 101 102 /** 103 * Configure whether the HTTP 500 response includes the full exception stack trace. 104 * 105 * <p><strong>Security warning:</strong> Setting this to {@code true} exposes internal exception 106 * information to scrape clients. Do not enable it for endpoints reachable by untrusted clients. 107 * 108 * <p>This setting is independent of {@link #errorReporter(Consumer)}. 109 */ 110 public Builder unsafeDebugResponse(boolean unsafeDebugResponse) { 111 this.unsafeDebugResponse = unsafeDebugResponse; 112 return this; 113 } 114 115 /** Build the policy. */ 116 public HttpErrorHandlingPolicy build() { 117 return new HttpErrorHandlingPolicy(unsafeDebugResponse, errorReporter); 118 } 119 } 120}