From 007dde8d45f8f88b67749f150438c1275381fc2a Mon Sep 17 00:00:00 2001
From: Jonathan Klabunde Tomer <125505367+jkt-signal@users.noreply.github.com>
Date: Mon, 22 Sep 2025 11:09:40 -0700
Subject: [PATCH] add OTLP logging appender factory
---
pom.xml | 14 +++++
service/pom.xml | 52 ++++++++++++++-----
.../textsecuregcm/WhisperServerService.java | 1 +
.../textsecuregcm/metrics/MetricsUtil.java | 37 +++++++++++++
.../metrics/OpenTelemetryAppenderFactory.java | 50 ++++++++++++++++++
.../workers/CommandDependencies.java | 3 ++
....dropwizard.logging.common.AppenderFactory | 1 +
7 files changed, 145 insertions(+), 13 deletions(-)
create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenTelemetryAppenderFactory.java
diff --git a/pom.xml b/pom.xml
index c05ea2db4..26659788a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -163,6 +163,20 @@
pom
import
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.54.0
+ pom
+ import
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-instrumentation-bom
+ 2.20.0
+ pom
+ import
+
io.projectreactor
reactor-bom
diff --git a/service/pom.xml b/service/pom.xml
index 4f0f7889e..f9f27173c 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -35,7 +35,7 @@
app-store-server-library
${storekit.version}
-
+
com.squareup.okio
okio-jvm
@@ -198,6 +198,28 @@
test
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-logback-appender-1.0
+
+ 2.19.0-alpha
+
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-instrumentation-api-incubator
+
+
+
+
party.iroiro.luajava
luajava
@@ -258,16 +280,11 @@
-
com.google.firebase
firebase-admin
${firebase-admin.version}
-
- io.opentelemetry.semconv
- opentelemetry-semconv
-
com.google.guava
@@ -275,18 +292,15 @@
-
- io.opentelemetry.semconv
- opentelemetry-semconv
- 1.37.0
-
+
com.google.cloud
google-cloud-firestore
+
- io.opentelemetry.semconv
- opentelemetry-semconv
+ io.opentelemetry.instrumentation
+ opentelemetry-instrumentation-api-incubator
@@ -561,9 +575,21 @@
org.jetbrains
annotations
+
+
+ com.squareup.okio
+ okio-jvm
+
+
+
+ com.squareup.okio
+ okio-jvm
+ 3.15.0
+
+
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
index 8799d50b7..f1a36998a 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
@@ -385,6 +385,7 @@ public class WhisperServerService extends Application dynamicConfigurationManager) {
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenTelemetryAppenderFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenTelemetryAppenderFactory.java
new file mode 100644
index 000000000..201a4923b
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenTelemetryAppenderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2025 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.metrics;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import io.dropwizard.logging.common.AbstractAppenderFactory;
+import io.dropwizard.logging.common.async.AsyncAppenderFactory;
+import io.dropwizard.logging.common.filter.LevelFilterFactory;
+import io.dropwizard.logging.common.layout.LayoutFactory;
+import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
+import jakarta.validation.constraints.NotEmpty;
+
+@JsonTypeName("otlp")
+public class OpenTelemetryAppenderFactory extends AbstractAppenderFactory {
+
+ @JsonProperty
+ private String destination;
+
+ @Override
+ public Appender build(
+ final LoggerContext context,
+ final String applicationName,
+ final LayoutFactory layoutFactory,
+ final LevelFilterFactory levelFilterFactory,
+ final AsyncAppenderFactory asyncAppenderFactory) {
+
+ final OpenTelemetryAppender appender = new OpenTelemetryAppender();
+ appender.setCaptureCodeAttributes(true);
+ appender.setCaptureLoggerContext(true);
+
+ // The installation of an OpenTelemetry configuration happens in
+ // WhisperServerService (or CommandDependencies), in order to let us tie
+ // into Dropwizard's lifecycle management; this allows us to buffer any
+ // logs emitted between now and that happening.
+ appender.setNumLogsCapturedBeforeOtelInstall(1000);
+
+ appender.addFilter(levelFilterFactory.build(threshold));
+ getFilterFactories().forEach(f -> appender.addFilter(f.build()));
+ appender.start();
+
+ return wrapAsync(appender, asyncAppenderFactory);
+ }
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java
index b6e790183..e5386896e 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java
@@ -45,6 +45,7 @@ import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.experiment.PushNotificationExperimentSamples;
import org.whispersystems.textsecuregcm.grpc.net.GrpcClientConnectionManager;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
+import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.FcmSender;
@@ -127,6 +128,8 @@ record CommandDependencies(
throws IOException, GeneralSecurityException, InvalidInputException {
Clock clock = Clock.systemUTC();
+ MetricsUtil.configureLogging(configuration, environment);
+
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final AwsCredentialsProvider awsCredentialsProvider = configuration.getAwsCredentialsConfiguration().build();
diff --git a/service/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory b/service/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory
index ebe62c359..a4d1da70b 100644
--- a/service/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory
+++ b/service/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory
@@ -1 +1,2 @@
org.whispersystems.textsecuregcm.metrics.LogstashTcpSocketAppenderFactory
+org.whispersystems.textsecuregcm.metrics.OpenTelemetryAppenderFactory