diff --git a/Dockerfile b/Dockerfile index 18d142a4..c445c4aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,17 @@ RUN --mount=type=cache,target=/root/.gradle ./gradlew check installDist --no-dae FROM alpine:3.24.2 +# bump: OpenTelemetry /opentelemetry-java-instrumentation\/releases\/download\/v([\d.]+)/ git:https://github.com/open-telemetry/opentelemetry-java-instrumentation.git|/\d+\./|* +ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.31.1/opentelemetry-javaagent.jar \ + /app/opentelemetry-agent.jar + COPY --from=builder /usr/bin/ff* /usr/bin/ COPY --from=builder /app/build/install/Stickerify/ . +ENV OTEL_SDK_DISABLED=true +ENV OTEL_METRICS_EXPORTER=none +ENV OTEL_LOGS_EXPORTER=none +ENV OTEL_JAVAAGENT_LOGGING=none +ENV JAVA_TOOL_OPTIONS=-javaagent:/app/opentelemetry-agent.jar ENV CONCURRENT_PROCESSES=5 CMD ["./bin/Stickerify"] diff --git a/Railway.dockerfile b/Railway.dockerfile index 06017f82..3cf830c0 100644 --- a/Railway.dockerfile +++ b/Railway.dockerfile @@ -5,4 +5,5 @@ ARG LOG_LEVEL ARG CONCURRENT_PROCESSES ENV STICKERIFY_TOKEN=$STICKERIFY_TOKEN \ LOG_LEVEL=$LOG_LEVEL \ - CONCURRENT_PROCESSES=$CONCURRENT_PROCESSES + CONCURRENT_PROCESSES=$CONCURRENT_PROCESSES \ + OTEL_SDK_DISABLED=false diff --git a/build.gradle.kts b/build.gradle.kts index 25f53c62..aa283d14 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(libs.jspecify) implementation(libs.logback.classic) implementation(libs.logstash.logback.encoder) + implementation(libs.opentelemetry.instrumentation.annotations) implementation(libs.telegram.bot.api) implementation(libs.tika) @@ -52,10 +53,11 @@ val jlink = tasks.register("jlink") { options = listOf("--strip-debug", "--no-header-files", "--no-man-pages", "--ignore-modified-runtime") modules = listOf( - "java.instrument", // for junit - "java.naming", // for logback - "java.sql", // for tika - "jdk.unsupported" // for gson + "java.instrument", // for JUnit + "java.naming", // for Logback + "java.management", // for OpenTelemetry + "java.sql", // for Tika + "jdk.unsupported" // for Gson ) includeModulePath = false javaCompiler = javaToolchains.compilerFor(java.toolchain) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1afdb7ab..e8d4b1ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ junit-platform = { module = "org.junit.platform:junit-platform-launcher", versio logback-classic = "ch.qos.logback:logback-classic:1.6.4" logstash-logback-encoder = "net.logstash.logback:logstash-logback-encoder:9.0" mockwebserver = "com.squareup.okhttp3:mockwebserver3-junit5:5.5.0" +opentelemetry-instrumentation-annotations = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.31.1" telegram-bot-api = "com.github.pengrad:java-telegram-bot-api:10.1.0" tika = "org.apache.tika:tika-core:4.0.0" diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 3e7cb99a..ed0ce675 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -34,6 +34,7 @@ import com.pengrad.telegrambot.request.richmessages.SendRichMessage; import com.pengrad.telegrambot.request.richmessages.SendRichMessageDraft; import com.pengrad.telegrambot.response.BaseResponse; +import io.opentelemetry.instrumentation.annotations.WithSpan; import org.jspecify.annotations.Nullable; import org.slf4j.event.Level; @@ -106,6 +107,7 @@ public void close() { bot.shutdown(); } + @WithSpan("bot.answer") private void answer(TelegramRequest request) { LOGGER.at(Level.INFO).log("Received request"); @@ -118,6 +120,7 @@ private void answer(TelegramRequest request) { } } + @WithSpan("bot.answer_file") private void answerFile(TelegramRequest request, TelegramFile file) { if (file == TelegramFile.NOT_SUPPORTED) { answerText(ERROR, request); @@ -163,6 +166,7 @@ private void answerFile(TelegramRequest request, String fileId) { } } + @WithSpan("bot.download_file") private File retrieveFile(String fileId) throws TelegramApiException, FileOperationException { var file = execute(new GetFile(fileId)).file(); @@ -206,6 +210,7 @@ private boolean processTelegramFailure(TelegramApiException e, boolean logUnmatc return replyToUser; } + @WithSpan("bot.answer_text") private void answerText(TelegramRequest request) { var message = request.message(); if (message.text() == null) { diff --git a/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java b/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java index fed451fb..e5172161 100644 --- a/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java +++ b/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java @@ -25,6 +25,8 @@ import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import org.apache.tika.Tika; import org.jspecify.annotations.Nullable; import org.slf4j.event.Level; @@ -96,7 +98,8 @@ private static String detectMimeType(File file) throws MediaException { * @throws InterruptedException if the current thread is interrupted while converting a video file * @see MediaHelper#convert(File) */ - private static @Nullable File performConversion(File inputFile, String mimeType) throws MediaException, InterruptedException { + @WithSpan("media.convert") + private static @Nullable File performConversion(File inputFile, @SpanAttribute("mime_type") String mimeType) throws MediaException, InterruptedException { LOGGER.at(Level.DEBUG).log("MIME type successfully detected"); try { @@ -179,6 +182,7 @@ private static boolean isAnimatedWebp(File file, String mimeType) { * @throws MediaException if an error occurred retrieving video information * @throws InterruptedException if the current thread is interrupted while retrieving file info */ + @WithSpan("media.check_video_compliance") private static boolean isVideoCompliant(File file) throws MediaException, InterruptedException { var mediaInfo = retrieveMultimediaInfo(file); @@ -278,6 +282,7 @@ record FormatInfo(@SerializedName("format_name") String format, @Nullable Float * @return {@code true} if the file is compliant * @throws FileOperationException if an error occurred retrieving the size of the file */ + @WithSpan("media.check_animation_compliance") private static boolean isAnimatedStickerCompliant(File file, String mimeType) throws FileOperationException { if ("application/gzip".equals(mimeType)) { var uncompressedContent = ""; @@ -358,6 +363,7 @@ private static boolean isSupportedImage(String mimeType) { * @throws MediaException if an error occurred retrieving image information * @throws InterruptedException if the current thread is interrupted while retrieving file info */ + @WithSpan("media.check_image_compliance") private static boolean isImageCompliant(File image, String mimeType) throws MediaException, InterruptedException { var mediaInfo = retrieveMultimediaInfo(image); @@ -384,6 +390,7 @@ && isSizeCompliant(imageInfo.width(), imageInfo.height()) * @throws MediaException if an error occurred processing passed-in image * @throws InterruptedException if the current thread is interrupted while converting the file */ + @WithSpan("media.convert_image") private static File convertToWebp(File file) throws MediaException, InterruptedException { var webpImage = createTempFile("webp"); var command = List.of( @@ -465,6 +472,7 @@ private static void deleteFile(File file) throws FileOperationException { * @throws MediaException if file conversion is not successful * @throws InterruptedException if the current thread is interrupted while converting the video file */ + @WithSpan("media.convert_video") private static File convertToWebm(File file) throws MediaException, InterruptedException { var webmVideo = createTempFile("webm"); var logPrefix = webmVideo.getAbsolutePath() + "-passlog"; diff --git a/src/main/java/com/github/stickerifier/stickerify/process/ProcessHelper.java b/src/main/java/com/github/stickerifier/stickerify/process/ProcessHelper.java index c69ed402..5d1b3c75 100644 --- a/src/main/java/com/github/stickerifier/stickerify/process/ProcessHelper.java +++ b/src/main/java/com/github/stickerifier/stickerify/process/ProcessHelper.java @@ -4,6 +4,8 @@ import com.github.stickerifier.stickerify.exception.ProcessException; import com.github.stickerifier.stickerify.logger.StructuredLogger; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import org.slf4j.event.Level; import java.io.IOException; @@ -34,7 +36,8 @@ public final class ProcessHelper { * * @throws InterruptedException if the current thread is interrupted while waiting for the command to finish */ - public static String executeCommand(final List command) throws ProcessException, InterruptedException { + @WithSpan("process.execute") + public static String executeCommand(@SpanAttribute("command") final List command) throws ProcessException, InterruptedException { SEMAPHORE.acquire(); try (var process = new ProcessBuilder(command).start()) {