Skip to content

Commit

Permalink
Logging - links logs to source (#857)
Browse files Browse the repository at this point in the history
* Added sourceCodeBaseUrl entry in config

* Added linkable title functionality

* Requested changes
  • Loading branch information
SquidXTV authored Jul 19, 2023
1 parent 27b97d3 commit 2279bf2
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 14 deletions.
5 changes: 3 additions & 2 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"wsh"
],
"logInfoChannelWebhook": "<put_your_webhook_here>",
"logErrorChannelWebhook": "<put_your_webhook_here>"
"openaiApiKey": "<check pins in #tjbot_discussion for the key>"
"logErrorChannelWebhook": "<put_your_webhook_here>",
"openaiApiKey": "<check pins in #tjbot_discussion for the key>",
"sourceCodeBaseUrl": "<https://github.com/<your_account_here>/<your_repo_here>/blob/master/application/src/main/java/>"
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public final class Config {
private final String logInfoChannelWebhook;
private final String logErrorChannelWebhook;
private final String openaiApiKey;
private final String sourceCodeBaseUrl;

@SuppressWarnings("ConstructorWithTooManyParameters")
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
Expand Down Expand Up @@ -72,7 +73,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
required = true) String logInfoChannelWebhook,
@JsonProperty(value = "logErrorChannelWebhook",
required = true) String logErrorChannelWebhook,
@JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey) {
@JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey,
@JsonProperty(value = "sourceCodeBaseUrl", required = true) String sourceCodeBaseUrl) {
this.token = Objects.requireNonNull(token);
this.gistApiKey = Objects.requireNonNull(gistApiKey);
this.databasePath = Objects.requireNonNull(databasePath);
Expand All @@ -96,6 +98,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook);
this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook);
this.openaiApiKey = Objects.requireNonNull(openaiApiKey);
this.sourceCodeBaseUrl = Objects.requireNonNull(sourceCodeBaseUrl);
}

