All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.turbospaces.logging.SentryAppender Maven / Gradle / Ivy

There is a newer version: 2.0.33
Show newest version
package com.turbospaces.logging;

import java.time.Duration;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import org.apache.commons.lang3.StringUtils;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.sentry.event.Event;
import io.sentry.event.EventBuilder;
import io.sentry.event.interfaces.ExceptionInterface;
import io.sentry.event.interfaces.MessageInterface;
import io.sentry.event.interfaces.SentryException;
import io.sentry.event.interfaces.StackTraceInterface;

public class SentryAppender extends AbstractAppender {
    private static final String RATE_LIMITER_SENTRY_APPENDER_KEY_PREFIX = "rate-limiter-sentry-appender.";
    private static final String DOT_REGEX = "\\.";
    private static final String UNDERSCORE = "_";

    public static final Duration PERIOD = Duration.ofMinutes(1);
    public static final int COUNT = 100;

    @Override
    public void start() {
        AlertLoggingFilter filter = (AlertLoggingFilter) context.getObject(Logback.SENTRY_LOGGING_FILTER);
        if (Objects.nonNull(filter)) {
            addFilter(filter);
        }

        super.start();

        //
        // ~ mark started
        //
        started = true;

        //
        // ~ effectively start
        //
        for (int i = 0; i < threads; i++) {
            WorkerThread worker = workers[i];
            worker.start();
        }
    }
    @Override
    protected boolean dryRun() {
        return alertsDryRun();
    }
    @Override
    protected void sendBulk(List list) {
        if (Objects.nonNull(getSentry())) {
            for (SequencedDeferredEvent next : list) {
                ILoggingEvent event = next.event();
                String logEventKey = generateLogEventKey(event);
                if (acquirePermission(logEventKey)) {
                    getSentry().sendEvent(writeBody(next));
                } else {
                    if (Objects.nonNull(event.getThrowableProxy())) {
                        addError(event.getFormattedMessage());
                    }
                }
            }
        }
    }
    private static String generateLogEventKey(ILoggingEvent loggingEvent) {
        String logEventKey = loggingEvent.getLoggerName();
        if (loggingEvent.getThrowableProxy() != null && StringUtils.isNotBlank(loggingEvent.getThrowableProxy().getClassName())) {
            String exceptionClassName = loggingEvent.getThrowableProxy().getClassName();
            String[] split = exceptionClassName.split(DOT_REGEX);
            String exceptionClassSimpleName = split.length > 0 ? split[split.length - 1] : StringUtils.EMPTY;
            logEventKey = logEventKey + UNDERSCORE + exceptionClassSimpleName;
        }

        return logEventKey;
    }
    public EventBuilder writeBody(SequencedDeferredEvent data) {
        ILoggingEvent event = data.event();

        EventBuilder builder = new EventBuilder();
        builder.withTimestamp(new Date(event.getTimeStamp()));
        builder.withMessage(event.getFormattedMessage());
        builder.withLogger(event.getLoggerName());
        builder.withLevel(formatLevel(event.getLevel()));
        builder.withExtra(Logback.SEQUENCE, data.seq());

        //
        // ~ write all formatted values
        //
        for (DocumentProperty field : properties.getProperties()) {
            String formatted = field.format(event);
            if (StringUtils.isEmpty(formatted)) {} else {
                builder.withTag(field.getName(), formatted);
            }
        }

        //
        // ~ argument array
        //
        if (event.getArgumentArray() != null) {
            List args = new ArrayList<>();
            for (Object argument : event.getArgumentArray()) {
                args.add(argument != null ? argument.toString() : null);
            }
            builder.withSentryInterface(new MessageInterface(event.getMessage(), args, event.getFormattedMessage()));
        }

        //
        // ~ exception
        //
        if (event.getThrowableProxy() != null) {
            builder.withSentryInterface(new ExceptionInterface(extractExceptionQueue(event)));
        } else if (event.getCallerData().length > 0) {
            builder.withSentryInterface(new StackTraceInterface(event.getCallerData()));
        }

        //
        // ~ write MDC values
        //
        Map mdc = event.getMDCPropertyMap();
        if (mdc != null) {
            for (Entry entry : mdc.entrySet()) {
                boolean toInclude = true;
                if (mdcNames.isEmpty()) {

                } else {
                    toInclude = mdcNames.contains(entry.getKey());
                }

                if (toInclude) {
                    if (StringUtils.isNotEmpty(entry.getValue())) {
                        builder.withTag(entry.getKey(), entry.getValue());
                    }
                }
            }
        }

        return builder;
    }
    private static Deque extractExceptionQueue(ILoggingEvent event) {
        IThrowableProxy throwable = event.getThrowableProxy();
        Deque exceptions = new ArrayDeque<>();
        Set circularityDetector = new HashSet<>();
        StackTraceElement[] enclosingStackTrace = {};

        // Stack the exceptions to send them in the reverse order
        while (throwable != null) {
            if (!circularityDetector.add(throwable)) {
                break;
            }

            StackTraceElement[] stackTraceElements = toStackTraceElements(throwable);
            StackTraceInterface stackTrace = new StackTraceInterface(stackTraceElements, enclosingStackTrace);
            Map.Entry mapping = extractPackageAndClassName(throwable.getClassName());
            exceptions.push(new SentryException(throwable.getMessage(), mapping.getKey(), mapping.getValue(), stackTrace));
            enclosingStackTrace = stackTraceElements;
            throwable = throwable.getCause();
        }

        return exceptions;
    }
    private static StackTraceElement[] toStackTraceElements(IThrowableProxy proxy) {
        StackTraceElementProxy[] elementProxies = proxy.getStackTraceElementProxyArray();
        StackTraceElement[] elements = new StackTraceElement[elementProxies.length];

        for (int i = 0; i < elementProxies.length; i++) {
            elements[i] = elementProxies[i].getStackTraceElement();
        }

        return elements;
    }
    private static Map.Entry extractPackageAndClassName(String canonicalClassName) {
        Map.Entry mapping;

        try {
            Class exceptionClass = Class.forName(canonicalClassName);
            Package exceptionPackage = exceptionClass.getPackage();
            String k = exceptionPackage != null ? exceptionPackage.getName() : SentryException.DEFAULT_PACKAGE_NAME;
            String v = exceptionClass.getSimpleName();
            mapping = new AbstractMap.SimpleEntry<>(k, v);
        } catch (ClassNotFoundException e) {
            int lastDot = canonicalClassName.lastIndexOf('.');
            if (lastDot != -1) {
                String k = canonicalClassName.substring(0, lastDot);
                String v = canonicalClassName.substring(lastDot);
                mapping = new AbstractMap.SimpleEntry<>(k, v);
            } else {
                mapping = new AbstractMap.SimpleEntry<>(SentryException.DEFAULT_PACKAGE_NAME, canonicalClassName);
            }
        }

        return mapping;
    }
    private static Event.Level formatLevel(Level level) {
        if (level.isGreaterOrEqual(Level.ERROR)) {
            return Event.Level.ERROR;
        } else if (level.isGreaterOrEqual(Level.WARN)) {
            return Event.Level.WARNING;
        } else if (level.isGreaterOrEqual(Level.INFO)) {
            return Event.Level.INFO;
        } else if (level.isGreaterOrEqual(Level.ALL)) {
            return Event.Level.DEBUG;
        } else {
            return null;
        }
    }
    private boolean acquirePermission(String key) {
        String rateLimiterKey = RATE_LIMITER_SENTRY_APPENDER_KEY_PREFIX + key;
        RateLimiterConfig current = getRateLimiterRegistry().getConfiguration(rateLimiterKey).orElseGet(new Supplier() {
            @Override
            public RateLimiterConfig get() {
                RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom()
                        .limitRefreshPeriod(PERIOD)
                        .limitForPeriod(COUNT)
                        .timeoutDuration(Duration.ZERO)
                        .build();
                getRateLimiterRegistry().addConfiguration(rateLimiterKey, rateLimiterConfig);
                return rateLimiterConfig;
            }
        });
        return getRateLimiterRegistry().rateLimiter(rateLimiterKey, current).acquirePermission();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy