diff --git a/abstracto-application/abstracto-modules/anti-raid/anti-raid-impl/src/main/java/dev/sheldan/abstracto/antiraid/listener/MassPingMessageListener.java b/abstracto-application/abstracto-modules/anti-raid/anti-raid-impl/src/main/java/dev/sheldan/abstracto/antiraid/listener/MassPingMessageListener.java index 491fb9685..7e17111f9 100644 --- a/abstracto-application/abstracto-modules/anti-raid/anti-raid-impl/src/main/java/dev/sheldan/abstracto/antiraid/listener/MassPingMessageListener.java +++ b/abstracto-application/abstracto-modules/anti-raid/anti-raid-impl/src/main/java/dev/sheldan/abstracto/antiraid/listener/MassPingMessageListener.java @@ -6,6 +6,8 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.ChannelType; @@ -19,13 +21,22 @@ public class MassPingMessageListener implements AsyncMessageReceivedListener { @Autowired private MassPingService massPingService; + @Autowired + private Tracer tracer; + @Override public DefaultListenerResult execute(MessageReceivedModel model) { - Message message = model.getMessage(); - if(message.getAuthor().isBot() || message.isWebhookMessage() || !message.isFromGuild() || !message.isFromType(ChannelType.TEXT)) { - return DefaultListenerResult.IGNORED; + Span newSpan = tracer.nextSpan().name("mass-ping-filter"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + Message message = model.getMessage(); + if (message.getAuthor().isBot() || message.isWebhookMessage() || !message.isFromGuild() || !message.isFromType(ChannelType.TEXT)) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + massPingService.processMessage(message).whenComplete((unused, throwable) -> { + newSpan.end(); + }); } - massPingService.processMessage(message); return DefaultListenerResult.PROCESSED; } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/Rank.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/Rank.java index 535fb1ab1..fdc7cc5d1 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/Rank.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/Rank.java @@ -27,6 +27,8 @@ import dev.sheldan.abstracto.experience.service.ExperienceLevelService; import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; @@ -83,11 +85,14 @@ public class Rank extends AbstractConditionableCommand { @Value("${abstracto.experience.leaderboard.externalUrl}") private String leaderboardExternalURL; + @Autowired + private Tracer tracer; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); Member targetMember = !parameters.isEmpty() ? (Member) parameters.get(0) : commandContext.getAuthor(); - if(!targetMember.getGuild().equals(commandContext.getGuild())) { + if (!targetMember.getGuild().equals(commandContext.getGuild())) { throw new EntityGuildMismatchException(); } AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(targetMember); @@ -97,10 +102,13 @@ public class Rank extends AbstractConditionableCommand { .builder() .member(targetMember) .build(); + Span span = tracer.currentSpan(); return future.thenCompose(leaderBoardEntryModel -> { + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { MessageToSend messageToSend = self.renderMessageToSend(targetMember, rankModel, leaderBoardEntryModel.get(0)); return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel())); - }).thenApply(result -> CommandResult.fromIgnored()); + } + }).thenApply(result -> CommandResult.fromIgnored()); } @Transactional @@ -133,7 +141,7 @@ public class Rank extends AbstractConditionableCommand { @Override public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { Member targetMember; - if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER, event)) { + if (slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER, event)) { targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER, event, Member.class); } else { targetMember = event.getMember(); @@ -145,9 +153,12 @@ public class Rank extends AbstractConditionableCommand { .builder() .member(targetMember) .build(); + Span span = tracer.currentSpan(); return future.thenCompose(leaderBoardEntryModel -> { - MessageToSend messageToSend = self.renderMessageToSend(targetMember, rankModel, leaderBoardEntryModel.get(0)); - return interactionService.replyMessageToSend(messageToSend, event); + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { + MessageToSend messageToSend = self.renderMessageToSend(targetMember, rankModel, leaderBoardEntryModel.get(0)); + return interactionService.replyMessageToSend(messageToSend, event); + } }).thenApply(result -> CommandResult.fromIgnored()); } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/converter/LeaderBoardModelConverter.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/converter/LeaderBoardModelConverter.java index 354e7ca0a..c257659a7 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/converter/LeaderBoardModelConverter.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/converter/LeaderBoardModelConverter.java @@ -4,7 +4,8 @@ import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.experience.model.LeaderBoard; import dev.sheldan.abstracto.experience.model.LeaderBoardEntry; import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel; -import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Member; import org.springframework.beans.factory.annotation.Autowired; @@ -29,10 +30,7 @@ public class LeaderBoardModelConverter { private MemberService memberService; @Autowired - private UserExperienceManagementService userExperienceManagementService; - - @Autowired - private LeaderBoardModelConverter self; + private Tracer tracer; /** * Converts the complete {@link LeaderBoard leaderBoard} into a list of {@link LeaderBoardEntryModel leaderbaordEntryModels} which contain additional @@ -62,12 +60,15 @@ public class LeaderBoardModelConverter { .build(); }) .collect(Collectors.toMap(LeaderBoardEntryModel::getUserId, Function.identity())); + Span span = tracer.currentSpan(); return memberService.getMembersInServerAsync(serverId, userIds).thenApply(members -> { - members.forEach(member -> models.get(member.getIdLong()).setMember(member)); - return new ArrayList<>(models.values()) - .stream() - .sorted(Comparator.comparing(LeaderBoardEntryModel::getRank)). - collect(Collectors.toList()); + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { + members.forEach(member -> models.get(member.getIdLong()).setMember(member)); + return new ArrayList<>(models.values()) + .stream() + .sorted(Comparator.comparing(LeaderBoardEntryModel::getRank)). + collect(Collectors.toList()); + } }); } } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java index 94fa45223..030793576 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java @@ -7,6 +7,8 @@ import dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListener; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition; import dev.sheldan.abstracto.experience.service.AUserExperienceService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import org.springframework.beans.factory.annotation.Autowired; @@ -23,17 +25,26 @@ public class ExperienceTrackerListener implements AsyncMessageReceivedListener { @Autowired private AUserExperienceService userExperienceService; + @Autowired + private Tracer tracer; + @Override public DefaultListenerResult execute(MessageReceivedModel model) { - Message message = model.getMessage(); - if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem() || message.getAuthor().isBot()) { - return DefaultListenerResult.IGNORED; - } - if(userExperienceService.experienceGainEnabledInChannel(message.getChannel())) { - userExperienceService.addExperience(message.getMember(), model.getMessage()); - return DefaultListenerResult.PROCESSED; - } else { - return DefaultListenerResult.IGNORED; + Span newSpan = tracer.nextSpan().name("experience-tracker"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + Message message = model.getMessage(); + if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem() || message.getAuthor().isBot()) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + if(userExperienceService.experienceGainEnabledInChannel(message.getChannel())) { + userExperienceService.addExperience(message.getMember(), model.getMessage()).whenComplete((unused, throwable) -> { + newSpan.end(); + }); + return DefaultListenerResult.PROCESSED; + } else { + return DefaultListenerResult.IGNORED; + } } } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBean.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBean.java index 5d83be779..1a2a0ad96 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBean.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBean.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.experience.service; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.database.*; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.models.template.display.RoleDisplay; @@ -25,6 +26,8 @@ import dev.sheldan.abstracto.experience.service.management.DisabledExpRoleManage import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService; import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService; import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; @@ -114,8 +117,11 @@ public class AUserExperienceServiceBean implements AUserExperienceService { @Qualifier("experienceUpdateExecutor") private TaskExecutor experienceUpdateExecutor; + @Autowired + private Tracer tracer; + @Override - public void addExperience(Member member, Message message) { + public CompletableFuture addExperience(Member member, Message message) { runTimeExperienceService.takeLock(); try { Map> runtimeExperience = runTimeExperienceService.getRuntimeExperience(); @@ -141,7 +147,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService { // we store when the user is eligible for experience _again_ Long maxSeconds = configService.getLongValueOrConfigDefault(EXP_COOLDOWN_SECONDS_KEY, serverId); serverExperience.put(userId, Instant.now().plus(maxSeconds, ChronoUnit.SECONDS)); - CompletableFuture.runAsync(() -> self.addExperienceToMember(member, message), experienceUpdateExecutor).exceptionally(throwable -> { + return CompletableFuture.runAsync(() -> self.addExperienceToMember(member, message), MetricUtils.wrapExecutor(experienceUpdateExecutor)).exceptionally(throwable -> { log.error("Failed to add experience to member {} in server {}.", message.getAuthor().getId(), message.getGuild().getIdLong(), throwable); return null; }); @@ -149,6 +155,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService { } finally { runTimeExperienceService.releaseLock(); } + return CompletableFuture.completedFuture(null); } @@ -297,37 +304,43 @@ public class AUserExperienceServiceBean implements AUserExperienceService { } @Transactional - public void addExperienceToMember(Member member, Message message) { - long serverId = member.getGuild().getIdLong(); - AServer server = serverManagementService.loadOrCreate(serverId); - List disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server); - List disabledRoles = disabledExpRoles - .stream() - .map(ADisabledExpRole::getRole) - .collect(Collectors.toList()); - if(roleService.hasAnyOfTheRoles(member, disabledRoles)) { - log.debug("User {} has a experience disable role in server {} - not giving any experience.", member.getIdLong(), serverId); - return; - } - AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member); - Long userInServerId = userInAServer.getUserInServerId(); - Optional aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId()); - AUserExperience aUserExperience = aUserExperienceOptional.orElseGet(() -> userExperienceManagementService.createUserInServer(userInAServer)); - if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) { - List levels = experienceLevelManagementService.getLevelConfig(); - levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded)); + public CompletableFuture addExperienceToMember(Member member, Message message) { + CompletableFuture notificationFuture = CompletableFuture.completedFuture(null); + CompletableFuture levelActionFuture = CompletableFuture.completedFuture(null); + CompletableFuture roleFuture = CompletableFuture.completedFuture(null); + Span newSpan = tracer.nextSpan().name("experience-adding"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + long serverId = member.getGuild().getIdLong(); + AServer server = serverManagementService.loadOrCreate(serverId); + List disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server); + List disabledRoles = disabledExpRoles + .stream() + .map(ADisabledExpRole::getRole) + .collect(Collectors.toList()); + if (roleService.hasAnyOfTheRoles(member, disabledRoles)) { + log.debug("User {} has a experience disable role in server {} - not giving any experience.", member.getIdLong(), serverId); + newSpan.end(); + return CompletableFuture.completedFuture(null); + } + AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member); + Long userInServerId = userInAServer.getUserInServerId(); + Optional aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId()); + AUserExperience aUserExperience = aUserExperienceOptional.orElseGet(() -> userExperienceManagementService.createUserInServer(userInAServer)); + if (Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) { + List levels = experienceLevelManagementService.getLevelConfig(); + levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded)); - Long minExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MIN_EXP_KEY, serverId); - Long maxExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MAX_EXP_KEY, serverId); - Double multiplier = configService.getDoubleValueOrConfigDefault(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId); - Long experienceRange = maxExp - minExp + 1; - Long gainedExperience = (secureRandom.nextInt(experienceRange.intValue()) + minExp); - gainedExperience = (long) Math.floor(gainedExperience * multiplier); + Long minExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MIN_EXP_KEY, serverId); + Long maxExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MAX_EXP_KEY, serverId); + Double multiplier = configService.getDoubleValueOrConfigDefault(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId); + Long experienceRange = maxExp - minExp + 1; + Long gainedExperience = (secureRandom.nextInt(experienceRange.intValue()) + minExp); + gainedExperience = (long) Math.floor(gainedExperience * multiplier); - List roles = experienceRoleManagementService.getExperienceRolesForServer(server); - roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); + List roles = experienceRoleManagementService.getExperienceRolesForServer(server); + roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); - log.debug("Handling {}. The user gains {}.", userInServerId, gainedExperience); + log.debug("Handling {}. The user gains {}.", userInServerId, gainedExperience); Long oldExperience = aUserExperience.getExperience(); Long newExperienceCount = oldExperience + gainedExperience; @@ -415,6 +428,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService { } else { log.debug("Experience gain was disabled. User did not gain any experience."); } + return CompletableFuture.allOf(notificationFuture, levelActionFuture, roleFuture).whenComplete((unused, throwable) -> { + newSpan.end(); + }); } @Override diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java index 2a4b7d542..ae1188186 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java @@ -20,7 +20,7 @@ import java.util.concurrent.CompletableFuture; */ public interface AUserExperienceService { String EXPERIENCE_GAIN_CHANNEL_GROUP_KEY = "experienceGain"; - void addExperience(Member member, Message message); + CompletableFuture addExperience(Member member, Message message); /** * Calculates the appropriate level for the given experience amount according to the given {@link AExperienceLevel levels} diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/listener/InviteLinkFilterListener.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/listener/InviteLinkFilterListener.java index 2130f370f..88709997c 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/listener/InviteLinkFilterListener.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/listener/InviteLinkFilterListener.java @@ -7,6 +7,8 @@ import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; import dev.sheldan.abstracto.invitefilter.config.InviteFilterFeatureDefinition; import dev.sheldan.abstracto.invitefilter.service.InviteLinkFilterService; import dev.sheldan.abstracto.invitefilter.service.InviteLinkFilterServiceBean; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +26,9 @@ public class InviteLinkFilterListener implements AsyncMessageReceivedListener { @Autowired private InviteLinkFilterServiceBean filterServiceBean; + @Autowired + private Tracer tracer; + @Override public FeatureDefinition getFeature() { return InviteFilterFeatureDefinition.INVITE_FILTER; @@ -31,31 +36,40 @@ public class InviteLinkFilterListener implements AsyncMessageReceivedListener { @Override public DefaultListenerResult execute(MessageReceivedModel model) { - Message message = model.getMessage(); + Span newSpan = tracer.nextSpan().name("invite-filter"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + Message message = model.getMessage(); - if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem()) { - return DefaultListenerResult.IGNORED; + if (!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem()) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + + List foundInvites = inviteLinkFilterService.findInvitesInMessage(message); + + if (foundInvites.isEmpty()) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + + if (!inviteLinkFilterService.isInviteFilterActiveInChannel(message.getChannel())) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + + if (inviteLinkFilterService.isMemberImmuneAgainstInviteFilter(message.getMember())) { + log.info("Not checking for invites in message, because author {} in channel {} in guild {} is immune against invite filter.", + message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong()); + newSpan.end(); + return DefaultListenerResult.IGNORED; + } + + // only to reduce code duplication, the interface is too concrete + filterServiceBean.resolveAndCheckInvites(message, foundInvites).whenComplete((unused, throwable) -> { + newSpan.end(); + }); + + return DefaultListenerResult.PROCESSED; } - - List foundInvites = inviteLinkFilterService.findInvitesInMessage(message); - - if(foundInvites.isEmpty()){ - return DefaultListenerResult.IGNORED; - } - - if(!inviteLinkFilterService.isInviteFilterActiveInChannel(message.getChannel())) { - return DefaultListenerResult.IGNORED; - } - - if(inviteLinkFilterService.isMemberImmuneAgainstInviteFilter(message.getMember())) { - log.info("Not checking for invites in message, because author {} in channel {} in guild {} is immune against invite filter.", - message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong()); - return DefaultListenerResult.IGNORED; - } - - // only to reduce code duplication, the interface is too concrete - filterServiceBean.resolveAndCheckInvites(message, foundInvites); - - return DefaultListenerResult.PROCESSED; } } diff --git a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java index a8b203314..a8d6c5737 100644 --- a/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java +++ b/abstracto-application/abstracto-modules/invite-filter/invite-filter-impl/src/main/java/dev/sheldan/abstracto/invitefilter/service/InviteLinkFilterServiceBean.java @@ -303,12 +303,13 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService { return foundInvites; } - public void resolveAndCheckInvites(Message message, List foundInvites) { + public CompletableFuture resolveAndCheckInvites(Message message, List foundInvites) { List> inviteList = new ArrayList<>(); JDA jda = message.getJDA(); foundInvites.forEach(s -> inviteList.add(resolveInvite(jda, s))); CompletableFutureList list = new CompletableFutureList<>(inviteList); + CompletableFuture returningFuture = new CompletableFuture<>(); list.getMainFuture().whenComplete((unused, throwable) -> { List invites = list.getObjects(); Long serverId = message.getGuild().getIdLong(); @@ -353,24 +354,31 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService { } if(toDelete) { metricService.incrementCounter(MESSAGE_INVITE_FILTERED); - messageService.deleteMessage(message); + + CompletableFuture deletionFuture = messageService.deleteMessage(message); + CompletableFuture notificationFuture = CompletableFuture.completedFuture(null); boolean trackUsages = featureModeService.featureModeActive(InviteFilterFeatureDefinition.INVITE_FILTER, serverId, InviteFilterMode.TRACK_USES); if(trackUsages) { targetServers.forEach((targetServerId, serverName) -> storeFilteredInviteLinkUsage(targetServerId, serverName, author)); } boolean sendNotification = featureModeService.featureModeActive(InviteFilterFeatureDefinition.INVITE_FILTER, serverId, InviteFilterMode.FILTER_NOTIFICATIONS); if(sendNotification) { - sendDeletionNotification(deletedInvites, message) + notificationFuture = sendDeletionNotification(deletedInvites, message) .thenAccept(unused1 -> log.info("Sent invite deletion notification.")).exceptionally(throwable1 -> { log.error("Failed to send invite deletion notification."); return null; }); } + CompletableFuture.allOf(deletionFuture, notificationFuture).whenComplete((unused1, throwable1) -> { + returningFuture.complete(null); + }); } }).exceptionally(throwable -> { log.error("Invite matching failed.", throwable); + returningFuture.complete(null); return null; }); + return returningFuture; } @PostConstruct diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/listener/ProfanityMessageReceivedListener.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/listener/ProfanityMessageReceivedListener.java index 25c036104..c3a77c4d1 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/listener/ProfanityMessageReceivedListener.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/listener/ProfanityMessageReceivedListener.java @@ -9,6 +9,8 @@ import dev.sheldan.abstracto.core.service.ProfanityService; import dev.sheldan.abstracto.profanityfilter.config.ProfanityFilterFeatureDefinition; import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterService; import dev.sheldan.abstracto.profanityfilter.service.ProfanityFilterServiceBean; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import org.springframework.beans.factory.annotation.Autowired; @@ -29,25 +31,35 @@ public class ProfanityMessageReceivedListener implements AsyncMessageReceivedLis @Autowired private ProfanityFilterServiceBean profanityFilterServiceBean; + @Autowired + private Tracer tracer; + @Override public DefaultListenerResult execute(MessageReceivedModel model) { - Message message = model.getMessage(); - if(message.isWebhookMessage() || message.getType().isSystem() || !message.isFromGuild()) { - return DefaultListenerResult.IGNORED; - } + Span newSpan = tracer.nextSpan().name("profanity-filter"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + Message message = model.getMessage(); + if (message.isWebhookMessage() || message.getType().isSystem() || !message.isFromGuild()) { + newSpan.end(); + return DefaultListenerResult.IGNORED; + } - if(profanityFilterService.isImmuneAgainstProfanityFilter(message.getMember())) { - log.debug("Not checking for profanities in message, because author {} in channel {} in guild {} is immune against profanity filter.", - message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong()); - return DefaultListenerResult.IGNORED; - } + if (profanityFilterService.isImmuneAgainstProfanityFilter(message.getMember())) { + log.debug("Not checking for profanities in message, because author {} in channel {} in guild {} is immune against profanity filter.", + message.getMember().getIdLong(), message.getGuild().getIdLong(), message.getChannel().getIdLong()); + newSpan.end(); + return DefaultListenerResult.IGNORED; + } - Long serverId = model.getServerId(); - Optional potentialProfanityGroup = profanityService.getProfanityRegex(message.getContentRaw(), serverId); - if(potentialProfanityGroup.isPresent()) { - ProfanityRegex foundProfanityGroup = potentialProfanityGroup.get(); - profanityFilterServiceBean.handleProfaneMessage(message, foundProfanityGroup); - return DefaultListenerResult.PROCESSED; + Long serverId = model.getServerId(); + Optional potentialProfanityGroup = profanityService.getProfanityRegex(message.getContentRaw(), serverId); + if (potentialProfanityGroup.isPresent()) { + ProfanityRegex foundProfanityGroup = potentialProfanityGroup.get(); + profanityFilterServiceBean.handleProfaneMessage(message, foundProfanityGroup).whenComplete((unused, throwable) -> { + newSpan.end(); + }); + return DefaultListenerResult.PROCESSED; + } } return DefaultListenerResult.IGNORED; } diff --git a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java index df945ef6a..a1f8be8aa 100644 --- a/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java +++ b/abstracto-application/abstracto-modules/profanity-filter/profanity-filter-impl/src/main/java/dev/sheldan/abstracto/profanityfilter/service/ProfanityFilterServiceBean.java @@ -238,22 +238,23 @@ public class ProfanityFilterServiceBean implements ProfanityFilterService { return roleImmunityService.isImmune(member, PROFANITY_FILTER_EFFECT_KEY); } - public void handleProfaneMessage(Message message, ProfanityRegex foundProfanityGroup) { + public CompletableFuture handleProfaneMessage(Message message, ProfanityRegex foundProfanityGroup) { metricService.incrementCounter(PROFANITIES_DETECTED_METRIC); if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, message.getGuild().getIdLong(), ProfanityFilterMode.PROFANITY_REPORT)) { - createProfanityReport(message, foundProfanityGroup).exceptionally(throwable -> { + return createProfanityReport(message, foundProfanityGroup).exceptionally(throwable -> { log.error("Failed to report or persist profanities in server {} for message {} in channel {}.", message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong(), throwable); return null; }); } if(featureModeService.featureModeActive(ProfanityFilterFeatureDefinition.PROFANITY_FILTER, message.getGuild().getIdLong(), ProfanityFilterMode.AUTO_DELETE_PROFANITIES)) { - messageService.deleteMessage(message).exceptionally(throwable -> { + return messageService.deleteMessage(message).exceptionally(throwable -> { log.error("Failed to delete profanity message with id {} in channel {} in server {}.", message.getIdLong(), message.getChannel().getIdLong(), message.getGuild().getIdLong(), throwable); return null; }); } + return CompletableFuture.completedFuture(null); } @PostConstruct diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java index f05d35d92..85f718230 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java @@ -8,6 +8,8 @@ import dev.sheldan.abstracto.core.service.GuildService; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import org.springframework.beans.factory.annotation.Autowired; @@ -30,13 +32,13 @@ public class EmoteTrackingListener implements AsyncMessageReceivedListener { @Autowired private GuildService guildService; - @Override - public FeatureDefinition getFeature() { - return StatisticFeatureDefinition.EMOTE_TRACKING; - } + @Autowired + private Tracer tracer; @Override public DefaultListenerResult execute(MessageReceivedModel model) { + Span newSpan = tracer.nextSpan().name("experience-tracker"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { Message message = model.getMessage(); if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem()) { return DefaultListenerResult.IGNORED; @@ -50,5 +52,15 @@ public class EmoteTrackingListener implements AsyncMessageReceivedListener { trackedEmoteService.addEmoteToRuntimeStorage(groupedEmotes.get(0), guildService.getGuildById(model.getServerId()), (long) groupedEmotes.size(), UsedEmoteType.MESSAGE) ); return DefaultListenerResult.PROCESSED; + } finally { + newSpan.end(); + } } + + + @Override + public FeatureDefinition getFeature() { + return StatisticFeatureDefinition.EMOTE_TRACKING; + } + } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/ListenerExecutorConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/ListenerExecutorConfig.java index d2caa7770..f829d3ad3 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/ListenerExecutorConfig.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/ListenerExecutorConfig.java @@ -207,4 +207,9 @@ public class ListenerExecutorConfig { return executorService.setupExecutorFor("voiceChatLeftListener"); } + @Bean(name = "genericExecutor") + public TaskExecutor genericExecutor() { + return executorService.setupExecutorFor("genericExecutor"); + } + } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java index c58d7e2b4..980d0bb15 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java @@ -11,6 +11,8 @@ import dev.sheldan.abstracto.core.templating.model.AttachedFile; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.ContextUtils; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -58,6 +60,9 @@ public class InteractionServiceBean implements InteractionService { @Autowired private TemplateService templateService; + @Autowired + private Tracer tracer; + public static final CounterMetric EPHEMERAL_MESSAGES_SEND = CounterMetric .builder() .name(DISCORD_API_INTERACTION_METRIC) @@ -259,6 +264,8 @@ public class InteractionServiceBean implements InteractionService { } public CompletableFuture replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback) { + Span newSpan = tracer.nextSpan().name("send-message-to-interaction"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { ReplyCallbackAction action = null; if(messageToSend.getUseComponentsV2()) { action = callback.replyComponents(messageToSend.getComponents()).useComponentsV2(); @@ -333,10 +340,12 @@ public class InteractionServiceBean implements InteractionService { } } - if(action == null) { - throw new AbstractoRunTimeException("The callback did not result in any message."); + if (action == null) { + throw new AbstractoRunTimeException("The callback did not result in any message."); + } + return action.submit(); + } } - return action.submit(); } @Override diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/button/listener/SyncButtonClickedListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/button/listener/SyncButtonClickedListenerBean.java index 67c4b254a..911fd0447 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/button/listener/SyncButtonClickedListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/button/listener/SyncButtonClickedListenerBean.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.interaction.InteractionResult; import dev.sheldan.abstracto.core.interaction.button.ButtonPostInteractionExecution; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.database.ComponentPayload; import dev.sheldan.abstracto.core.interaction.button.ButtonPayload; import dev.sheldan.abstracto.core.service.FeatureConfigService; @@ -63,7 +64,7 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter { @Override public void onButtonInteraction(@Nonnull ButtonInteractionEvent event) { if(listenerList == null) return; - CompletableFuture.runAsync(() -> self.executeListenerLogic(event), buttonClickedExecutor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeListenerLogic(event), MetricUtils.wrapExecutor(buttonClickedExecutor)).exceptionally(throwable -> { log.error("Failed to execute listener logic in async button event.", throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/context/message/MessageContextCommandListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/context/message/MessageContextCommandListenerBean.java index da9f00197..b587cd4e0 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/context/message/MessageContextCommandListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/context/message/MessageContextCommandListenerBean.java @@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.interaction.context.message.listener.MessageCo import dev.sheldan.abstracto.core.metric.service.CounterMetric; import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricTag; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel; import dev.sheldan.abstracto.core.service.FeatureConfigService; import dev.sheldan.abstracto.core.service.FeatureFlagService; @@ -65,7 +66,7 @@ public class MessageContextCommandListenerBean extends ListenerAdapter { @Override public void onMessageContextInteraction(MessageContextInteractionEvent event) { if(listenerList == null) return; - CompletableFuture.runAsync(() -> self.executeListenerLogic(event), messageContextCommandExecutor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeListenerLogic(event), MetricUtils.wrapExecutor(messageContextCommandExecutor)).exceptionally(throwable -> { log.error("Failed to execute listener logic in async message context event.", throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/menu/StringSelectMenuListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/menu/StringSelectMenuListenerBean.java index 14662aad3..8eb5dde2f 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/menu/StringSelectMenuListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/menu/StringSelectMenuListenerBean.java @@ -3,9 +3,9 @@ package dev.sheldan.abstracto.core.interaction.menu; import com.google.gson.Gson; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; -import dev.sheldan.abstracto.core.interaction.InteractionResult; import dev.sheldan.abstracto.core.interaction.menu.listener.StringSelectMenuListener; import dev.sheldan.abstracto.core.interaction.menu.listener.StringSelectMenuListenerModel; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.database.ComponentPayload; import dev.sheldan.abstracto.core.service.FeatureConfigService; import dev.sheldan.abstracto.core.service.FeatureFlagService; @@ -60,7 +60,7 @@ public class StringSelectMenuListenerBean extends ListenerAdapter { public void onStringSelectInteraction(@Nonnull StringSelectInteractionEvent event) { if(listenerList == null) return; event.deferEdit().queue(); - CompletableFuture.runAsync(() -> self.executeListenerLogic(event), buttonClickedExecutor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeListenerLogic(event), MetricUtils.wrapExecutor(buttonClickedExecutor)).exceptionally(throwable -> { log.error("Failed to execute listener logic in async button event.", throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java index 7c5b54c1f..874060a84 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/listener/ModalInteractionListenerBean.java @@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; import dev.sheldan.abstracto.core.interaction.InteractionResult; import dev.sheldan.abstracto.core.interaction.modal.ModalPayload; import dev.sheldan.abstracto.core.interaction.modal.ModalPostInteractionExecution; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.database.ComponentPayload; import dev.sheldan.abstracto.core.service.FeatureConfigService; import dev.sheldan.abstracto.core.service.FeatureFlagService; @@ -62,7 +63,7 @@ public class ModalInteractionListenerBean extends ListenerAdapter { @Override public void onModalInteraction(@Nonnull ModalInteractionEvent event) { if(listenerList == null) return; - CompletableFuture.runAsync(() -> self.executeListenerLogic(event), modalInteractionExecutor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeListenerLogic(event), MetricUtils.wrapExecutor(modalInteractionExecutor)).exceptionally(throwable -> { log.error("Failed to execute listener logic in modal interaction event.", throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java index fb0636524..cddb8434c 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java @@ -31,6 +31,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import dev.sheldan.abstracto.core.utils.ContextUtils; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; @@ -100,6 +105,12 @@ public class SlashCommandListenerBean extends ListenerAdapter { @Autowired private SchedulerService schedulerService; + @Autowired + private ObservationRegistry observationRegistry; + + @Autowired + private Tracer tracer; + @Autowired private ComponentPayloadManagementService componentPayloadManagementService; @@ -124,6 +135,8 @@ public class SlashCommandListenerBean extends ListenerAdapter { @Override public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { + Observation observation = Observation.createNotStarted("slash-command-received", this.observationRegistry); + observation.observe(() -> { try { if(commands == null || commands.isEmpty()) return; if(ContextUtils.hasGuild(event.getInteraction())) { @@ -131,43 +144,59 @@ public class SlashCommandListenerBean extends ListenerAdapter { } else { log.debug("Executing slash command by user {}", event.getUser().getIdLong()); } - CompletableFuture.runAsync(() -> self.executeListenerLogic(event), slashCommandExecutor).exceptionally(throwable -> { + Span span = tracer.currentSpan(); + CompletableFuture.runAsync(() -> { + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { + self.executeListenerLogic(event).whenComplete((unused, throwable) -> { + span.end(); + }); + } + + }, slashCommandExecutor).exceptionally(throwable -> { log.error("Failed to execute listener logic in async slash command event.", throwable); return null; }); } catch (Exception exception) { log.error("Failed to process slash command interaction event.", exception); - } + }); } @Transactional - public void executeListenerLogic(SlashCommandInteractionEvent event) { + public CompletableFuture executeListenerLogic(SlashCommandInteractionEvent event) { Optional potentialCommand = findCommand(event); - potentialCommand.ifPresent(command -> { - metricService.incrementCounter(SLASH_COMMANDS_PROCESSED_COUNTER); - try { - commandService.isCommandExecutable(command, event).thenAccept(conditionResult -> { - self.executeCommand(event, command, conditionResult); - }).exceptionally(throwable -> { - log.error("Error while executing command {}", command.getConfiguration().getName(), throwable); - CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable); + if(potentialCommand.isPresent()) { + Command command = potentialCommand.get(); + metricService.incrementCounter(SLASH_COMMANDS_PROCESSED_COUNTER); + try { + Span span = tracer.currentSpan(); + span.tag("command-name", command.getConfiguration().getName()); + return commandService.isCommandExecutable(command, event).thenCompose(conditionResult -> { + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { + return self.executeCommand(event, command, conditionResult); + } + }).exceptionally(throwable -> { + log.error("Error while executing command {}", command.getConfiguration().getName(), throwable); + CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable); + self.executePostCommandListener(command, event, commandResult); + return null; + }); + } catch (Exception exception) { + log.error("Error while checking if command {} is executable.", command.getConfiguration().getName(), exception); + CommandResult commandResult = CommandResult.fromError(exception.getMessage(), exception); self.executePostCommandListener(command, event, commandResult); - return null; - }); - } catch (Exception exception) { - log.error("Error while checking if command {} is executable.", command.getConfiguration().getName(), exception); - CommandResult commandResult = CommandResult.fromError(exception.getMessage(), exception); - self.executePostCommandListener(command, event, commandResult); - } - }); + return CompletableFuture.completedFuture(null); + } + } else { + return CompletableFuture.completedFuture(null); + } } @Override public void onCommandAutoCompleteInteraction(@Nonnull CommandAutoCompleteInteractionEvent event) { try { if(commands == null || commands.isEmpty()) return; - CompletableFuture.runAsync(() -> self.executeAutCompleteListenerLogic(event), slashCommandAutoCompleteExecutor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeAutCompleteListenerLogic(event), MetricUtils.wrapExecutor(slashCommandAutoCompleteExecutor)).exceptionally(throwable -> { log.error("Failed to execute listener logic in async auto complete interaction event.", throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveMessageReceivedListener.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveMessageReceivedListener.java index 3418a1daa..c4916a7c1 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveMessageReceivedListener.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveMessageReceivedListener.java @@ -6,6 +6,9 @@ import dev.sheldan.abstracto.core.interactive.setup.callback.MessageInteractionC import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; @@ -20,6 +23,9 @@ public class InteractiveMessageReceivedListener implements AsyncMessageReceivedL // TODO timeout private Map>> callbacks = new HashMap<>(); + @Autowired + private Tracer tracer; + private static final Lock runTimeLock = new ReentrantLock(); @Override @@ -40,7 +46,8 @@ public class InteractiveMessageReceivedListener implements AsyncMessageReceivedL public boolean executeCallback(MessageReceivedModel model) { runTimeLock.lock(); - try { + Span newSpan = tracer.nextSpan().name("interactive-message-tracker"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())){ if(callbacks.containsKey(model.getServerId())) { Map> channelMap = callbacks.get(model.getServerId()); if(channelMap.containsKey(model.getMessage().getChannel().getIdLong())) { @@ -56,6 +63,7 @@ public class InteractiveMessageReceivedListener implements AsyncMessageReceivedL } } } finally { + newSpan.end(); runTimeLock.unlock(); } return false; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncMessageReceivedListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncMessageReceivedListenerBean.java index 13754e693..70d609923 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncMessageReceivedListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncMessageReceivedListenerBean.java @@ -1,12 +1,10 @@ package dev.sheldan.abstracto.core.listener.async.jda; -import dev.sheldan.abstracto.core.command.service.ExceptionService; import dev.sheldan.abstracto.core.listener.ListenerService; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; -import dev.sheldan.abstracto.core.service.BotService; -import dev.sheldan.abstracto.core.service.FeatureConfigService; -import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.MessageCache; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -28,18 +26,6 @@ public class AsyncMessageReceivedListenerBean extends ListenerAdapter { @Autowired(required = false) private List listenerList; - @Autowired - private FeatureConfigService featureConfigService; - - @Autowired - private FeatureFlagService featureFlagService; - - @Autowired - private BotService botService; - - @Autowired - private ExceptionService exceptionService; - @Autowired @Qualifier("messageReceivedExecutor") private TaskExecutor messageReceivedExecutor; @@ -47,14 +33,21 @@ public class AsyncMessageReceivedListenerBean extends ListenerAdapter { @Autowired private ListenerService listenerService; + @Autowired + private ObservationRegistry observationRegistry; + @Override @Transactional public void onMessageReceived(@Nonnull MessageReceivedEvent event) { if(listenerList == null) return; if(!event.isFromGuild()) return; - messageCache.putMessageInCache(event.getMessage()); - MessageReceivedModel model = getModel(event); - listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, messageReceivedExecutor)); + Observation observation = Observation.createNotStarted("async-message-received", this.observationRegistry); + observation.lowCardinalityKeyValue("some-tag", "some-value"); + observation.observe(() -> { + messageCache.putMessageInCache(event.getMessage()); + MessageReceivedModel model = getModel(event); + listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, messageReceivedExecutor)); + }); } private MessageReceivedModel getModel(MessageReceivedEvent event) { diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncPrivateMessageReceivedListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncPrivateMessageReceivedListenerBean.java index df1ee57ad..81dbcd15a 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncPrivateMessageReceivedListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncPrivateMessageReceivedListenerBean.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.core.listener.async.jda; import dev.sheldan.abstracto.core.command.service.ExceptionService; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.service.BotService; import dev.sheldan.abstracto.core.service.CacheEntityService; @@ -54,7 +55,7 @@ public class AsyncPrivateMessageReceivedListenerBean extends ListenerAdapter { cacheEntityService.buildCachedMessageFromMessage(event.getMessage()).thenAccept(cachedMessage -> privateMessageReceivedListeners.forEach(messageReceivedListener -> CompletableFuture.runAsync(() -> self.executeIndividualPrivateMessageReceivedListener(cachedMessage, messageReceivedListener, event) - , privateMessageReceivedExecutor) + , MetricUtils.wrapExecutor(privateMessageReceivedExecutor)) .exceptionally(throwable -> { log.error("Async private message receiver listener {} failed.", messageReceivedListener, throwable); return null; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/sync/jda/MessageReceivedListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/sync/jda/MessageReceivedListenerBean.java index 83e350a05..5af10c6de 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/sync/jda/MessageReceivedListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/sync/jda/MessageReceivedListenerBean.java @@ -1,16 +1,15 @@ package dev.sheldan.abstracto.core.listener.sync.jda; -import dev.sheldan.abstracto.core.command.service.ExceptionService; import dev.sheldan.abstracto.core.listener.ListenerService; import dev.sheldan.abstracto.core.metric.service.CounterMetric; import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricTag; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; -import dev.sheldan.abstracto.core.service.BotService; -import dev.sheldan.abstracto.core.service.FeatureConfigService; -import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.MessageCache; import dev.sheldan.abstracto.core.utils.BeanUtils; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.annotation.Observed; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -25,6 +24,7 @@ import java.util.List; @Component @Slf4j +@Observed public class MessageReceivedListenerBean extends ListenerAdapter { @Autowired @@ -33,24 +33,15 @@ public class MessageReceivedListenerBean extends ListenerAdapter { @Autowired(required = false) private List listenerList; - @Autowired - private FeatureConfigService featureConfigService; - - @Autowired - private FeatureFlagService featureFlagService; - - @Autowired - private BotService botService; - - @Autowired - private ExceptionService exceptionService; - @Autowired private ListenerService listenerService; @Autowired private MetricService metricService; + @Autowired + private ObservationRegistry observationRegistry; + public static final String MESSAGE_METRIC = "message"; public static final String ACTION = "action"; private static final CounterMetric MESSAGE_RECEIVED_COUNTER = CounterMetric @@ -63,12 +54,14 @@ public class MessageReceivedListenerBean extends ListenerAdapter { @Transactional public void onMessageReceived(@Nonnull MessageReceivedEvent event) { if(!event.isFromGuild()) return; - metricService.incrementCounter(MESSAGE_RECEIVED_COUNTER); - messageCache.putMessageInCache(event.getMessage()); - if(listenerList == null) return; - MessageReceivedModel model = getModel(event); - listenerList.forEach(messageReceivedListener -> listenerService.executeFeatureAwareListener(messageReceivedListener, model)); - + Observation observation = Observation.createNotStarted("message-received", this.observationRegistry); + observation.observe(() -> { + metricService.incrementCounter(MESSAGE_RECEIVED_COUNTER); + messageCache.putMessageInCache(event.getMessage()); + if (listenerList == null) return; + MessageReceivedModel model = getModel(event); + listenerList.forEach(messageReceivedListener -> listenerService.executeFeatureAwareListener(messageReceivedListener, model)); + }); } private MessageReceivedModel getModel(MessageReceivedEvent event) { diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/BotServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/BotServiceBean.java index c595f9209..32528ac86 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/BotServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/BotServiceBean.java @@ -3,6 +3,10 @@ package dev.sheldan.abstracto.core.service; import dev.sheldan.abstracto.core.logging.OkHttpLogger; import dev.sheldan.abstracto.core.metric.OkHttpMetrics; import dev.sheldan.abstracto.core.models.SystemInfo; +import io.micrometer.context.ContextExecutorService; +import io.micrometer.context.ContextSnapshotFactory; +import io.micrometer.core.instrument.binder.okhttp3.OkHttpObservationInterceptor; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; @@ -11,9 +15,13 @@ import net.dv8tion.jda.api.utils.ChunkingFilter; import net.dv8tion.jda.api.utils.MemberCachePolicy; import net.dv8tion.jda.api.utils.cache.CacheFlag; import net.dv8tion.jda.internal.utils.IOUtil; +import okhttp3.Dispatcher; import okhttp3.OkHttpClient; +import okhttp3.internal.Util; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Service; import java.lang.management.ManagementFactory; @@ -24,6 +32,9 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; @Service @Slf4j @@ -46,6 +57,18 @@ public class BotServiceBean implements BotService { @Value("${abstracto.memberCachePolicy:ALL}") private String memberCachePolicy; + @Autowired + @Qualifier("genericExecutor") + private TaskExecutor genericExecutor; + + @Autowired + private ObservationRegistry observationRegistry; + + private OkHttpObservationInterceptor.Builder defaultInterceptorBuilder() { + return OkHttpObservationInterceptor.builder(observationRegistry, "okhttp.requests") + .uriMapper(req -> req.url().encodedPath()); + } + private static final Map POSSIBLE_MEMBER_CACHE_POLICIES = new HashMap<>(); static { @@ -84,6 +107,10 @@ public class BotServiceBean implements BotService { OkHttpClient.Builder defaultBuilder = IOUtil.newHttpClientBuilder(); defaultBuilder.addInterceptor(okHttpMetrics); defaultBuilder.addInterceptor(okHttpLogger); + ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, + new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false)); + defaultBuilder.dispatcher(new Dispatcher(ContextExecutorService.wrap(executor, ContextSnapshotFactory.builder().build()::captureAll))); + defaultBuilder.addInterceptor(defaultInterceptorBuilder().build()); builder.setHttpClientBuilder(defaultBuilder); this.instance = builder.build(); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java index 9d16f7e29..528cb5623 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java @@ -14,6 +14,8 @@ import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.CompletableFutureList; import dev.sheldan.abstracto.core.utils.FileService; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; @@ -35,6 +37,7 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -77,6 +80,9 @@ public class ChannelServiceBean implements ChannelService { @Autowired private ServerManagementService serverManagementService; + @Autowired + private Tracer tracer; + public static final CounterMetric CHANNEL_CREATE_METRIC = CounterMetric .builder() .name(DISCORD_API_INTERACTION_METRIC) @@ -209,6 +215,9 @@ public class ChannelServiceBean implements ChannelService { @Override public List> sendMessageToSendToChannel(MessageToSend messageToSend, MessageChannel textChannel) { + Span newSpan = tracer.nextSpan().name("send-message"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + newSpan.tag("channel.id", textChannel.getIdLong()); messageToSend.setEphemeral(false); if(textChannel instanceof GuildMessageChannel guildMessageChannel) { long maxFileSize = guildMessageChannel.getGuild().getMaxFileSize(); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ListenerServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ListenerServiceBean.java index 0764b8168..8bf5397fa 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ListenerServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ListenerServiceBean.java @@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.service; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.listener.*; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.task.TaskExecutor; @@ -84,7 +85,7 @@ public class ListenerServiceBean implements ListenerService { return; } try { - CompletableFuture.runAsync(() -> self.executeFeatureListenerInTransaction(listener, model), executor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeFeatureListenerInTransaction(listener, model), MetricUtils.wrapExecutor(executor)).exceptionally(throwable -> { log.error("Feature aware async Listener {} failed with async exception:", listener.getClass().getName(), throwable); return null; }); @@ -111,7 +112,7 @@ public class ListenerServiceBean implements ListenerService { @Override public void executeListener(AbstractoListener listener, T model, TaskExecutor executor) { try { - CompletableFuture.runAsync(() -> self.executeListenerInTransaction(listener, model), executor).exceptionally(throwable -> { + CompletableFuture.runAsync(() -> self.executeListenerInTransaction(listener, model), MetricUtils.wrapExecutor(executor)).exceptionally(throwable -> { log.error("Async Listener {} failed with async exception:", listener.getClass().getName(), throwable); return null; }); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java index 9db148ba0..bfb4fa110 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.model.database.ACommand; import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService; import dev.sheldan.abstracto.core.command.service.management.CommandManagementService; import dev.sheldan.abstracto.core.listener.AsyncStartupListener; +import dev.sheldan.abstracto.core.metric.service.MetricUtils; import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannelType; import dev.sheldan.abstracto.core.models.database.ARole; @@ -21,6 +22,8 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.apache.commons.collections4.SetUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -65,6 +68,10 @@ public class StartupServiceBean implements Startup { @Autowired private ProfanityService profanityService; + @Autowired + @Qualifier("genericExecutor") + private TaskExecutor genericExecutor; + @Override public void startBot() throws LoginException { service.login(); @@ -95,7 +102,7 @@ public class StartupServiceBean implements Startup { } catch (Exception e) { log.error("Startup listener {} failed.", asyncStartupListener, e); } - }).thenAccept(unused -> log.info("Startup listener {} finished.", asyncStartupListener)) + }, MetricUtils.wrapExecutor(genericExecutor)).thenAccept(unused -> log.info("Startup listener {} finished.", asyncStartupListener)) .exceptionally(throwable -> { log.error("Startup listener {} failed.", asyncStartupListener, throwable); return null; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/loading/DatabaseTemplateLoader.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/loading/DatabaseTemplateLoader.java index a0e2089ca..e51c2eb0f 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/loading/DatabaseTemplateLoader.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/loading/DatabaseTemplateLoader.java @@ -2,9 +2,10 @@ package dev.sheldan.abstracto.core.templating.loading; import dev.sheldan.abstracto.core.config.ServerContext; import dev.sheldan.abstracto.core.templating.model.EffectiveTemplate; -import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.management.EffectiveTemplateManagementService; import freemarker.cache.TemplateLoader; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -26,6 +27,9 @@ public class DatabaseTemplateLoader implements TemplateLoader { @Autowired private ServerContext serverContext; + @Autowired + private Tracer tracer; + /** * Loads the content of the template object * @param s The key of the template to load @@ -34,6 +38,9 @@ public class DatabaseTemplateLoader implements TemplateLoader { @Override public Object findTemplateSource(String s) throws IOException { Optional templateByKey; + Span newSpan = tracer.nextSpan().name("load-template"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + newSpan.tag("template.key", s); if(s.contains("/")) { String[] parts = s.split("/"); templateByKey = effectiveTemplateManagementService.getTemplateByKeyAndServer(parts[1], Long.parseLong(parts[0])); @@ -41,6 +48,9 @@ public class DatabaseTemplateLoader implements TemplateLoader { templateByKey = effectiveTemplateManagementService.getTemplateByKey(s); } return templateByKey.orElse(null); + } finally { + newSpan.end(); + } } @Override diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java index 9b12eeb89..4c1b739ee 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java @@ -32,6 +32,8 @@ import dev.sheldan.abstracto.core.utils.FileService; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -97,6 +99,10 @@ public class TemplateServiceBean implements TemplateService { @Autowired private FileService fileService; + @Autowired + private Tracer tracer; + + /** * Formats the passed count with the embed used for formatting pages. * diff --git a/abstracto-application/core/core-int/pom.xml b/abstracto-application/core/core-int/pom.xml index 208973916..25fa45881 100644 --- a/abstracto-application/core/core-int/pom.xml +++ b/abstracto-application/core/core-int/pom.xml @@ -48,5 +48,10 @@ com.github.everit-org.json-schema org.everit.json.schema + + dev.sheldan.abstracto.core + metrics-int + ${project.version} + \ No newline at end of file diff --git a/abstracto-application/core/metrics-impl/src/main/java/dev/sheldan/abstracto/core/metric/config/ObservationConfig.java b/abstracto-application/core/metrics-impl/src/main/java/dev/sheldan/abstracto/core/metric/config/ObservationConfig.java new file mode 100644 index 000000000..171345143 --- /dev/null +++ b/abstracto-application/core/metrics-impl/src/main/java/dev/sheldan/abstracto/core/metric/config/ObservationConfig.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.core.metric.config; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObservationConfig { + @Bean + ObservedAspect observedAspect(ObservationRegistry registry) { + return new ObservedAspect(registry); + } +} diff --git a/abstracto-application/core/metrics-int/pom.xml b/abstracto-application/core/metrics-int/pom.xml index 00b081af8..4a4d4030b 100644 --- a/abstracto-application/core/metrics-int/pom.xml +++ b/abstracto-application/core/metrics-int/pom.xml @@ -9,5 +9,25 @@ metrics-int + + + io.micrometer + micrometer-tracing-bridge-brave + + + io.zipkin.reporter2 + zipkin-reporter-brave + + + org.springframework.boot + spring-boot-starter-aop + + + net.ttddyy.observation + datasource-micrometer-spring-boot + 1.0.5 + + + \ No newline at end of file diff --git a/abstracto-application/core/metrics-int/src/main/java/dev/sheldan/abstracto/core/metric/service/MetricUtils.java b/abstracto-application/core/metrics-int/src/main/java/dev/sheldan/abstracto/core/metric/service/MetricUtils.java new file mode 100644 index 000000000..a0b0363f5 --- /dev/null +++ b/abstracto-application/core/metrics-int/src/main/java/dev/sheldan/abstracto/core/metric/service/MetricUtils.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.core.metric.service; + +import io.micrometer.context.ContextSnapshotFactory; + +import java.util.concurrent.Executor; + +public class MetricUtils { + public static Executor wrapExecutor(Executor e) { + return ContextSnapshotFactory + .builder() + .build() + .captureAll() + .wrapExecutor(e); + } +}