/**
Expand Down Expand Up @@ -316,4 +319,15 @@ public String getLogErrorChannelWebhook() {
public String getOpenaiApiKey() {
return openaiApiKey;
}

/**
* The base URL of the source code of this bot. E.g.
* {@code getSourceCodeBaseUrl() + "/org/togetherjava/tjbot/config/Config.java"} would point to
* this file.
*
* @return the base url of the source code of this bot
*/
public String getSourceCodeBaseUrl() {
return sourceCodeBaseUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ final class DiscordLogAppender extends AbstractAppender {
private final DiscordLogForwarder logForwarder;

private DiscordLogAppender(String name, Filter filter, StringLayout layout,
boolean ignoreExceptions, URI webhook) {
boolean ignoreExceptions, URI webhook, String sourceCodeBaseUrl) {
super(name, filter, layout, ignoreExceptions, NO_PROPERTIES);

logForwarder = new DiscordLogForwarder(webhook);
logForwarder = new DiscordLogForwarder(webhook, sourceCodeBaseUrl);
}

@Override
Expand All @@ -43,11 +43,19 @@ static final class DiscordLogAppenderBuilder
@Required
private URI webhook;

@Required
private String sourceCodeBaseUrl;

public DiscordLogAppenderBuilder setWebhook(URI webhook) {
this.webhook = webhook;
return asBuilder();
}

public DiscordLogAppenderBuilder setSourceCodeBaseUrl(String sourceCodeBaseUrl) {
this.sourceCodeBaseUrl = sourceCodeBaseUrl;
return asBuilder();
}

@Override
public DiscordLogAppender build() {
Layout<? extends Serializable> layout = getOrCreateLayout();
Expand All @@ -58,7 +66,7 @@ public DiscordLogAppender build() {
String name = Objects.requireNonNull(getName());

return new DiscordLogAppender(name, getFilter(), (StringLayout) layout,
isIgnoreExceptions(), webhook);
isIgnoreExceptions(), webhook, sourceCodeBaseUrl);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -70,16 +71,23 @@ final class DiscordLogForwarder {
0xDFDF00, Level.ERROR, 0xBF2200, Level.FATAL, 0xFF8484);

private final WebhookClient webhookClient;
private final String sourceCodeBaseUrl;
/**
* Internal buffer of logs that still have to be forwarded to Discord. Actions are synchronized
* using {@link #pendingLogsLock} to ensure thread safety.
*/
private final Queue<LogMessage> pendingLogs = new PriorityQueue<>();
private final Object pendingLogsLock = new Object();

DiscordLogForwarder(URI webhook) {
DiscordLogForwarder(URI webhook, String sourceCodeBaseUrl) {
webhookClient = WebhookClient.withUrl(webhook.toString());

if (!sourceCodeBaseUrl.endsWith("/")) {
this.sourceCodeBaseUrl = sourceCodeBaseUrl + "/";
} else {
this.sourceCodeBaseUrl = sourceCodeBaseUrl;
}

SERVICE.scheduleWithFixedDelay(this::processPendingLogs, 5, 5, TimeUnit.SECONDS);
}

Expand Down Expand Up @@ -110,7 +118,7 @@ void forwardLogEvent(LogEvent event) {
""");
}

LogMessage log = LogMessage.ofEvent(event);
LogMessage log = LogMessage.ofEvent(event, sourceCodeBaseUrl);

synchronized (pendingLogsLock) {
pendingLogs.add(log);
Expand Down Expand Up @@ -160,22 +168,24 @@ private List<LogMessage> validateBatch(List<LogMessage> logBatch) {
private record LogMessage(WebhookEmbed embed,
Instant timestamp) implements Comparable<LogMessage> {

private static LogMessage ofEvent(LogEvent event) {
private static final String BASE_PACKAGE = "org.togetherjava.tjbot.";

private static LogMessage ofEvent(LogEvent event, String sourceCodeBaseUrl) {
String authorName = event.getLoggerName();
String authorUrl = linkToSource(event.getSource(), sourceCodeBaseUrl).orElse(null);
String title = event.getLevel().name();
int colorDecimal = Objects.requireNonNull(LEVEL_TO_AMBIENT_COLOR.get(event.getLevel()));
String description =
MessageUtils.abbreviate(describeLogEvent(event), MAX_EMBED_DESCRIPTION);
Instant timestamp = Instant.ofEpochMilli(event.getInstant().getEpochMillisecond());

WebhookEmbed embed = new WebhookEmbedBuilder()
.setAuthor(new WebhookEmbed.EmbedAuthor(authorName, null, null))
.setAuthor(new WebhookEmbed.EmbedAuthor(authorName, null, authorUrl))
.setTitle(new WebhookEmbed.EmbedTitle(title, null))
.setDescription(description)
.setColor(colorDecimal)
.setTimestamp(timestamp)
.build();

return new LogMessage(embed, timestamp);
}

Expand All @@ -193,6 +203,21 @@ private static String describeLogEvent(LogEvent event) {
return logMessage + "\n" + exceptionWriter.toString().replace("\t", "> ");
}

private static Optional<String> linkToSource(@Nullable StackTraceElement sourceElement,
String sourceCodeBaseUrl) {
if (sourceElement == null) {
return Optional.empty();
}

String source = sourceElement.getClassName();
if (!source.startsWith(BASE_PACKAGE)) {
return Optional.empty();
}

String link = "%s%s.java".formatted(sourceCodeBaseUrl, source.replace('.', '/'));
return Optional.of(link);
}

private LogMessage shortened() {
String shortDescription = MessageUtils.abbreviate(
Objects.requireNonNull(embed.getDescription()), MAX_EMBED_DESCRIPTION_SHORT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ public static void startDiscordLogging(Config botConfig) {
private static void addAppenders(Configuration logConfig, Config botConfig) {
parseWebhookUri(botConfig.getLogInfoChannelWebhook())
.ifPresent(webhookUri -> addDiscordLogAppender("DiscordInfo", createInfoRangeFilter(),
webhookUri, logConfig));
webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig));

parseWebhookUri(botConfig.getLogErrorChannelWebhook())
.ifPresent(webhookUri -> addDiscordLogAppender("DiscordError", createErrorRangeFilter(),
webhookUri, logConfig));
webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig));
}

private static Optional<URI> parseWebhookUri(String webhookUri) {
Expand All @@ -71,7 +71,7 @@ private static Optional<URI> parseWebhookUri(String webhookUri) {
// to the config.
@SuppressWarnings("squid:S4792")
private static void addDiscordLogAppender(String name, Filter filter, URI webhookUri,
Configuration logConfig) {
String sourceCodeBaseUrl, Configuration logConfig) {
// NOTE The whole setup is done programmatically in order to allow the webhooks
// to be read from the config file
Filter[] filters = {filter, createDenyMarkerFilter(LogMarkers.NO_DISCORD.getName()),
Expand All @@ -80,6 +80,7 @@ private static void addDiscordLogAppender(String name, Filter filter, URI webhoo
Appender appender = DiscordLogAppender.newBuilder()
.setName(name)
.setWebhook(webhookUri)
.setSourceCodeBaseUrl(sourceCodeBaseUrl)
.setFilter(CompositeFilter.createFilters(filters))
.build();

Expand Down

0 comments on commit 2279bf2

Please sign in to comment.