Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,33 @@ ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", timeoutInMillis);
```


### Controlling SDK logging

The SDK is silent by default so it never emits Personally Identifiable Information (PII) unless you
explicitly opt in. To integrate the SDK with your application's logging framework, register a
`ZBLogger` implementation before issuing any API calls. The helper class `ZBLoggers` adapts
`java.util.logging` (JUL) out of the box:

```java
import com.zerobounce.ZBLoggers;

ZeroBounceSDK.setLogger(
ZBLoggers.jul(java.util.logging.Logger.getLogger("ZeroBounceSDK"))
);

// Optional: enable verbose payload logging for troubleshooting only.
ZeroBounceSDK.setLogPayloads(true);
```

Passing `null` to `ZeroBounceSDK.setLogger(...)` resets the logger to a no-op implementation, which
disables SDK logging again.


## Examples

> **Note:** The snippets below print responses for demonstration purposes. Avoid logging raw API
> data that may contain PII in production systems.

Then you can use any of the SDK methods, for example:

* ##### Validate an email address
Expand Down
25 changes: 25 additions & 0 deletions documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,33 @@ ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", timeoutInMillis);
```


#### Controlling SDK logging

The SDK is silent by default so it never emits Personally Identifiable Information (PII) unless you
explicitly opt in. To integrate the SDK with your application's logging framework, register a
`ZBLogger` implementation before issuing any API calls. The helper class `ZBLoggers` adapts
`java.util.logging` (JUL) out of the box:

```java
import com.zerobounce.ZBLoggers;

ZeroBounceSDK.setLogger(
ZBLoggers.jul(java.util.logging.Logger.getLogger("ZeroBounceSDK"))
);

// Optional: enable verbose payload logging for troubleshooting only.
ZeroBounceSDK.setLogPayloads(true);
```

Passing `null` to `ZeroBounceSDK.setLogger(...)` resets the logger to a no-op implementation, which
disables SDK logging again.


#### Examples

> **Note:** The snippets below print responses for demonstration purposes. Avoid logging raw API
> data that may contain PII in production systems.

Then you can use any of the SDK methods, for example:

* ####### Validate an email address
Expand Down
24 changes: 24 additions & 0 deletions documentation_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,32 @@ ZeroBounceSDK.getInstance().initialize("<TU_CLAVE_DE_API>");
ZeroBounceSDK.getInstance().initialize("<YOUR_API_KEY>", timeoutInMillis);
```

#### Control del registro del SDK

El SDK permanece silencioso de forma predeterminada, por lo que nunca emite información personal
identificable (PII) a menos que habilites el registro explícitamente. Para integrarlo con el sistema
de registro de tu aplicación, registra una implementación de `ZBLogger` antes de realizar llamadas a
la API. La clase auxiliar `ZBLoggers` adapta `java.util.logging` (JUL) sin agregar dependencias:

```java
import com.zerobounce.ZBLoggers;

ZeroBounceSDK.setLogger(
ZBLoggers.jul(java.util.logging.Logger.getLogger("ZeroBounceSDK"))
);

