Skip to content

Commit 2279bf2

Browse files
authored
Logging - links logs to source (#857)
* Added sourceCodeBaseUrl entry in config * Added linkable title functionality * Requested changes
1 parent 27b97d3 commit 2279bf2

File tree

5 files changed

+63
-14
lines changed

5 files changed

+63
-14
lines changed

application/config.json.template

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"wsh"
8888
],
8989
"logInfoChannelWebhook": "<put_your_webhook_here>",
90-
"logErrorChannelWebhook": "<put_your_webhook_here>"
91-
"openaiApiKey": "<check pins in #tjbot_discussion for the key>"
90+
"logErrorChannelWebhook": "<put_your_webhook_here>",
91+
"openaiApiKey": "<check pins in #tjbot_discussion for the key>",
92+
"sourceCodeBaseUrl": "<https://github.com/<your_account_here>/<your_repo_here>/blob/master/application/src/main/java/>"
9293
}

application/src/main/java/org/togetherjava/tjbot/config/Config.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public final class Config {
3737
private final String logInfoChannelWebhook;
3838
private final String logErrorChannelWebhook;
3939
private final String openaiApiKey;
40+
private final String sourceCodeBaseUrl;
4041

4142
@SuppressWarnings("ConstructorWithTooManyParameters")
4243
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
@@ -72,7 +73,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
7273
required = true) String logInfoChannelWebhook,
7374
@JsonProperty(value = "logErrorChannelWebhook",
7475
required = true) String logErrorChannelWebhook,
75-
@JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey) {
76+
@JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey,
77+
@JsonProperty(value = "sourceCodeBaseUrl", required = true) String sourceCodeBaseUrl) {
7678
this.token = Objects.requireNonNull(token);
7779
this.gistApiKey = Objects.requireNonNull(gistApiKey);
7880
this.databasePath = Objects.requireNonNull(databasePath);
@@ -96,6 +98,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
9698
this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook);
9799
this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook);
98100
this.openaiApiKey = Objects.requireNonNull(openaiApiKey);
101+
this.sourceCodeBaseUrl = Objects.requireNonNull(sourceCodeBaseUrl);
99102
}
100103

101104
/**
@@ -316,4 +319,15 @@ public String getLogErrorChannelWebhook() {
316319
public String getOpenaiApiKey() {
317320
return openaiApiKey;
318321
}
322+
323+
/**
324+
* The base URL of the source code of this bot. E.g.
325+
* {@code getSourceCodeBaseUrl() + "/org/togetherjava/tjbot/config/Config.java"} would point to
326+
* this file.
327+
*
328+
* @return the base url of the source code of this bot
329+
*/
330+
public String getSourceCodeBaseUrl() {
331+
return sourceCodeBaseUrl;
332+
}
319333
}

application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogAppender.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ final class DiscordLogAppender extends AbstractAppender {
1919
private final DiscordLogForwarder logForwarder;
2020

2121
private DiscordLogAppender(String name, Filter filter, StringLayout layout,
22-
boolean ignoreExceptions, URI webhook) {
22+
boolean ignoreExceptions, URI webhook, String sourceCodeBaseUrl) {
2323
super(name, filter, layout, ignoreExceptions, NO_PROPERTIES);
2424

25-
logForwarder = new DiscordLogForwarder(webhook);
25+
logForwarder = new DiscordLogForwarder(webhook, sourceCodeBaseUrl);
2626
}
2727

2828
@Override
@@ -43,11 +43,19 @@ static final class DiscordLogAppenderBuilder
4343
@Required
4444
private URI webhook;
4545

46+
@Required
47+
private String sourceCodeBaseUrl;
48+
4649
public DiscordLogAppenderBuilder setWebhook(URI webhook) {
4750
this.webhook = webhook;
4851
return asBuilder();
4952
}
5053

54+
public DiscordLogAppenderBuilder setSourceCodeBaseUrl(String sourceCodeBaseUrl) {
55+
this.sourceCodeBaseUrl = sourceCodeBaseUrl;
56+
return asBuilder();
57+
}
58+
5159
@Override
5260
public DiscordLogAppender build() {
5361
Layout<? extends Serializable> layout = getOrCreateLayout();
@@ -58,7 +66,7 @@ public DiscordLogAppender build() {
5866
String name = Objects.requireNonNull(getName());
5967

6068
return new DiscordLogAppender(name, getFilter(), (StringLayout) layout,
61-
isIgnoreExceptions(), webhook);
69+
isIgnoreExceptions(), webhook, sourceCodeBaseUrl);
6270
}
6371
}
6472
}

