Skip to content

Commit 9fb9c24

Browse files
authored
Use structured logging (#489)
1 parent bdffb43 commit 9fb9c24

File tree

15 files changed

+173
-458
lines changed

15 files changed

+173
-458
lines changed

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ dependencies {
1515
compileOnly(libs.jspecify)
1616
implementation(libs.gson)
1717
implementation(libs.logback.classic)
18+
implementation(libs.logstash.logback.encoder)
1819
implementation(libs.telegram.bot.api)
1920
implementation(libs.tika)
2021

22+
constraints {
23+
implementation(libs.jackson.core)
24+
}
25+
2126
testCompileOnly(libs.jspecify)
2227
testImplementation(libs.hamcrest)
2328
testImplementation(libs.junit.jupiter)

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ junit = "6.0.3"
44
[libraries]
55
gson = "com.google.code.gson:gson:2.13.2"
66
hamcrest = "org.hamcrest:hamcrest:3.0"
7+
jackson-core = "tools.jackson.core:jackson-core:3.1.2"
78
jspecify = "org.jspecify:jspecify:1.0.0"
89
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
910
junit-platform = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit" }
1011
logback-classic = "ch.qos.logback:logback-classic:1.5.32"
12+
logstash-logback-encoder = "net.logstash.logback:logstash-logback-encoder:9.0"
1113
mockwebserver = "com.squareup.okhttp3:mockwebserver3-junit5:5.3.2"
1214
telegram-bot-api = "com.github.pengrad:java-telegram-bot-api:9.6.0"
1315
tika = "org.apache.tika:tika-core:3.3.0"

src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.stickerifier.stickerify.bot;
22

3+
import static com.github.stickerifier.stickerify.logger.StructuredLogger.REQUEST_DETAILS;
34
import static com.github.stickerifier.stickerify.telegram.Answer.CORRUPTED;
45
import static com.github.stickerifier.stickerify.telegram.Answer.ERROR;
56
import static com.github.stickerifier.stickerify.telegram.Answer.FILE_ALREADY_VALID;
@@ -9,11 +10,10 @@
910
import static java.util.HashSet.newHashSet;
1011
import static java.util.concurrent.Executors.newThreadPerTaskExecutor;
1112

12-
import com.github.stickerifier.stickerify.exception.BaseException;
1313
import com.github.stickerifier.stickerify.exception.CorruptedFileException;
1414
import com.github.stickerifier.stickerify.exception.FileOperationException;
15-
import com.github.stickerifier.stickerify.exception.MediaException;
1615
import com.github.stickerifier.stickerify.exception.TelegramApiException;
16+
import com.github.stickerifier.stickerify.logger.StructuredLogger;
1717
import com.github.stickerifier.stickerify.media.MediaHelper;
1818
import com.github.stickerifier.stickerify.telegram.Answer;
1919
import com.github.stickerifier.stickerify.telegram.model.TelegramFile;
@@ -31,8 +31,7 @@
3131
import com.pengrad.telegrambot.request.SendDocument;
3232
import com.pengrad.telegrambot.request.SendMessage;
3333
import com.pengrad.telegrambot.response.BaseResponse;
34-
import org.slf4j.Logger;
35-
import org.slf4j.LoggerFactory;
34+
import org.slf4j.event.Level;
3635

3736
import java.io.File;
3837
import java.io.IOException;
@@ -51,7 +50,7 @@
5150
*/
5251
public record Stickerify(TelegramBot bot, Executor executor) implements UpdatesListener, ExceptionHandler, AutoCloseable {
5352

54-
private static final Logger LOGGER = LoggerFactory.getLogger(Stickerify.class);
53+
private static final StructuredLogger LOGGER = new StructuredLogger(Stickerify.class);
5554
private static final String BOT_TOKEN = System.getenv("STICKERIFY_TOKEN");
5655
private static final ThreadFactory VIRTUAL_THREAD_FACTORY = Thread.ofVirtual().name("Virtual-", 0).factory();
5756

@@ -78,9 +77,7 @@ public int process(List<Update> updates) {
7877
updates.forEach(update -> executor.execute(() -> {
7978
if (update.message() != null) {
8079
var request = new TelegramRequest(update.message());
81-
LOGGER.atInfo().log("Received {}", request.getDescription());
82-
83-
answer(request);
80+
ScopedValue.where(REQUEST_DETAILS, request.toRequestDetails()).run(() -> answer(request));
8481
}
8582
}));
8683

@@ -89,7 +86,7 @@ public int process(List<Update> updates) {
8986

9087
@Override
9188
public void onException(TelegramException e) {
92-
LOGGER.atError().log("There was an unexpected failure: {}", e.getMessage());
89+
LOGGER.at(Level.ERROR).setCause(e).addKeyValue("exception_message", e.getMessage()).log("An unexpected failure occurred");
9390
}
9491

9592
@Override
@@ -104,12 +101,14 @@ public void close() {
104101
}
105102

106103
private void answer(TelegramRequest request) {
104+
LOGGER.at(Level.INFO).log("Received request");
105+
107106
var file = request.getFile();
108107

109-
if (file != null) {
110-
answerFile(request, file);
111-
} else {
108+
if (file == null) {
112109
answerText(request);
110+
} else {
111+
answerFile(request, file);
113112
}
114113
}
115114

@@ -119,7 +118,7 @@ private void answerFile(TelegramRequest request, TelegramFile file) {
119118
} else if (file.canBeDownloaded()) {
120119
answerFile(request, file.id());
121120
} else {
122-
LOGGER.atInfo().log("Passed-in file is too large");
121+
LOGGER.at(Level.INFO).log("Passed-in file is too large");
123122

124123
answerText(FILE_TOO_LARGE, request);
125124
}
@@ -147,10 +146,10 @@ private void answerFile(TelegramRequest request, String fileId) {
147146

148147
execute(answerWithFile);
149148
}
150-
} catch (TelegramApiException | MediaException e) {
151-
processFailure(request, e, fileId);
152149
} catch (InterruptedException e) {
153150
Thread.currentThread().interrupt();
151+
} catch (Exception e) {
152+
processFailure(request, e, fileId);
154153
} finally {
155154
deleteTempFiles(pathsToDelete);
156155
}
@@ -170,36 +169,44 @@ private File retrieveFile(String fileId) throws TelegramApiException, FileOperat
170169
}
171170
}
172171

173-
private void processFailure(TelegramRequest request, BaseException e, String fileId) {
172+
private void processFailure(TelegramRequest request, Exception e, String fileId) {
174173
if (e instanceof TelegramApiException telegramException) {
175-
processTelegramFailure(request.getDescription(), telegramException, false);
174+
boolean replyToUser = processTelegramFailure(telegramException, false);
175+
if (!replyToUser) {
176+
return;
177+
}
176178
}
177179

178180
if (e instanceof CorruptedFileException) {
179-
LOGGER.atInfo().log("Unable to reply to the {}: the file is corrupted", request.getDescription());
181+
LOGGER.at(Level.INFO).log("Unable to reply to the request: the file is corrupted");
180182
answerText(CORRUPTED, request);
181183
} else {
182-
LOGGER.atWarn().setCause(e).log("Unable to process the file {}", fileId);
184+
LOGGER.at(Level.WARN).setCause(e).addKeyValue("file_id", fileId).log("Unable to process file");
183185
answerText(ERROR, request);
184186
}
185187
}
186188

187-
private void processTelegramFailure(String requestDescription, TelegramApiException e, boolean logUnmatchedFailure) {
189+
private boolean processTelegramFailure(TelegramApiException e, boolean logUnmatchedFailure) {
190+
boolean replyToUser = false;
191+
188192
switch (e.getDescription()) {
189-
case "Bad Request: message to be replied not found" -> LOGGER.atInfo().log("Unable to reply to the {}: the message sent has been deleted", requestDescription);
190-
case "Forbidden: bot was blocked by the user" -> LOGGER.atInfo().log("Unable to reply to the {}: the user blocked the bot", requestDescription);
193+
case "Bad Request: message to be replied not found" -> LOGGER.at(Level.INFO).log("Unable to reply to the request: the message sent has been deleted");
194+
case "Forbidden: bot was blocked by the user" -> LOGGER.at(Level.INFO).log("Unable to reply to the request: the user blocked the bot");
191195
default -> {
192196
if (logUnmatchedFailure) {
193-
LOGGER.atError().setCause(e).log("Unable to reply to the {}", requestDescription);
197+
LOGGER.at(Level.ERROR).setCause(e).log("Unable to reply to the request");
194198
}
199+
replyToUser = true;
195200
}
196201
}
202+
203+
return replyToUser;
197204
}
198205

199206
private void answerText(TelegramRequest request) {
200207
var message = request.message();
201208
if (message.text() == null) {
202-
LOGGER.atInfo().log("An unhandled message type has been received: {}", message);
209+
LOGGER.at(Level.INFO).log("An unhandled message type has been received");
203210
}
204211

205212
answerText(request.getAnswerMessage(), request);
@@ -216,7 +223,7 @@ private void answerText(Answer answer, TelegramRequest request) {
216223
try {
217224
execute(answerWithText);
218225
} catch (TelegramApiException e) {
219-
processTelegramFailure(request.getDescription(), e, true);
226+
processTelegramFailure(e, true);
220227
}
221228
}
222229

@@ -234,10 +241,10 @@ private static void deleteTempFiles(Set<Path> pathsToDelete) {
234241
for (var path : pathsToDelete) {
235242
try {
236243
if (!Files.deleteIfExists(path)) {
237-
LOGGER.atInfo().log("Unable to delete temp file {}", path);
244+
LOGGER.at(Level.INFO).addKeyValue("file_path", path).log("Unable to delete temp file");
238245
}
239246
} catch (IOException e) {
240-
LOGGER.atError().setCause(e).log("An error occurred trying to delete temp file {}", path);
247+
LOGGER.at(Level.ERROR).setCause(e).addKeyValue("file_path", path).log("An error occurred trying to delete temp file");
241248
}
242249
}
243250
}

src/main/java/com/github/stickerifier/stickerify/logger/ExceptionHighlighter.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/main/java/com/github/stickerifier/stickerify/logger/HighlightHelper.java

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/main/java/com/github/stickerifier/stickerify/logger/MessageHighlighter.java

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.stickerifier.stickerify.logger;
2+
3+
import com.github.stickerifier.stickerify.telegram.model.TelegramRequest.RequestDetails;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import org.slf4j.event.Level;
7+
import org.slf4j.spi.LoggingEventBuilder;
8+
9+
public record StructuredLogger(Logger logger) {
10+
11+
public static final ScopedValue<RequestDetails> REQUEST_DETAILS = ScopedValue.newInstance();
12+
public static final ScopedValue<String> MIME_TYPE = ScopedValue.newInstance();
13+
14+
public StructuredLogger(Class<?> clazz) {
15+
this(LoggerFactory.getLogger(clazz));
16+
}
17+
18+
/**
19+
* Creates a {@link LoggingEventBuilder} at the specified level with request details and MIME type information, if set.
20+
*
21+
* @param level the level of the log
22+
* @return the log builder with context information
23+
*/
24+
public LoggingEventBuilder at(Level level) {
25+
var logBuilder = logger.atLevel(level);
26+
27+
if (REQUEST_DETAILS.isBound()) {
28+
logBuilder = logBuilder.addKeyValue("request_details", REQUEST_DETAILS.get());
29+
}
30+
if (MIME_TYPE.isBound()) {
31+
logBuilder = logBuilder.addKeyValue("mime_type", MIME_TYPE.get());
32+
}
33+
34+
return logBuilder;
35+
}
36+
}

0 commit comments

Comments
 (0)