// Opcional: habilita el registro detallado de cargas solo para depuración.
ZeroBounceSDK.setLogPayloads(true);
```

Pasar `null` a `ZeroBounceSDK.setLogger(...)` restablece el registrador a una implementación que no
realiza ninguna acción, lo cual vuelve a desactivar el registro del SDK.

#### Ejemplos

> **Nota:** Los siguientes fragmentos imprimen respuestas con fines demostrativos. Evita registrar
> datos sin filtrar de la API en entornos de producción, ya que pueden contener PII.

A continuación, puedes utilizar cualquiera de los métodos del SDK. Por ejemplo:

* ####### Validar una dirección de correo electrónico
Expand Down
16 changes: 16 additions & 0 deletions zero-bounce-sdk/src/main/java/com/zerobounce/ZBLogger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.zerobounce;

/**
* Minimal logging abstraction used by the ZeroBounce SDK.
*/
public interface ZBLogger {
default void debug(String msg) {}
default void info(String msg) {}
default void warn(String msg) {}
default void error(String msg, Throwable t) {}

static ZBLogger noop() {
return new ZBLogger() {
};
}
}
38 changes: 38 additions & 0 deletions zero-bounce-sdk/src/main/java/com/zerobounce/ZBLoggers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.zerobounce;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Helper factory methods for adapting common logger implementations to {@link ZBLogger}.
*/
public final class ZBLoggers {
private ZBLoggers() {
}

public static ZBLogger jul(Logger logger) {
return new ZBLogger() {
@Override
public void debug(String msg) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(msg);
}
}

@Override
public void info(String msg) {
logger.info(msg);
}

@Override
public void warn(String msg) {
logger.warning(msg);
}

@Override
public void error(String msg, Throwable t) {
logger.log(Level.SEVERE, msg, t);
}
};
}
}
53 changes: 46 additions & 7 deletions zero-bounce-sdk/src/main/java/com/zerobounce/ZeroBounceSDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
public class ZeroBounceSDK {

private static volatile ZeroBounceSDK instance;
private static volatile ZBLogger logger = ZBLogger.noop();
private static volatile boolean logPayloads = false;

public static ZeroBounceSDK getInstance() {
// The implementation below uses a double-check locking mechanism to boost performance while keeping the
Expand All @@ -48,6 +50,24 @@ public static ZeroBounceSDK getInstance() {
return instance;
}

/**
* Sets a custom logger that will receive log messages emitted by the SDK.
*
* @param logger the logger to use; when {@code null} logging is disabled
*/
public static void setLogger(@Nullable ZBLogger logger) {
ZeroBounceSDK.logger = logger != null ? logger : ZBLogger.noop();
}

/**
* Enables or disables logging of HTTP payload content.
*
* @param enabled {@code true} to include payloads in debug logs
*/
public static void setLogPayloads(boolean enabled) {
logPayloads = enabled;
}

final String apiBaseUrl = "https://api.zerobounce.net/v2";
private final String bulkApiBaseUrl = "https://bulkapi.zerobounce.net/v2";
private final String bulkApiScoringBaseUrl = "https://bulkapi.zerobounce.net/v2/scoring";
Expand Down Expand Up @@ -332,7 +352,7 @@ private void _sendFile(
@NotNull OnErrorCallback errorCallback) throws ZBException {

String urlPath = (scoring ? bulkApiScoringBaseUrl : bulkApiBaseUrl) + "/sendfile";
System.out.println("ZeroBounceSDK::sendFile urlPath=" + urlPath);
logDebug("ZeroBounceSDK::sendFile urlPath=" + urlPath);

if (emailAddressColumnIndex < 1) {
throw new ZBException("Index for emailAddressColumnIndex must start from 1.");
Expand Down Expand Up @@ -398,7 +418,7 @@ private void _sendFile(
HttpEntity responseEntity = response.getEntity();
int status = response.getStatusLine().getStatusCode();

System.out.println("ZeroBounceSDK::sendFile status: " + status);
logDebug("ZeroBounceSDK::sendFile status: " + status);

StringBuilder content = new StringBuilder();
try (BufferedReader in = new BufferedReader(new InputStreamReader(responseEntity.getContent()))) {
Expand All @@ -410,7 +430,7 @@ private void _sendFile(

String rsp = content.toString();

System.out.println("ZeroBounceSDK::sendFile rsp=" + rsp);
logDebug("ZeroBounceSDK::sendFile rsp=" + rsp);

if (status > 299) {
ErrorResponse errorResponse = ErrorResponse.parseError(rsp);
Expand Down Expand Up @@ -726,7 +746,7 @@ private <T> void sendRequest(
@NotNull OnSuccessCallback<T> successCallback,
@NotNull OnErrorCallback errorCallback) {
try {
System.out.println("ZeroBounceSDK::sendRequest urlPath=" + urlPath);
logDebug("ZeroBounceSDK::sendRequest preparing request: " + urlPath);
URIBuilder ub = new URIBuilder(urlPath);

if (queryParameters != null) {
Expand All @@ -740,9 +760,12 @@ private <T> void sendRequest(
con.setRequestProperty("Accept", "application/json");
// con.setRequestProperty("Content-Type", "application/json");

String httpMethod;
if (body == null) {
httpMethod = "GET";
con.setRequestMethod("GET");
} else {
httpMethod = "POST";
con.setRequestMethod("POST");
con.setDoOutput(true);

Expand All @@ -757,10 +780,12 @@ private <T> void sendRequest(
}
}

logDebug("ZeroBounceSDK::sendRequest executing " + httpMethod + " " + urlPath);

con.setConnectTimeout(timeoutInMillis);

int status = con.getResponseCode();
System.out.println("ZeroBounceSDK::sendRequest status: " + status);
logDebug("ZeroBounceSDK::sendRequest status: " + status);
Reader streamReader;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
Expand All @@ -779,7 +804,7 @@ private <T> void sendRequest(
con.disconnect();
String rsp = content.toString();

System.out.println("ZeroBounceSDK::sendRequest rsp=" + rsp);
logPayload("ZeroBounceSDK::sendRequest rsp=" + rsp);

if (status > 299) {
ErrorResponse errorResponse = ErrorResponse.parseError(rsp);
Expand All @@ -789,13 +814,27 @@ private <T> void sendRequest(
successCallback.onSuccess(response);
}
} catch (Exception e) {
e.printStackTrace();
logError("ZeroBounceSDK::sendRequest failed", e);
ErrorResponse errorResponse = ErrorResponse.parseError(e.getMessage());
errorCallback.onError(errorResponse);
}

}

private static void logDebug(String message) {
logger.debug(message);
}

private static void logPayload(String message) {
if (logPayloads) {
logger.debug(message);
}
}

private static void logError(String message, Exception exception) {
logger.error(message, exception);
}

/**
* A class that can be used to configure the [sendFile] request.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
Expand Down Expand Up @@ -1015,6 +1016,81 @@ public void getActivityData_ReturnsError() throws Exception {
});
}

@Test
public void defaultLoggerEmitsNoConsoleOutput() throws Exception {
String responseJson = "{\"Credits\":2375323}";
String urlPath = getEncodedUrl(
"https://api.zerobounce.net/v2/getcredits",
new HashMap<String, String>() {
{
put("api_key", API_KEY);
}
}
);

PrintStream originalOut = System.out;
ByteArrayOutputStream capturedOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(capturedOut));

ZeroBounceSDK.setLogger(null);
ZeroBounceSDK.setLogPayloads(false);

try {
mockRequest(urlPath, 200, responseJson, "");
ZeroBounceSDK.getInstance().getCredits(
response -> {
}, errorResponse -> fail(errorResponse.toString()));
} finally {
System.setOut(originalOut);
ZeroBounceSDK.setLogger(null);
ZeroBounceSDK.setLogPayloads(false);
}

assertEquals("", capturedOut.toString());
}

@Test
public void payloadLoggingUsesConfiguredLoggerOnly() throws Exception {
String responseJson = "{\"Credits\":2375323}";
String urlPath = getEncodedUrl(
"https://api.zerobounce.net/v2/getcredits",
new HashMap<String, String>() {
{
put("api_key", API_KEY);
}
}
);

List<String> debugMessages = new ArrayList<>();
ZBLogger testLogger = new ZBLogger() {
@Override
public void debug(String msg) {
debugMessages.add(msg);
}
};

PrintStream originalOut = System.out;
ByteArrayOutputStream capturedOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(capturedOut));

ZeroBounceSDK.setLogger(testLogger);
ZeroBounceSDK.setLogPayloads(true);

try {
mockRequest(urlPath, 200, responseJson, "");
ZeroBounceSDK.getInstance().getCredits(
response -> {
}, errorResponse -> fail(errorResponse.toString()));
} finally {
System.setOut(originalOut);
ZeroBounceSDK.setLogger(null);
ZeroBounceSDK.setLogPayloads(false);
}

assertTrue(debugMessages.stream().anyMatch(message -> message.contains("ZeroBounceSDK::sendRequest rsp=" + responseJson)));
assertEquals("", capturedOut.toString());
}

private String getEncodedUrl(
String urlPath,
@Nullable Map<String, String> queryParameters
Expand Down