add OTLP logging appender factory

This commit is contained in:
Jonathan Klabunde Tomer
2025-09-22 11:09:40 -07:00
committed by GitHub
parent f80e30f9f2
commit 007dde8d45
7 changed files with 145 additions and 13 deletions

View File

@@ -385,6 +385,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
dynamicConfigurationManager.start();
MetricsUtil.configureRegistries(config, environment, dynamicConfigurationManager);
MetricsUtil.configureLogging(config, environment);
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);

View File

@@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.metrics;
import com.codahale.metrics.SharedMetricRegistries;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
@@ -21,6 +22,14 @@ import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.registry.otlp.OtlpMeterRegistry;
import io.micrometer.statsd.StatsdMeterRegistry;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ServiceAttributes;
import java.time.Duration;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.WhisperServerVersion;
@@ -99,6 +108,34 @@ public class MetricsUtil {
new MicrometerRegistryManager(Metrics.globalRegistry, shutdownWaitDuration));
}
public static void configureLogging(final WhisperServerConfiguration config, final Environment environment) {
if (!config.getOpenTelemetryConfiguration().enabled()) {
return;
}
final OpenTelemetrySdk openTelemetry =
OpenTelemetrySdk.builder()
.setLoggerProvider(
SdkLoggerProvider.builder()
.setResource(
Resource.builder()
.put(ServiceAttributes.SERVICE_NAME, "chat")
.put(ServiceAttributes.SERVICE_VERSION, WhisperServerVersion.getServerVersion())
.build())
.addLogRecordProcessor(
BatchLogRecordProcessor.builder(OtlpHttpLogRecordExporter.getDefault()).build())
.build())
.build();
OpenTelemetryAppender.install(openTelemetry);
environment.lifecycle().manage(new Managed() {
@Override
public void stop() {
openTelemetry.shutdown();
}
});
}
@VisibleForTesting
static void configureMeterFilters(MeterRegistry.Config config,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {

View File

@@ -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<ILoggingEvent> {
@JsonProperty
private String destination;
@Override
public Appender<ILoggingEvent> build(
final LoggerContext context,
final String applicationName,
final LayoutFactory<ILoggingEvent> layoutFactory,
final LevelFilterFactory<ILoggingEvent> levelFilterFactory,
final AsyncAppenderFactory<ILoggingEvent> 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);
}
}

View File

@@ -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();