From c5f36fcf3d13d1aa2ff1fec304304c161c7dd6b1 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 1 Aug 2024 09:31:49 +0200 Subject: [PATCH 01/24] feat: Add RecentLogs support --- .../cloudfoundry/doppler/DopplerClient.java | 3 ++ .../logcache/v1/LogCacheClient.java | 8 ++++ .../applications/DefaultApplications.java | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index a9c03441cf..4e2c869b32 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,12 +39,15 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); + //TODO Adapt the message /** * Makes the Recent Logs request * + * @deprecated Do not use this type directly, it exists only for the Jackson-binding infrastructure * @param request the Recent Logs request * @return the events from the recent logs */ + @Deprecated Flux recentLogs(RecentLogsRequest request); /** diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java index e455db220a..8a9b08505c 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java @@ -46,4 +46,12 @@ public interface LogCacheClient { * @return the read response */ Mono read(ReadRequest request); + + /** + * Makes the Log Cache RecentLogs /api/v1/read request + * + * @param request the Recent Logs request + * @return the events from the recent logs + */ + Mono recentLogs(ReadRequest request); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index e51ddbb472..718ab25245 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -40,6 +40,7 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; + import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.OrderDirection; import org.cloudfoundry.client.v2.applications.AbstractApplicationResource; @@ -154,6 +155,9 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.EnvelopeType; +import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -200,6 +204,9 @@ public final class DefaultApplications implements Applications { private static final Comparator LOG_MESSAGE_COMPARATOR = Comparator.comparing(LogMessage::getTimestamp); + private static final Comparator LOG_MESSAGE_COMPARATOR_LOG_CACHE = + Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); + private static final Duration LOG_MESSAGE_TIMESPAN = Duration.ofMillis(500); private static final int MAX_NUMBER_OF_RECENT_EVENTS = 50; @@ -1617,6 +1624,14 @@ private static Flux getLogs( } } + private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + .map(org.cloudfoundry.logcache.v1.Envelope::getLog) + .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .flatMapIterable(d -> d); + } + @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = @@ -2509,6 +2524,32 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } + private static Flux requestLogsRecentLogCache( + Mono logCacheClient, String applicationId) { + return logCacheClient.flatMapMany( + client -> + client.recentLogs( + ReadRequest.builder() + .sourceId(applicationId) + .envelopeType(EnvelopeType.LOG) + .limit(100) + .build() + ) + .flatMap( + response -> + Mono.justOrEmpty( + response.getEnvelopes().getBatch().stream().findFirst() + ) + ) + .repeatWhenEmpty( + exponentialBackOff( + Duration.ofSeconds(1), + Duration.ofSeconds(5), + Duration.ofMinutes(1)) + ) + ); + } + private static Flux requestLogsStream( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( From 1ed7205bf9e6b28779972ced63ce2941dcb0eb79 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Mon, 19 Aug 2024 12:28:00 +0200 Subject: [PATCH 02/24] fix: The Reactor part --- .../reactor/logcache/v1/ReactorLogCacheEndpoints.java | 4 ++++ .../reactor/logcache/v1/_ReactorLogCacheClient.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java index 2e68c52538..f0b610d0c3 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java @@ -48,4 +48,8 @@ Mono meta(MetaRequest request) { Mono read(ReadRequest request) { return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); } + + Mono recentLogs(ReadRequest request) { + return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); + } } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java index d9460476ea..286f4c787c 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java @@ -53,6 +53,11 @@ public Mono read(ReadRequest request) { return getReactorLogCacheEndpoints().read(request); } + @Override + public Mono recentLogs(ReadRequest request) { + return getReactorLogCacheEndpoints().recentLogs(request); + } + /** * The connection context */ From ba7724d5e9dcc34805d6cf8d7ed07af1d2d4969e Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 4 Sep 2024 16:48:48 +0100 Subject: [PATCH 03/24] fix: Adjust the logcache --- .../_DefaultCloudFoundryOperations.java | 16 ++++++- .../operations/applications/Applications.java | 3 +- .../applications/DefaultApplications.java | 47 ++++++++++--------- .../operations/AbstractOperationsTest.java | 3 ++ .../applications/DefaultApplicationsTest.java | 27 +++++------ .../operations/ApplicationsTest.java | 4 +- 6 files changed, 61 insertions(+), 39 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java index 299b4bf5e4..e625d7f331 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java @@ -23,6 +23,7 @@ import org.cloudfoundry.client.v3.spaces.ListSpacesRequest; import org.cloudfoundry.client.v3.spaces.SpaceResource; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.networking.NetworkingClient; import org.cloudfoundry.operations.advanced.Advanced; import org.cloudfoundry.operations.advanced.DefaultAdvanced; @@ -79,7 +80,7 @@ public Advanced advanced() { @Override @Value.Derived public Applications applications() { - return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getSpaceId()); + return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getLogCacheClientPublisher(), getSpaceId()); } @Override @@ -178,6 +179,12 @@ Mono getCloudFoundryClientPublisher() { @Nullable abstract DopplerClient getDopplerClient(); + /** + * The {@link LogCacheClient} to use for operations functionality + */ + @Nullable + abstract LogCacheClient getLogCacheClient(); + @Value.Derived Mono getDopplerClientPublisher() { return Optional.ofNullable(getDopplerClient()) @@ -185,6 +192,13 @@ Mono getDopplerClientPublisher() { .orElse(Mono.error(new IllegalStateException("DopplerClient must be set"))); } + @Value.Derived + Mono getLogCacheClientPublisher() { + return Optional.ofNullable(getLogCacheClient()) + .map(Mono::just) + .orElse(Mono.error(new IllegalStateException("LogCacheClient must be set"))); + } + /** * The {@link NetworkingClient} to use for operations functionality */ diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 5196fef6c8..2f0750523e 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -17,6 +17,7 @@ package org.cloudfoundry.operations.applications; import org.cloudfoundry.doppler.LogMessage; +import org.cloudfoundry.logcache.v1.Log; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -124,7 +125,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(LogsRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 718ab25245..be6f0cf1bf 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -156,6 +156,7 @@ import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; import org.cloudfoundry.logcache.v1.EnvelopeType; +import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.util.OperationsLogging; @@ -221,6 +222,8 @@ public final class DefaultApplications implements Applications { private final Mono dopplerClient; + private final Mono logCacheClient; + private final RandomWords randomWords; private final Mono spaceId; @@ -228,22 +231,25 @@ public final class DefaultApplications implements Applications { public DefaultApplications( Mono cloudFoundryClient, Mono dopplerClient, + Mono logCacheClient, Mono spaceId) { - this(cloudFoundryClient, dopplerClient, new WordListRandomWords(), spaceId); + this(cloudFoundryClient, dopplerClient, logCacheClient, new WordListRandomWords(), spaceId); } DefaultApplications( Mono cloudFoundryClient, Mono dopplerClient, + Mono logCacheClient, RandomWords randomWords, Mono spaceId) { this.cloudFoundryClient = cloudFoundryClient; this.dopplerClient = dopplerClient; + this.logCacheClient = logCacheClient; this.randomWords = randomWords; this.spaceId = spaceId; } - @Override +@Override public Mono copySource(CopySourceApplicationRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( @@ -537,7 +543,7 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(LogsRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -546,7 +552,7 @@ public Flux logs(LogsRequest request) { cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> - getLogs(this.dopplerClient, applicationId, request.getRecent())) + getRecentLogs(this.logCacheClient, applicationId)) .transform(OperationsLogging.log("Get Application Logs")) .checkpoint(); } @@ -1607,30 +1613,29 @@ private static int getInstances(AbstractApplicationResource resource) { .orElse(0); } - private static Flux getLogs( - Mono dopplerClient, String applicationId, Boolean recent) { + /* private static Flux getLogs( + Mono logCacheClient, String applicationId, Boolean recent) { if (Optional.ofNullable(recent).orElse(false)) { - return requestLogsRecent(dopplerClient, applicationId) - .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) - .map(Envelope::getLogMessage) - .collectSortedList(LOG_MESSAGE_COMPARATOR) - .flatMapIterable(d -> d); - } else { - return requestLogsStream(dopplerClient, applicationId) - .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) - .map(Envelope::getLogMessage) - .transformDeferred( - SortingUtils.timespan(LOG_MESSAGE_COMPARATOR, LOG_MESSAGE_TIMESPAN)); + return getRecentLogs(logCacheClient, applicationId); } + }*/ + + private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + // .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } - private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { +/* private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { return requestLogsRecentLogCache(logCacheClient, applicationId) .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog) - .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .flatMapIterable(d -> d); - } + .collectList() + .flatMapIterable(d1 -> d1).cast(org.cloudfoundry.logcache.v1.Log.class); + } */ @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java index d864ed497e..5a0854f48b 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java @@ -51,6 +51,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.tasks.Tasks; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.routing.RoutingClient; import org.cloudfoundry.routing.v1.routergroups.RouterGroups; import org.cloudfoundry.uaa.UaaClient; @@ -101,6 +102,8 @@ public abstract class AbstractOperationsTest { protected final DopplerClient dopplerClient = mock(DopplerClient.class, RETURNS_SMART_NULLS); + protected final LogCacheClient logCacheClient = mock(LogCacheClient.class, RETURNS_SMART_NULLS); + protected final Events events = mock(Events.class, RETURNS_SMART_NULLS); protected final FeatureFlags featureFlags = mock(FeatureFlags.class, RETURNS_SMART_NULLS); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index cdc9619d2d..685c572717 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -144,6 +144,10 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.Log; +import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.AbstractOperationsTest; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; @@ -163,6 +167,7 @@ final class DefaultApplicationsTest extends AbstractOperationsTest { new DefaultApplications( Mono.just(this.cloudFoundryClient), Mono.just(this.dopplerClient), + Mono.just(this.logCacheClient), this.randomWords, Mono.just(TEST_SPACE_ID)); @@ -1318,7 +1323,7 @@ void logs() { this.applications .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -1346,12 +1351,12 @@ void logsRecent() { "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -1368,7 +1373,7 @@ void logsRecentNotSet() { this.applications .logs(LogsRequest.builder().name("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -5317,17 +5322,11 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.recentLogs( - RecentLogsRequest.builder().applicationId(applicationId).build())) + private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { + when(logCacheClient.recentLogs( + ReadRequest.builder().sourceId(applicationId).build())) .thenReturn( - Flux.just( - Envelope.builder() - .eventType(EventType.LOG_MESSAGE) - .logMessage( - fill(LogMessage.builder(), "log-message-").build()) - .origin("rsp") - .build())); + Mono.just(ReadResponse.builder().envelopes(fill(org.cloudfoundry.logcache.v1.EnvelopeBatch.builder()).build()).build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 36e1bd9456..cf2cde4aa6 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -529,10 +529,10 @@ public void logs() throws IOException { .name(applicationName) .recent(true) .build())) - .map(ApplicationLog::getLogType) + .map(org.cloudfoundry.logcache.v1.Log::getType) .next() .as(StepVerifier::create) - .expectNext(ApplicationLogType.OUT) + .expectNext(org.cloudfoundry.logcache.v1.LogType.OUT) .expectComplete() .verify(Duration.ofMinutes(5)); } From 75758feb182f1c81b2215af7ca39cd87614db955 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 30 Oct 2024 16:36:05 +0000 Subject: [PATCH 04/24] fix: Adjust the logcache client implementation --- .../ReactorServiceInstancesV3Test.java | 1 + .../_DefaultCloudFoundryOperations.java | 12 +++---- .../operations/applications/Applications.java | 4 ++- .../applications/DefaultApplications.java | 35 +++++++++---------- .../applications/DefaultApplicationsTest.java | 29 ++++++++++----- .../IntegrationTestConfiguration.java | 3 ++ .../operations/ApplicationsTest.java | 3 +- 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java index 1e6df1f258..1745aa7285 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java @@ -62,6 +62,7 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java index e625d7f331..62e442a53d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java @@ -179,12 +179,6 @@ Mono getCloudFoundryClientPublisher() { @Nullable abstract DopplerClient getDopplerClient(); - /** - * The {@link LogCacheClient} to use for operations functionality - */ - @Nullable - abstract LogCacheClient getLogCacheClient(); - @Value.Derived Mono getDopplerClientPublisher() { return Optional.ofNullable(getDopplerClient()) @@ -192,6 +186,12 @@ Mono getDopplerClientPublisher() { .orElse(Mono.error(new IllegalStateException("DopplerClient must be set"))); } + /** + * The {@link LogCacheClient} to use for operations functionality + */ + @Nullable + abstract LogCacheClient getLogCacheClient(); + @Value.Derived Mono getLogCacheClientPublisher() { return Optional.ofNullable(getLogCacheClient()) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 2f0750523e..31a4ad510c 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -18,6 +18,8 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; +import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -125,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(ReadRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index be6f0cf1bf..62d76d5c08 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -159,6 +159,7 @@ import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -543,13 +544,13 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(LogsRequest request) { + public Flux logs(ReadRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( (cloudFoundryClient, spaceId) -> getApplicationId( - cloudFoundryClient, request.getName(), spaceId))) + cloudFoundryClient, request.getSourceId(), spaceId))) .flatMapMany( applicationId -> getRecentLogs(this.logCacheClient, applicationId)) @@ -686,7 +687,6 @@ public Mono pushManifestV3(PushManifestV3Request request) { } catch (IOException e) { throw new RuntimeException("Could not serialize manifest", e); } - return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -1613,30 +1613,29 @@ private static int getInstances(AbstractApplicationResource resource) { .orElse(0); } - /* private static Flux getLogs( - Mono logCacheClient, String applicationId, Boolean recent) { + private static Flux getLogs( + Mono dopplerClient, String applicationId, Boolean recent) { if (Optional.ofNullable(recent).orElse(false)) { - return getRecentLogs(logCacheClient, applicationId); + return requestLogsRecent(dopplerClient, applicationId) + .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) + .map(Envelope::getLogMessage) + .collectSortedList(LOG_MESSAGE_COMPARATOR) + .flatMapIterable(d -> d); + } else { + return requestLogsStream(dopplerClient, applicationId) + .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) + .map(Envelope::getLogMessage) + .transformDeferred( + SortingUtils.timespan(LOG_MESSAGE_COMPARATOR, LOG_MESSAGE_TIMESPAN)); } - }*/ + } private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { return requestLogsRecentLogCache(logCacheClient, applicationId) - .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) - // .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } -/* private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { - return requestLogsRecentLogCache(logCacheClient, applicationId) - .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) - .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .map(org.cloudfoundry.logcache.v1.Envelope::getLog) - .collectList() - .flatMapIterable(d1 -> d1).cast(org.cloudfoundry.logcache.v1.Log.class); - } */ - @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 685c572717..e10e673a2d 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -144,8 +144,11 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; +import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.LogType; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.AbstractOperationsTest; @@ -1318,10 +1321,10 @@ void logs() { "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -1333,7 +1336,7 @@ void logsNoApp() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .consumeErrorWith( t -> @@ -1354,7 +1357,7 @@ void logsRecent() { requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -1371,7 +1374,7 @@ void logsRecentNotSet() { requestLogsStream(this.dopplerClient, "test-metadata-id"); this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -5323,10 +5326,18 @@ private static void requestListTasksEmpty( } private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { - when(logCacheClient.recentLogs( - ReadRequest.builder().sourceId(applicationId).build())) - .thenReturn( - Mono.just(ReadResponse.builder().envelopes(fill(org.cloudfoundry.logcache.v1.EnvelopeBatch.builder()).build()).build())); + when(logCacheClient.recentLogs( + ReadRequest.builder().sourceId(applicationId).build())) + .thenReturn( + Mono.just(fill(ReadResponse.builder()) + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT).build()) + .build()) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index c0b3f44b30..da390ac204 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -48,6 +48,7 @@ import org.cloudfoundry.client.v2.stacks.StackResource; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.TestLogCacheEndpoints; import org.cloudfoundry.networking.NetworkingClient; import org.cloudfoundry.operations.DefaultCloudFoundryOperations; @@ -266,6 +267,7 @@ ReactorCloudFoundryClient cloudFoundryClient( DefaultCloudFoundryOperations cloudFoundryOperations( CloudFoundryClient cloudFoundryClient, DopplerClient dopplerClient, + LogCacheClient logCacheClient, NetworkingClient networkingClient, RoutingClient routingClient, UaaClient uaaClient, @@ -274,6 +276,7 @@ DefaultCloudFoundryOperations cloudFoundryOperations( return DefaultCloudFoundryOperations.builder() .cloudFoundryClient(cloudFoundryClient) .dopplerClient(dopplerClient) + .logCacheClient(logCacheClient) .networkingClient(networkingClient) .routingClient(routingClient) .uaaClient(uaaClient) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index cf2cde4aa6..a70e276875 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -526,8 +526,7 @@ public void logs() throws IOException { .applications() .logs( ApplicationLogsRequest.builder() - .name(applicationName) - .recent(true) + .sourceId(applicationName) .build())) .map(org.cloudfoundry.logcache.v1.Log::getType) .next() From 97e25b8d66495d231a4f9facafce10e1ea2788ae Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 31 Oct 2024 12:37:08 -0400 Subject: [PATCH 05/24] fix: Adjust the expectations * because of the findFirst() on the envelopes, it could be type OUT or ERR, so we don't really care, as long as the payload is the same. Also, we don't care about the precise argument to the recentLogs call --- .../applications/DefaultApplicationsTest.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index e10e673a2d..bfb4c28d39 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -20,6 +20,7 @@ import static org.cloudfoundry.client.v3.LifecycleType.BUILDPACK; import static org.cloudfoundry.client.v3.LifecycleType.DOCKER; import static org.cloudfoundry.operations.TestObjects.fill; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_SMART_NULLS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -1326,7 +1327,7 @@ void logs() { this.applications .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) + .expectNextMatches(log -> log.getPayload().equals("test-payload")) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -5327,16 +5328,16 @@ private static void requestListTasksEmpty( private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { when(logCacheClient.recentLogs( - ReadRequest.builder().sourceId(applicationId).build())) + any())) .thenReturn( Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT).build()) - .build()) - .build()) + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT).build()) + .build()) + .build()) .build())); } From 2d4845ad8e237fec2d72530518a23f0fe219384b Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 1 Nov 2024 12:46:50 +0000 Subject: [PATCH 06/24] fix: Test only recent logs request --- .../operations/applications/Applications.java | 2 +- .../applications/DefaultApplications.java | 4 +- .../applications/DefaultApplicationsTest.java | 90 ++++++++++--------- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 31a4ad510c..9c4c68ca3d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -127,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(ReadRequest request); + Flux logs(LogsRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 62d76d5c08..9e8dfc8c95 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -544,13 +544,13 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(ReadRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( (cloudFoundryClient, spaceId) -> getApplicationId( - cloudFoundryClient, request.getSourceId(), spaceId))) + cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> getRecentLogs(this.logCacheClient, applicationId)) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index bfb4c28d39..79914016d4 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -156,6 +156,7 @@ import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; import org.cloudfoundry.util.ResourceMatchingUtils; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; @@ -1325,7 +1326,7 @@ void logs() { requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getPayload().equals("test-payload")) .expectComplete() @@ -1337,7 +1338,7 @@ void logsNoApp() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) + .logs(LogsRequest.builder().name("test-application-name").build()) .as(StepVerifier::create) .consumeErrorWith( t -> @@ -1348,39 +1349,40 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } - @Test - void logsRecent() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); - - this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @Test - void logsRecentNotSet() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } + // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient + @Test + void logsRecent() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient + @Test + void logsRecentNotSet() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } @Test void pushDocker() { @@ -5331,14 +5333,16 @@ private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, Str any())) .thenReturn( Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT).build()) - .build()) - .build()) - .build())); + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope + .builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT) + .build()) + .build()) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { From 531e7a431babc00cb8a435e19dd4897d9fb37340 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Wed, 23 Apr 2025 18:29:45 +0200 Subject: [PATCH 07/24] Fix JUnit tests for logCache The old "Applications.logs" method is keept for compatibility and when a log stream is needed. Adding new "logsRecent" method to access the logCache. integration-tests "ApplicationTest.logs" and "ApplicationTest.logsRecent" work. Minor changes in DefaultApplicationsTest in other JUnit tests to make them execute in Eclipse. --- .../operations/applications/Applications.java | 11 +- .../applications/DefaultApplications.java | 130 ++++++++++++++- .../applications/DefaultApplicationsTest.java | 155 +++++++++++------- .../operations/ApplicationsTest.java | 62 ++++++- 4 files changed, 293 insertions(+), 65 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 9c4c68ca3d..6549f0a84c 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -19,7 +19,7 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -129,6 +129,15 @@ public interface Applications { @Deprecated Flux logs(LogsRequest request); + /** + * List the applications logs from logCacheClient. + * If no messages are available, an empty Flux is returned. + * + * @param request the application logs request + * @return the applications logs + */ + Flux logsRecent(ReadRequest request); + /** * List the applications logs. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 9e8dfc8c95..1121e739fc 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -150,12 +150,10 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; -import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; -import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; @@ -578,6 +576,83 @@ public Flux logs(ApplicationLogsRequest request) { .build()); } + @SuppressWarnings("deprecation") + @Test + void logsRecent_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecent(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsNoApp_doppler() { + requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); + + this.applications + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logs_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void logsRecent_LogCache() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications + .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder()).type(LogType.OUT).build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsRecentNotSet_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + @Override @SuppressWarnings("deprecation") public Mono push(PushApplicationRequest request) { @@ -2432,6 +2507,57 @@ private static Mono requestGetApplication( .cast(AbstractApplicationResource.class); } + private static void requestInstancesApplicationFailing( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) + .thenReturn( + Mono.just( + fill( + ApplicationInstancesResponse.builder(), + "application-instances-") + .instance( + "instance-0", + fill( + ApplicationInstanceInfo.builder(), + "application-instance-info-") + .state("FAILED") + .build()) + .build())); + } + + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); + } + private static Flux requestListDomains( CloudFoundryClient cloudFoundryClient, String organizationId) { return PaginationUtils.requestClientV3Resources( diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 79914016d4..08effafac7 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -140,13 +140,11 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; -import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.LogType; @@ -156,7 +154,6 @@ import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; import org.cloudfoundry.util.ResourceMatchingUtils; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; @@ -1316,25 +1313,26 @@ void listTasks() { .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logs() { + void logsRecent_doppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); - + requestLogsRecent(this.dopplerClient, "test-metadata-id"); this.applications .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNextMatches(log -> log.getPayload().equals("test-payload")) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsNoApp() { + void logsNoApp_doppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1349,40 +1347,56 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } - // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient - @Test - void logsRecent() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient - @Test - void logsRecentNotSet() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } + @SuppressWarnings("deprecation") + @Test + void logs_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void logsRecent_LogCache() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications + .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder()).type(LogType.OUT).build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsRecentNotSet_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } @Test void pushDocker() { @@ -5328,28 +5342,53 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { - when(logCacheClient.recentLogs( - any())) - .thenReturn( - Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope - .builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT) - .build()) - .build()) - .build()) - .build())); + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( - Envelope.builder() + org.cloudfoundry.doppler.Envelope.builder() + .eventType(EventType.LOG_MESSAGE) + .logMessage( + fill(LogMessage.builder(), "log-message-").build()) + .origin("rsp") + .build())); + } + + @SuppressWarnings("deprecation") + private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.recentLogs( + RecentLogsRequest.builder().applicationId(applicationId).build())) + .thenReturn( + Flux.just( + org.cloudfoundry.doppler.Envelope.builder() .eventType(EventType.LOG_MESSAGE) .logMessage( fill(LogMessage.builder(), "log-message-").build()) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index a70e276875..19d784cb16 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.logging.Level; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; @@ -86,6 +87,7 @@ import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.operations.services.GetServiceInstanceRequest; import org.cloudfoundry.operations.services.ServiceInstance; +import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.FluentMap; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,6 +95,7 @@ import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.SignalType; import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass @@ -511,6 +514,7 @@ public void listTasks() throws IOException { * Doppler was dropped in PCF 4.x in favor of logcache. This test does not work * on TAS 4.x. */ + @Deprecated @Test @IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2) public void logs() throws IOException { @@ -525,13 +529,40 @@ public void logs() throws IOException { this.cloudFoundryOperations .applications() .logs( - ApplicationLogsRequest.builder() - .sourceId(applicationName) + LogsRequest.builder() + .name(applicationName) + .recent(true) .build())) - .map(org.cloudfoundry.logcache.v1.Log::getType) + .map(LogMessage::getMessageType) .next() .as(StepVerifier::create) - .expectNext(org.cloudfoundry.logcache.v1.LogType.OUT) + .expectNext(MessageType.OUT) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void logsRecent() throws IOException { + String applicationName = this.nameFactory.getApplicationName(); + Mono applicationGuid = + getAppGuidFromAppName(cloudFoundryOperations, applicationName); + createApplication( + this.cloudFoundryOperations, + new ClassPathResource("test-application.zip").getFile().toPath(), + applicationName, + false) + .then( + applicationGuid + .map(ApplicationsTest::getReadRequest) + .flatMapMany( + readRequest -> + callLogsRecent( + this.cloudFoundryOperations, + readRequest) + .log(null, Level.ALL, SignalType.ON_NEXT)) + .map(ApplicationsTest::checkOneLogEntry) + .then()) + .as(StepVerifier::create) .expectComplete() .verify(Duration.ofMinutes(5)); } @@ -2155,4 +2186,27 @@ private static Mono requestSshEnabled( .applications() .sshEnabled(ApplicationSshEnabledRequest.builder().name(applicationName).build()); } + + private static ReadRequest getReadRequest(String applicationId) { + return ReadRequest.builder().sourceId(applicationId).build(); + } + + private static Flux callLogsRecent( + CloudFoundryOperations cloudFoundryOperations, ReadRequest readRequest) { + return cloudFoundryOperations.applications().logsRecent(readRequest); + } + + private static Mono getAppGuidFromAppName( + CloudFoundryOperations cloudFoundryOperations, String applicationName) { + return cloudFoundryOperations + .applications() + .get(GetApplicationRequest.builder().name(applicationName).build()) + .map(ApplicationDetail::getId); + } + + private static Log checkOneLogEntry(Log log) { + assertThat(log.getType().equals(LogType.OUT)); + OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); + return log; + } } From 2e15397b8fc64cac3c46ed084ab751878a000202 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 08:01:21 +0100 Subject: [PATCH 08/24] fix: Adjust the merge conflicts --- .../operations/applications/Applications.java | 8 + .../applications/DefaultApplications.java | 163 +++++------------- 2 files changed, 50 insertions(+), 121 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 6549f0a84c..b484e7f586 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -117,6 +117,14 @@ public interface Applications { */ Flux listTasks(ListApplicationTasksRequest request); + /** + * List the applications logs from dopplerClient + * @deprecated Only for compatibility. Switch to logCacheClient method below. + * @param request the application logs request + * @return the applications logs + */ + Flux logs(LogsRequest request); + /** * List the applications logs. Uses Doppler under the hood. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 1121e739fc..880bcdc339 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -158,6 +158,8 @@ import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.logcache.v1.ReadResponse; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; +import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -248,7 +250,7 @@ public DefaultApplications( this.spaceId = spaceId; } -@Override + @Override public Mono copySource(CopySourceApplicationRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( @@ -541,8 +543,9 @@ public Flux listTasks(ListApplicationTasksRequest request) { .checkpoint(); } + @Deprecated @Override - public Flux logs(LogsRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -551,107 +554,37 @@ public Flux logs(LogsRequest request) { cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> - getRecentLogs(this.logCacheClient, applicationId)) + getLogs(this.dopplerClient, applicationId, request.getRecent())) .transform(OperationsLogging.log("Get Application Logs")) .checkpoint(); } @Override - public Flux logs(ApplicationLogsRequest request) { - return logs(LogsRequest.builder() - .name(request.getName()) - .recent(request.getRecent()) - .build()) - .map( - logMessage -> - ApplicationLog.builder() - .sourceId(logMessage.getApplicationId()) - .sourceType(logMessage.getSourceType()) - .instanceId(logMessage.getSourceInstance()) - .message(logMessage.getMessage()) - .timestamp(logMessage.getTimestamp()) - .logType( - ApplicationLogType.from( - logMessage.getMessageType().name())) - .build()); - } - - @SuppressWarnings("deprecation") - @Test - void logsRecent_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); - this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) - .as(StepVerifier::create) - .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @SuppressWarnings("deprecation") - @Test - void logsNoApp_doppler() { - requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); - - this.applications - .verify(Duration.ofSeconds(5)); - } - - @SuppressWarnings("deprecation") - @Test - void logs_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) - .as(StepVerifier::create) - .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @Test - void logsRecent_LogCache() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); - this.applications - .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder()).type(LogType.OUT).build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); + public Flux logsRecent(ReadRequest request) { + return getRecentLogsLogCache(this.logCacheClient, request) + .transform(OperationsLogging.log("Get Application Logs")) + .checkpoint(); } - @SuppressWarnings("deprecation") - @Test - void logsRecentNotSet_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } +// @Override +// public Flux logs(ApplicationLogsRequest request) { +// return logs(LogsRequest.builder() +// .name(request.getName()) +// .recent(request.getRecent()) +// .build()) +// .map( +// logMessage -> +// ApplicationLog.builder() +// .sourceId(logMessage.getApplicationId()) +// .sourceType(logMessage.getSourceType()) +// .instanceId(logMessage.getSourceInstance()) +// .message(logMessage.getMessage()) +// .timestamp(logMessage.getTimestamp()) +// .logType( +// ApplicationLogType.from( +// logMessage.getMessageType().name())) +// .build()); +// } @Override @SuppressWarnings("deprecation") @@ -1705,8 +1638,13 @@ private static Flux getLogs( } } - private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { - return requestLogsRecentLogCache(logCacheClient, applicationId) + private static Flux getRecentLogsLogCache( + Mono logCacheClient, ReadRequest readRequest) { + return requestLogsRecentLogCache(logCacheClient, readRequest) + .map(EnvelopeBatch::getBatch) + .map(List::stream) + .flatMapIterable(envelopeStream -> envelopeStream.collect(Collectors.toList())) + .filter(e -> e.getLog() != null) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } @@ -2646,6 +2584,7 @@ private static Flux requestListTasks( .build())); } + @Deprecated private static Flux requestLogsRecent( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2654,30 +2593,12 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } - private static Flux requestLogsRecentLogCache( - Mono logCacheClient, String applicationId) { - return logCacheClient.flatMapMany( + private static Mono requestLogsRecentLogCache( + Mono logCacheClient, ReadRequest readRequest) { + return logCacheClient.flatMap( client -> - client.recentLogs( - ReadRequest.builder() - .sourceId(applicationId) - .envelopeType(EnvelopeType.LOG) - .limit(100) - .build() - ) - .flatMap( - response -> - Mono.justOrEmpty( - response.getEnvelopes().getBatch().stream().findFirst() - ) - ) - .repeatWhenEmpty( - exponentialBackOff( - Duration.ofSeconds(1), - Duration.ofSeconds(5), - Duration.ofMinutes(1)) - ) - ); + client.recentLogs(readRequest) + .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))); } private static Flux requestLogsStream( From 634d13508a5adf2e2ebf2a2ed42edc3a347fb840 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 08:52:07 +0100 Subject: [PATCH 09/24] fix: Adjust the code and tests --- .../operations/applications/Applications.java | 10 +- .../applications/DefaultApplications.java | 92 ++++-------------- .../applications/DefaultApplicationsTest.java | 96 +++++++++---------- 3 files changed, 69 insertions(+), 129 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index b484e7f586..38ffb6251e 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -117,14 +117,6 @@ public interface Applications { */ Flux listTasks(ListApplicationTasksRequest request); - /** - * List the applications logs from dopplerClient - * @deprecated Only for compatibility. Switch to logCacheClient method below. - * @param request the application logs request - * @return the applications logs - */ - Flux logs(LogsRequest request); - /** * List the applications logs. Uses Doppler under the hood. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} @@ -135,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(LogsRequest request); /** * List the applications logs from logCacheClient. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 880bcdc339..22a923607d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -154,12 +154,11 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -566,25 +565,25 @@ public Flux logsRecent(ReadRequest request) { .checkpoint(); } -// @Override -// public Flux logs(ApplicationLogsRequest request) { -// return logs(LogsRequest.builder() -// .name(request.getName()) -// .recent(request.getRecent()) -// .build()) -// .map( -// logMessage -> -// ApplicationLog.builder() -// .sourceId(logMessage.getApplicationId()) -// .sourceType(logMessage.getSourceType()) -// .instanceId(logMessage.getSourceInstance()) -// .message(logMessage.getMessage()) -// .timestamp(logMessage.getTimestamp()) -// .logType( -// ApplicationLogType.from( -// logMessage.getMessageType().name())) -// .build()); -// } + @Override + public Flux logs(ApplicationLogsRequest request) { + return logs(LogsRequest.builder() + .name(request.getName()) + .recent(request.getRecent()) + .build()) + .map( + logMessage -> + ApplicationLog.builder() + .sourceId(logMessage.getApplicationId()) + .sourceType(logMessage.getSourceType()) + .instanceId(logMessage.getSourceInstance()) + .message(logMessage.getMessage()) + .timestamp(logMessage.getTimestamp()) + .logType( + ApplicationLogType.from( + logMessage.getMessageType().name())) + .build()); + } @Override @SuppressWarnings("deprecation") @@ -2445,57 +2444,6 @@ private static Mono requestGetApplication( .cast(AbstractApplicationResource.class); } - private static void requestInstancesApplicationFailing( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() - .applicationId(applicationId) - .build())) - .thenReturn( - Mono.just( - fill( - ApplicationInstancesResponse.builder(), - "application-instances-") - .instance( - "instance-0", - fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") - .state("FAILED") - .build()) - .build())); - } - - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static Flux requestListDomains( CloudFoundryClient cloudFoundryClient, String organizationId) { return PaginationUtils.requestClientV3Resources( diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 08effafac7..5594830836 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5054,44 +5054,72 @@ private static void requestGetApplicationFailing( .build())); } - private static void requestInstancesApplicationFailing( + private static void requestGetApplicationTimeout( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() .applicationId(applicationId) .build())) + .thenReturn( + Mono.just( + fill(GetApplicationResponse.builder()) + .entity( + fill(ApplicationEntity.builder()) + .packageState("STAGING") + .build()) + .build())); + } + + private static void requestInstancesApplicationFailing( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill( - ApplicationInstancesResponse.builder(), - "application-instances-") + ApplicationInstancesResponse.builder(), + "application-instances-") .instance( "instance-0", fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") + ApplicationInstanceInfo.builder(), + "application-instance-info-") .state("FAILED") .build()) .build())); } - private static void requestGetApplicationTimeout( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) .thenReturn( Mono.just( - fill(GetApplicationResponse.builder()) - .entity( - fill(ApplicationEntity.builder()) - .packageState("STAGING") + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) .build()) .build())); } @@ -5342,34 +5370,6 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( From 5f79d2fd3d29af9bd902bdc136c291511e43386c Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 11:55:22 +0100 Subject: [PATCH 10/24] feat: Adjust the integrationtests --- .../java/org/cloudfoundry/operations/ApplicationsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 19d784cb16..e3cc443c50 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -529,14 +529,14 @@ public void logs() throws IOException { this.cloudFoundryOperations .applications() .logs( - LogsRequest.builder() + ApplicationLogsRequest.builder() .name(applicationName) .recent(true) .build())) - .map(LogMessage::getMessageType) + .map(ApplicationLog::getLogType) .next() .as(StepVerifier::create) - .expectNext(MessageType.OUT) + .expectNext(ApplicationLogType.OUT) .expectComplete() .verify(Duration.ofMinutes(5)); } From 5a105217d377c23cefe2d7086960947c81c6bbfb Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 15:05:21 +0100 Subject: [PATCH 11/24] fix: Adjust the lint issues --- .../ReactorServiceInstancesV3Test.java | 1 - .../cloudfoundry/doppler/DopplerClient.java | 2 +- .../operations/applications/Applications.java | 1 - .../applications/DefaultApplications.java | 10 ++++---- .../applications/DefaultApplicationsTest.java | 24 +++++++++---------- .../operations/ApplicationsTest.java | 12 +++++----- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java index 1745aa7285..1e6df1f258 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java @@ -62,7 +62,6 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; -import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index 4e2c869b32..44d5306036 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,7 +39,7 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); - //TODO Adapt the message + // TODO Adapt the message /** * Makes the Recent Logs request * diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 38ffb6251e..53317ccb63 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -19,7 +19,6 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.ReadRequest; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 22a923607d..2f1bdef41a 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -40,7 +40,6 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; - import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.OrderDirection; import org.cloudfoundry.client.v2.applications.AbstractApplicationResource; @@ -150,15 +149,15 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; -import org.cloudfoundry.doppler.Envelope; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -205,8 +204,9 @@ public final class DefaultApplications implements Applications { private static final Comparator LOG_MESSAGE_COMPARATOR = Comparator.comparing(LogMessage::getTimestamp); - private static final Comparator LOG_MESSAGE_COMPARATOR_LOG_CACHE = - Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); + private static final Comparator + LOG_MESSAGE_COMPARATOR_LOG_CACHE = + Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); private static final Duration LOG_MESSAGE_TIMESPAN = Duration.ofMillis(500); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 5594830836..5695108af7 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5076,21 +5076,21 @@ private static void requestGetApplicationTimeout( private static void requestInstancesApplicationFailing( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient - .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() - .applicationId(applicationId) - .build())) + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill( - ApplicationInstancesResponse.builder(), - "application-instances-") + ApplicationInstancesResponse.builder(), + "application-instances-") .instance( "instance-0", fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") + ApplicationInstanceInfo.builder(), + "application-instance-info-") .state("FAILED") .build()) .build())); @@ -5107,9 +5107,9 @@ private static void requestLogsRecentLogCache( .batch( Arrays.asList( fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) + .logcache.v1 + .Envelope + .builder()) .log( Log .builder() diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index e3cc443c50..832afe3c07 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -547,18 +547,18 @@ public void logsRecent() throws IOException { Mono applicationGuid = getAppGuidFromAppName(cloudFoundryOperations, applicationName); createApplication( - this.cloudFoundryOperations, - new ClassPathResource("test-application.zip").getFile().toPath(), - applicationName, - false) + this.cloudFoundryOperations, + new ClassPathResource("test-application.zip").getFile().toPath(), + applicationName, + false) .then( applicationGuid .map(ApplicationsTest::getReadRequest) .flatMapMany( readRequest -> callLogsRecent( - this.cloudFoundryOperations, - readRequest) + this.cloudFoundryOperations, + readRequest) .log(null, Level.ALL, SignalType.ON_NEXT)) .map(ApplicationsTest::checkOneLogEntry) .then()) From a47f55981e03926f27eb3c38f68587373303182b Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 08:17:44 +0100 Subject: [PATCH 12/24] feat: Sanitize the code --- .../logcache/v1/ReactorLogCacheEndpoints.java | 2 +- .../cloudfoundry/doppler/DopplerClient.java | 9 ++- .../applications/DefaultApplicationsTest.java | 61 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java index f0b610d0c3..3d1623cef3 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java @@ -50,6 +50,6 @@ Mono read(ReadRequest request) { } Mono recentLogs(ReadRequest request) { - return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); + return read(request); } } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index 44d5306036..74d23b1488 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,13 +39,16 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); - // TODO Adapt the message /** * Makes the Recent Logs request * - * @deprecated Do not use this type directly, it exists only for the Jackson-binding infrastructure + * @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest)} instead. + * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} and {@code TAS < 4.0}. * @param request the Recent Logs request - * @return the events from the recent logs + * @return a flux of events from the recent logs + * @see Loggregator + * @see Log Cache + * @see org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest) */ @Deprecated Flux recentLogs(RecentLogsRequest request); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 5695108af7..657fb7d561 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -20,7 +20,6 @@ import static org.cloudfoundry.client.v3.LifecycleType.BUILDPACK; import static org.cloudfoundry.client.v3.LifecycleType.DOCKER; import static org.cloudfoundry.operations.TestObjects.fill; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_SMART_NULLS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -144,6 +143,7 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; @@ -1315,7 +1315,7 @@ void listTasks() { @SuppressWarnings("deprecation") @Test - void logsRecent_doppler() { + void logsRecentDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1332,7 +1332,7 @@ void logsRecent_doppler() { @SuppressWarnings("deprecation") @Test - void logsNoApp_doppler() { + void logsNoAppDoppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1349,7 +1349,7 @@ void logsNoApp_doppler() { @SuppressWarnings("deprecation") @Test - void logs_doppler() { + void logsDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1365,7 +1365,7 @@ void logs_doppler() { } @Test - void logsRecent_LogCache() { + void logsRecentLogCache() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1373,7 +1373,7 @@ void logsRecent_LogCache() { "test-metadata-id"); requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); this.applications - .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .logsRecent(ReadRequest.builder().sourceId("test-metadata-id").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder()).type(LogType.OUT).build()) .expectComplete() @@ -1382,7 +1382,7 @@ void logsRecent_LogCache() { @SuppressWarnings("deprecation") @Test - void logsRecentNotSet_doppler() { + void logsRecentNotSetDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -5054,25 +5054,6 @@ private static void requestGetApplicationFailing( .build())); } - private static void requestGetApplicationTimeout( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) - .thenReturn( - Mono.just( - fill(GetApplicationResponse.builder()) - .entity( - fill(ApplicationEntity.builder()) - .packageState("STAGING") - .build()) - .build())); - } - private static void requestInstancesApplicationFailing( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient @@ -5096,9 +5077,28 @@ private static void requestInstancesApplicationFailing( .build())); } + private static void requestGetApplicationTimeout( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() + .applicationId(applicationId) + .build())) + .thenReturn( + Mono.just( + fill(GetApplicationResponse.builder()) + .entity( + fill(ApplicationEntity.builder()) + .packageState("STAGING") + .build()) + .build())); + } + private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) + LogCacheClient logCacheClient, String sourceId, String payload) { + when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) .thenReturn( Mono.just( fill(ReadResponse.builder()) @@ -5106,10 +5106,7 @@ private static void requestLogsRecentLogCache( fill(EnvelopeBatch.builder()) .batch( Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) + fill(Envelope.builder()) .log( Log .builder() From e22fd81f0afc4cb75c401f55c520f65a5341cfb7 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 08:22:10 +0100 Subject: [PATCH 13/24] docs: Add deprecation notes --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index ffed2f36f4..d6291337c6 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,41 @@ The `cf-java-client` project is a Java language binding for interacting with a C ## Versions The Cloud Foundry Java Client has two active versions. The `5.x` line is compatible with Spring Boot `2.4.x - 2.6.x` just to manage its dependencies, while the `4.x` line uses Spring Boot `2.3.x`. +## Deprecations + +### `DopplerClient.recentLogs()` — Recent Logs via Doppler + +> [!WARNING] +> **Deprecated since cf-java-client `5.17.x`** +> +> The `DopplerClient.recentLogs()` endpoint (and the related `RecentLogsRequest` / `LogMessage` types from the `org.cloudfoundry.doppler` package) are **deprecated** and will be removed in a future release. +> +> This API relies on the [Loggregator][loggregator] Doppler/Traffic Controller endpoint `/apps/{id}/recentlogs`, which was removed in **Loggregator ≥ 107.0**. +> The affected platform versions are: +> +> | Platform | Last version with Doppler recent-logs support | +> | -------- | --------------------------------------------- | +> | CF Deployment (CFD) | `< 24.3` | +> | Tanzu Application Service (TAS) | `< 4.0` | +> +> **Migration:** Replace any call to `DopplerClient.recentLogs()` with [`LogCacheClient.read()`][log-cache-api] (available via `org.cloudfoundry.logcache.v1.LogCacheClient`). +> +> ```java +> // Before (deprecated) +> dopplerClient.recentLogs(RecentLogsRequest.builder() +> .applicationId(appId) +> .build()); +> +> // After +> logCacheClient.read(ReadRequest.builder() +> .sourceId(appId) +> .envelopeTypes(EnvelopeType.LOG) +> .build()); +> ``` + +[loggregator]: https://github.com/cloudfoundry/loggregator +[log-cache-api]: https://github.com/cloudfoundry/log-cache + ## Dependencies Most projects will need two dependencies; the Operations API and an implementation of the Client API. For Maven, the dependencies would be defined like this: @@ -76,6 +111,9 @@ Both the `cloudfoundry-operations` and `cloudfoundry-client` projects follow a [ ### `CloudFoundryClient`, `DopplerClient`, `UaaClient` Builders +> [!NOTE] +> **`DopplerClient` — partial deprecation:** The `recentLogs()` method on `DopplerClient` is deprecated and only works against Loggregator \< 107.0 (CFD \< 24.3 / TAS \< 4.0). See the [Deprecations](#deprecations) section above for the migration path to `LogCacheClient`. + The lowest-level building blocks of the API are `ConnectionContext` and `TokenProvider`. These types are intended to be shared between instances of the clients, and come with out of the box implementations. To instantiate them, you configure them with builders: ```java From d49d6ab7c662e891b01e1d88976f9018dcab2326 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Thu, 26 Feb 2026 11:40:17 +0100 Subject: [PATCH 14/24] feat: Update the tests --- .../test/java/org/cloudfoundry/operations/ApplicationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 832afe3c07..1fdbd023ab 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -2205,7 +2205,7 @@ private static Mono getAppGuidFromAppName( } private static Log checkOneLogEntry(Log log) { - assertThat(log.getType().equals(LogType.OUT)); + assertThat(log.getType()).isEqualTo(LogType.OUT); OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); return log; } From fc778beeb30bea2ca30f20c81dcf8a42bc081aff Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Fri, 27 Feb 2026 11:44:36 +0100 Subject: [PATCH 15/24] STDERR logs should not let test fail --- .../test/java/org/cloudfoundry/operations/ApplicationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 1fdbd023ab..03903e028f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -2205,8 +2205,8 @@ private static Mono getAppGuidFromAppName( } private static Log checkOneLogEntry(Log log) { - assertThat(log.getType()).isEqualTo(LogType.OUT); OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); + assertThat(log.getType()).isIn(LogType.OUT, LogType.ERR); return log; } } From 5c673097cac33ce1985f943863f5e17c1cfbf332 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 09:24:02 +0100 Subject: [PATCH 16/24] fix: Adjust the lint issues --- .../applications/DefaultApplicationsTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 657fb7d561..d189b2c0fe 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5080,12 +5080,12 @@ private static void requestInstancesApplicationFailing( private static void requestGetApplicationTimeout( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) + .applicationsV2() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill(GetApplicationResponse.builder()) From 7e4468d7dabfdcbc329ad4aaebdf264cd30b9d60 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Thu, 26 Feb 2026 11:40:17 +0100 Subject: [PATCH 17/24] Revert some unnecessary style changes --- .../applications/DefaultApplicationsTest.java | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index d189b2c0fe..6fab68c50c 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -1315,15 +1315,16 @@ void listTasks() { @SuppressWarnings("deprecation") @Test - void logsRecentDoppler() { + void logsDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() @@ -1349,15 +1350,16 @@ void logsNoAppDoppler() { @SuppressWarnings("deprecation") @Test - void logsDoppler() { + void logsRecentDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); + requestLogsRecent(this.dopplerClient, "test-metadata-id"); + this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() @@ -1372,6 +1374,7 @@ void logsRecentLogCache() { TEST_SPACE_ID, "test-metadata-id"); requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications .logsRecent(ReadRequest.builder().sourceId("test-metadata-id").build()) .as(StepVerifier::create) @@ -5096,31 +5099,6 @@ private static void requestGetApplicationTimeout( .build())); } - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String sourceId, String payload) { - when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(Envelope.builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static void requestGetApplicationV3Buildpack( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient @@ -5367,8 +5345,10 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) + @SuppressWarnings("deprecation") + private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.recentLogs( + RecentLogsRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( org.cloudfoundry.doppler.Envelope.builder() @@ -5379,10 +5359,33 @@ private static void requestLogsStream(DopplerClient dopplerClient, String applic .build())); } - @SuppressWarnings("deprecation") - private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.recentLogs( - RecentLogsRequest.builder().applicationId(applicationId).build())) + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String sourceId, String payload) { + when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(Envelope.builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); + } + + private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( org.cloudfoundry.doppler.Envelope.builder() From b07d7e9761c246f5ec2743ba5f9e883efd2dd1d4 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 17 Mar 2026 09:59:23 +0100 Subject: [PATCH 18/24] feat: Cleanup the recentLogs endpoint --- .../reactor/logcache/v1/ReactorLogCacheEndpoints.java | 4 ---- .../reactor/logcache/v1/_ReactorLogCacheClient.java | 5 ----- .../main/java/org/cloudfoundry/doppler/DopplerClient.java | 4 ++-- .../java/org/cloudfoundry/logcache/v1/LogCacheClient.java | 8 -------- .../operations/applications/DefaultApplications.java | 2 +- .../operations/applications/DefaultApplicationsTest.java | 2 +- 6 files changed, 4 insertions(+), 21 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java index 3d1623cef3..2e68c52538 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java @@ -48,8 +48,4 @@ Mono meta(MetaRequest request) { Mono read(ReadRequest request) { return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); } - - Mono recentLogs(ReadRequest request) { - return read(request); - } } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java index 286f4c787c..d9460476ea 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java @@ -53,11 +53,6 @@ public Mono read(ReadRequest request) { return getReactorLogCacheEndpoints().read(request); } - @Override - public Mono recentLogs(ReadRequest request) { - return getReactorLogCacheEndpoints().recentLogs(request); - } - /** * The connection context */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index 74d23b1488..3d61922734 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -42,13 +42,13 @@ public interface DopplerClient { /** * Makes the Recent Logs request * - * @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest)} instead. + * @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#read(org.cloudfoundry.logcache.v1.ReadRequest)} instead. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} and {@code TAS < 4.0}. * @param request the Recent Logs request * @return a flux of events from the recent logs * @see Loggregator * @see Log Cache - * @see org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest) + * @see org.cloudfoundry.logcache.v1.LogCacheClient#read(org.cloudfoundry.logcache.v1.ReadRequest) */ @Deprecated Flux recentLogs(RecentLogsRequest request); diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java index 8a9b08505c..e455db220a 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java @@ -46,12 +46,4 @@ public interface LogCacheClient { * @return the read response */ Mono read(ReadRequest request); - - /** - * Makes the Log Cache RecentLogs /api/v1/read request - * - * @param request the Recent Logs request - * @return the events from the recent logs - */ - Mono recentLogs(ReadRequest request); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 2f1bdef41a..1a78fce2cf 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -2545,7 +2545,7 @@ private static Mono requestLogsRecentLogCache( Mono logCacheClient, ReadRequest readRequest) { return logCacheClient.flatMap( client -> - client.recentLogs(readRequest) + client.read(readRequest) .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))); } diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 6fab68c50c..a5724a5efa 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5361,7 +5361,7 @@ private static void requestLogsRecent(DopplerClient dopplerClient, String applic private static void requestLogsRecentLogCache( LogCacheClient logCacheClient, String sourceId, String payload) { - when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) + when(logCacheClient.read(ReadRequest.builder().sourceId(sourceId).build())) .thenReturn( Mono.just( fill(ReadResponse.builder()) From 7908b9c27ac0b81903f37e45baf8dbd59ceb6aa0 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 17 Mar 2026 10:19:42 +0100 Subject: [PATCH 19/24] feat: Update the code --- .../operations/applications/DefaultApplications.java | 7 ++----- .../java/org/cloudfoundry/operations/ApplicationsTest.java | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 1a78fce2cf..b247261d10 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -1640,12 +1640,9 @@ private static Flux getLogs( private static Flux getRecentLogsLogCache( Mono logCacheClient, ReadRequest readRequest) { return requestLogsRecentLogCache(logCacheClient, readRequest) - .map(EnvelopeBatch::getBatch) - .map(List::stream) - .flatMapIterable(envelopeStream -> envelopeStream.collect(Collectors.toList())) - .filter(e -> e.getLog() != null) + .flatMapIterable(EnvelopeBatch::getBatch) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .map(org.cloudfoundry.logcache.v1.Envelope::getLog); + .mapNotNull(org.cloudfoundry.logcache.v1.Envelope::getLog); } @SuppressWarnings("unchecked") diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 03903e028f..d5455d5b1e 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -542,6 +542,7 @@ public void logs() throws IOException { } @Test + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v2) public void logsRecent() throws IOException { String applicationName = this.nameFactory.getApplicationName(); Mono applicationGuid = From 43e16f6135e5060b8381a8fdecaca2f1b19c82de Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Fri, 20 Mar 2026 16:45:06 +0100 Subject: [PATCH 20/24] Migrate logs(ApplicationLogsRequest) from Doppler to LogCache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use LogCacheClient.read() for recent logs (the default), fall back to Doppler streaming when recent is explicitly false. Remove logCacheLogs() integration test — it was a temporary reference for the direct LogCache API, now redundant since logs() exercises the same path. Remove logsRecent() and its helpers likewise. --- .../operations/applications/Applications.java | 15 +-- .../applications/DefaultApplications.java | 94 ++++++++++------ .../applications/DefaultApplicationsTest.java | 29 +++-- .../operations/ApplicationsTest.java | 102 ------------------ 4 files changed, 82 insertions(+), 158 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 53317ccb63..8e77502bb3 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -17,8 +17,6 @@ package org.cloudfoundry.operations.applications; import org.cloudfoundry.doppler.LogMessage; -import org.cloudfoundry.logcache.v1.Log; -import org.cloudfoundry.logcache.v1.ReadRequest; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -129,18 +127,7 @@ public interface Applications { Flux logs(LogsRequest request); /** - * List the applications logs from logCacheClient. - * If no messages are available, an empty Flux is returned. - * - * @param request the application logs request - * @return the applications logs - */ - Flux logsRecent(ReadRequest request); - - /** - * List the applications logs. - * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} - * and {@code TAS < 4.0}. + * List the applications logs. Uses Log Cache under the hood. * * @param request the application logs request * @return the applications logs diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index b247261d10..2f8d1aab79 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -155,7 +155,6 @@ import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.util.OperationsLogging; @@ -558,31 +557,25 @@ public Flux logs(LogsRequest request) { .checkpoint(); } - @Override - public Flux logsRecent(ReadRequest request) { - return getRecentLogsLogCache(this.logCacheClient, request) - .transform(OperationsLogging.log("Get Application Logs")) - .checkpoint(); - } - @Override public Flux logs(ApplicationLogsRequest request) { - return logs(LogsRequest.builder() - .name(request.getName()) - .recent(request.getRecent()) - .build()) - .map( - logMessage -> - ApplicationLog.builder() - .sourceId(logMessage.getApplicationId()) - .sourceType(logMessage.getSourceType()) - .instanceId(logMessage.getSourceInstance()) - .message(logMessage.getMessage()) - .timestamp(logMessage.getTimestamp()) - .logType( - ApplicationLogType.from( - logMessage.getMessageType().name())) - .build()); + if (Optional.ofNullable(request.getRecent()).orElse(true)) { + return Mono.zip(this.cloudFoundryClient, this.spaceId) + .flatMap( + function( + (cloudFoundryClient, spaceId) -> + getApplicationId( + cloudFoundryClient, + request.getName(), + spaceId))) + .flatMapMany( + applicationId -> getLogsLogCache(this.logCacheClient, applicationId)) + .transform(OperationsLogging.log("Get Application Logs")) + .checkpoint(); + } else { + return logs(LogsRequest.builder().name(request.getName()).recent(false).build()) + .map(DefaultApplications::toApplicationLog); + } } @Override @@ -1637,12 +1630,30 @@ private static Flux getLogs( } } - private static Flux getRecentLogsLogCache( - Mono logCacheClient, ReadRequest readRequest) { - return requestLogsRecentLogCache(logCacheClient, readRequest) - .flatMapIterable(EnvelopeBatch::getBatch) + private static Flux getLogsLogCache( + Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> e.getLog() != null) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .mapNotNull(org.cloudfoundry.logcache.v1.Envelope::getLog); + .map( + envelope -> + ApplicationLog.builder() + .sourceId( + Optional.ofNullable(envelope.getSourceId()) + .orElse("")) + .sourceType( + envelope.getTags().getOrDefault("source_type", "")) + .instanceId( + Optional.ofNullable(envelope.getInstanceId()) + .orElse("")) + .message(envelope.getLog().getPayloadAsText()) + .timestamp( + Optional.ofNullable(envelope.getTimestamp()) + .orElse(0L)) + .logType( + ApplicationLogType.from( + envelope.getLog().getType().name())) + .build()); } @SuppressWarnings("unchecked") @@ -2538,12 +2549,14 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } - private static Mono requestLogsRecentLogCache( - Mono logCacheClient, ReadRequest readRequest) { - return logCacheClient.flatMap( - client -> - client.read(readRequest) - .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))); + private static Flux requestLogsRecentLogCache( + Mono logCacheClient, String applicationId) { + return logCacheClient + .flatMap( + client -> + client.read(ReadRequest.builder().sourceId(applicationId).build())) + .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes())) + .flatMapIterable(EnvelopeBatch::getBatch); } private static Flux requestLogsStream( @@ -2951,6 +2964,17 @@ private static Mono stopApplicationIfNotStopped( : Mono.just(resource); } + private static ApplicationLog toApplicationLog(LogMessage logMessage) { + return ApplicationLog.builder() + .sourceId(logMessage.getApplicationId()) + .sourceType(logMessage.getSourceType()) + .instanceId(logMessage.getSourceInstance()) + .message(logMessage.getMessage()) + .timestamp(logMessage.getTimestamp()) + .logType(ApplicationLogType.from(logMessage.getMessageType().name())) + .build(); + } + private static ApplicationDetail toApplicationDetail( List buildpacks, SummaryApplicationResponse summaryApplicationResponse, diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index a5724a5efa..5d21f8e584 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -25,9 +25,11 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Duration; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -1367,18 +1369,25 @@ void logsRecentDoppler() { } @Test - void logsRecentLogCache() { + void logsLogCache() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications - .logsRecent(ReadRequest.builder().sourceId("test-metadata-id").build()) + .logs(ApplicationLogsRequest.builder().name("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(Log.builder()).type(LogType.OUT).build()) + .expectNextMatches( + log -> + log.getMessage().equals("test-payload") + && log.getLogType() == ApplicationLogType.OUT + && log.getSourceId().equals("test-sourceId") + && log.getInstanceId().equals("test-instanceId") + && log.getSourceType().equals("APP/PROC/WEB") + && log.getTimestamp() == 1L) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -5359,8 +5368,9 @@ private static void requestLogsRecent(DopplerClient dopplerClient, String applic .build())); } - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String sourceId, String payload) { + private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String sourceId) { + String base64Payload = + Base64.getEncoder().encodeToString("test-payload".getBytes(StandardCharsets.UTF_8)); when(logCacheClient.read(ReadRequest.builder().sourceId(sourceId).build())) .thenReturn( Mono.just( @@ -5370,11 +5380,16 @@ private static void requestLogsRecentLogCache( .batch( Arrays.asList( fill(Envelope.builder()) + .tags( + Collections + .singletonMap( + "source_type", + "APP/PROC/WEB")) .log( Log .builder() .payload( - payload) + base64Payload) .type( LogType .OUT) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index d5455d5b1e..8d0b81e49f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -25,20 +25,11 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.logging.Level; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.logcache.v1.Envelope; -import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.EnvelopeType; -import org.cloudfoundry.logcache.v1.Log; -import org.cloudfoundry.logcache.v1.LogCacheClient; -import org.cloudfoundry.logcache.v1.LogType; -import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationEnvironments; import org.cloudfoundry.operations.applications.ApplicationEvent; @@ -87,7 +78,6 @@ import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.operations.services.GetServiceInstanceRequest; import org.cloudfoundry.operations.services.ServiceInstance; -import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.FluentMap; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -95,7 +85,6 @@ import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.SignalType; import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass @@ -111,7 +100,6 @@ public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private String serviceName; - @Autowired private LogCacheClient logCacheClient; @Autowired private CloudFoundryClient cloudFoundryClient; // To create a service in #pushBindService, the Service Broker must be installed first. @@ -514,7 +502,6 @@ public void listTasks() throws IOException { * Doppler was dropped in PCF 4.x in favor of logcache. This test does not work * on TAS 4.x. */ - @Deprecated @Test @IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2) public void logs() throws IOException { @@ -541,72 +528,6 @@ public void logs() throws IOException { .verify(Duration.ofMinutes(5)); } - @Test - @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v2) - public void logsRecent() throws IOException { - String applicationName = this.nameFactory.getApplicationName(); - Mono applicationGuid = - getAppGuidFromAppName(cloudFoundryOperations, applicationName); - createApplication( - this.cloudFoundryOperations, - new ClassPathResource("test-application.zip").getFile().toPath(), - applicationName, - false) - .then( - applicationGuid - .map(ApplicationsTest::getReadRequest) - .flatMapMany( - readRequest -> - callLogsRecent( - this.cloudFoundryOperations, - readRequest) - .log(null, Level.ALL, SignalType.ON_NEXT)) - .map(ApplicationsTest::checkOneLogEntry) - .then()) - .as(StepVerifier::create) - .expectComplete() - .verify(Duration.ofMinutes(5)); - } - - /** - * Exercise the LogCache client. Serves as a reference for using the logcache client, - * and will help with the transition to the new - * {@link org.cloudfoundry.operations.applications.Applications#logs(ApplicationLogsRequest)}. - */ - @Test - public void logCacheLogs() throws IOException { - String applicationName = this.nameFactory.getApplicationName(); - - createApplication( - this.cloudFoundryOperations, - new ClassPathResource("test-application.zip").getFile().toPath(), - applicationName, - false) - .then( - this.cloudFoundryOperations - .applications() - .get(GetApplicationRequest.builder().name(applicationName).build())) - .map(ApplicationDetail::getId) - .flatMapMany( - appGuid -> - this.logCacheClient.read( - ReadRequest.builder() - .sourceId(appGuid) - .envelopeType(EnvelopeType.LOG) - .limit(1) - .build())) - .map(ReadResponse::getEnvelopes) - .map(EnvelopeBatch::getBatch) - .flatMap(Flux::fromIterable) - .map(Envelope::getLog) - .map(Log::getType) - .next() - .as(StepVerifier::create) - .expectNext(LogType.OUT) - .expectComplete() - .verify(Duration.ofMinutes(5)); - } - @Test public void pushBindServices() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -2187,27 +2108,4 @@ private static Mono requestSshEnabled( .applications() .sshEnabled(ApplicationSshEnabledRequest.builder().name(applicationName).build()); } - - private static ReadRequest getReadRequest(String applicationId) { - return ReadRequest.builder().sourceId(applicationId).build(); - } - - private static Flux callLogsRecent( - CloudFoundryOperations cloudFoundryOperations, ReadRequest readRequest) { - return cloudFoundryOperations.applications().logsRecent(readRequest); - } - - private static Mono getAppGuidFromAppName( - CloudFoundryOperations cloudFoundryOperations, String applicationName) { - return cloudFoundryOperations - .applications() - .get(GetApplicationRequest.builder().name(applicationName).build()) - .map(ApplicationDetail::getId); - } - - private static Log checkOneLogEntry(Log log) { - OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); - assertThat(log.getType()).isIn(LogType.OUT, LogType.ERR); - return log; - } } From 5218d2783bbfcae783cc40c3f82f879535077096 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Fri, 20 Mar 2026 16:45:20 +0100 Subject: [PATCH 21/24] Update logs() integration test javadoc and version gate LogCache has been available since cf-deployment v3.0.0 (July 2018). Lower the version gate from PCF_4_v2 to PCF_2_3 and update the javadoc to reflect that the test now exercises the LogCache-backed path. --- .../org/cloudfoundry/operations/ApplicationsTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 8d0b81e49f..37c701dbbe 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -499,11 +499,13 @@ public void listTasks() throws IOException { } /** - * Doppler was dropped in PCF 4.x in favor of logcache. This test does not work - * on TAS 4.x. + * Exercise the LogCache client via {@code logs(ApplicationLogsRequest)}. + * LogCache has been a default cf-deployment component since v3.0.0 (July 2018), + * with the {@code /api/v1/read} endpoint available since log-cache-release v2.0.0 + * (October 2018). */ @Test - @IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2) + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_3) public void logs() throws IOException { String applicationName = this.nameFactory.getApplicationName(); From a94615474f545a34c803c76945e9137b8cd57e26 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Mon, 23 Mar 2026 09:53:04 +0100 Subject: [PATCH 22/24] Add Operations API note to README deprecation section logs(ApplicationLogsRequest) now uses Log Cache internally, so Operations API users do not need to migrate. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d6291337c6..058431f921 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ The Cloud Foundry Java Client has two active versions. The `5.x` line is compati > .build()); > ``` +> [!NOTE] +> **Operations API users:** `Applications.logs(ApplicationLogsRequest)` now uses Log Cache under the hood for recent logs (the default). No migration is needed at the Operations layer. + [loggregator]: https://github.com/cloudfoundry/loggregator [log-cache-api]: https://github.com/cloudfoundry/log-cache From b0a5616cce5c29816643c0d46d75a89aad9c20f2 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Tue, 24 Mar 2026 12:29:48 +0100 Subject: [PATCH 23/24] Reference Doppler and shared nothing architecture --- .../cloudfoundry/operations/applications/Applications.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 8e77502bb3..56aba6af64 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -127,7 +127,10 @@ public interface Applications { Flux logs(LogsRequest request); /** - * List the applications logs. Uses Log Cache under the hood. + * List the applications logs. + * Uses Log Cache under the hood when {@link ApplicationLogsRequest#getRecent()} is {@code true}. + * Log streaming still uses Doppler, which is not available in CF deployments following + * shared-nothing architecture. * * @param request the application logs request * @return the applications logs From 9586fb8c987444e2a4ef8796a15bb2e976b57786 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Tue, 24 Mar 2026 12:34:34 +0100 Subject: [PATCH 24/24] Use simple null check instead of Optional --- .../operations/applications/DefaultApplications.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 2f8d1aab79..bb492fffe7 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -559,7 +559,7 @@ public Flux logs(LogsRequest request) { @Override public Flux logs(ApplicationLogsRequest request) { - if (Optional.ofNullable(request.getRecent()).orElse(true)) { + if (request.getRecent() == null || request.getRecent()) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function(