package me.alidg.errors.fingerprint; import me.alidg.errors.FingerprintProvider; import me.alidg.errors.HttpError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.lang.NonNull; import org.springframework.util.DigestUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Optional; import static java.nio.charset.StandardCharsets.UTF_8; /** * A MD5 based implementation of {@link FingerprintProvider} which generates a * fingerprint from the handled exception using the following formula: * <pre> * md5(exceptionName + currentTimeInMillis) * </pre> * * @author zarebski-m */ public class Md5FingerprintProvider implements FingerprintProvider { /** * The logger. */ private static final Logger logger = LoggerFactory.getLogger(Md5FingerprintProvider.class); /** * Generates a fingerprint based on the exception and the current timestamp. * * @param httpError Error event for which fingerprint is generated. * @return The generated fingerprint. */ @Override public String generate(@NonNull HttpError httpError) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { write(outputStream, exceptionName(httpError)); write(outputStream, System.currentTimeMillis()); // To ensure that the fingerprint is unique when the same exception is handled // at almost the same time in the same JVM instance. write(outputStream, System.nanoTime()); return DigestUtils.md5DigestAsHex(outputStream.toByteArray()); } catch (Exception e) { logger.warn("Failed to generate a fingerprint for {}", httpError); return null; } } private String exceptionName(HttpError httpError) { return Optional.ofNullable(httpError.getOriginalException()) .map(Throwable::getClass) .map(Class::getName) .orElse("no-exception"); } private void write(OutputStream os, String toWrite) { try { os.write(toWrite.getBytes(UTF_8)); } catch (IOException ignored) { } } private void write(OutputStream os, long timestamp) { try { ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); buffer.putLong(0, timestamp); os.write(buffer.array()); } catch (IOException ignored) { } } }