application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.apache.logging.log4j.Level;
88
import org.apache.logging.log4j.core.LogEvent;
99
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
1213

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

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

80-
DiscordLogForwarder(URI webhook) {
82+
DiscordLogForwarder(URI webhook, String sourceCodeBaseUrl) {
8183
webhookClient = WebhookClient.withUrl(webhook.toString());
8284

85+
if (!sourceCodeBaseUrl.endsWith("/")) {
86+
this.sourceCodeBaseUrl = sourceCodeBaseUrl + "/";
87+
} else {
88+
this.sourceCodeBaseUrl = sourceCodeBaseUrl;
89+
}
90+
8391
SERVICE.scheduleWithFixedDelay(this::processPendingLogs, 5, 5, TimeUnit.SECONDS);
8492
}
8593

@@ -110,7 +118,7 @@ void forwardLogEvent(LogEvent event) {
110118
""");
111119
}
112120

113-
LogMessage log = LogMessage.ofEvent(event);
121+
LogMessage log = LogMessage.ofEvent(event, sourceCodeBaseUrl);
114122

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

163-
private static LogMessage ofEvent(LogEvent event) {
171+
private static final String BASE_PACKAGE = "org.togetherjava.tjbot.";
172+
173+
private static LogMessage ofEvent(LogEvent event, String sourceCodeBaseUrl) {
164174
String authorName = event.getLoggerName();
175+
String authorUrl = linkToSource(event.getSource(), sourceCodeBaseUrl).orElse(null);
165176
String title = event.getLevel().name();
166177
int colorDecimal = Objects.requireNonNull(LEVEL_TO_AMBIENT_COLOR.get(event.getLevel()));
167178
String description =
168179
MessageUtils.abbreviate(describeLogEvent(event), MAX_EMBED_DESCRIPTION);
169180
Instant timestamp = Instant.ofEpochMilli(event.getInstant().getEpochMillisecond());
170181

171182
WebhookEmbed embed = new WebhookEmbedBuilder()
172-
.setAuthor(new WebhookEmbed.EmbedAuthor(authorName, null, null))
183+
.setAuthor(new WebhookEmbed.EmbedAuthor(authorName, null, authorUrl))
173184
.setTitle(new WebhookEmbed.EmbedTitle(title, null))
174185
.setDescription(description)
175186
.setColor(colorDecimal)
176187
.setTimestamp(timestamp)
177188
.build();
178-
179189
return new LogMessage(embed, timestamp);
180190
}
181191

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

206+
private static Optional<String> linkToSource(@Nullable StackTraceElement sourceElement,
207+
String sourceCodeBaseUrl) {
208+
if (sourceElement == null) {
209+
return Optional.empty();
210+
}
211+
212+
String source = sourceElement.getClassName();
213+
if (!source.startsWith(BASE_PACKAGE)) {
214+
return Optional.empty();
215+
}
216+
217+
String link = "%s%s.java".formatted(sourceCodeBaseUrl, source.replace('.', '/'));
218+
return Optional.of(link);
219+
}
220+
196221
private LogMessage shortened() {
197222
String shortDescription = MessageUtils.abbreviate(
198223
Objects.requireNonNull(embed.getDescription()), MAX_EMBED_DESCRIPTION_SHORT);

application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogging.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ public static void startDiscordLogging(Config botConfig) {
4747
private static void addAppenders(Configuration logConfig, Config botConfig) {
4848
parseWebhookUri(botConfig.getLogInfoChannelWebhook())
4949
.ifPresent(webhookUri -> addDiscordLogAppender("DiscordInfo", createInfoRangeFilter(),
50-
webhookUri, logConfig));
50+
webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig));
5151

5252
parseWebhookUri(botConfig.getLogErrorChannelWebhook())
5353
.ifPresent(webhookUri -> addDiscordLogAppender("DiscordError", createErrorRangeFilter(),
54-
webhookUri, logConfig));
54+
webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig));
5555
}
5656

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

0 commit comments

Comments
 (0)