Skip to content

Commit 21d6d25

Browse files
authored
feat: Replace deprecated Doppler recentLogs with Log Cache client (#1348)
See gh-1237, gh-1348
1 parent d100933 commit 21d6d25

File tree

9 files changed

+234
-82
lines changed

9 files changed

+234
-82
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,44 @@ The `cf-java-client` project is a Java language binding for interacting with a C
1717
## Versions
1818
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`.
1919

20+
## Deprecations
21+
22+
### `DopplerClient.recentLogs()` — Recent Logs via Doppler
23+
24+
> [!WARNING]
25+
> **Deprecated since cf-java-client `5.17.x`**
26+
>
27+
> 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.
28+
>
29+
> This API relies on the [Loggregator][loggregator] Doppler/Traffic Controller endpoint `/apps/{id}/recentlogs`, which was removed in **Loggregator ≥ 107.0**.
30+
> The affected platform versions are:
31+
>
32+
> | Platform | Last version with Doppler recent-logs support |
33+
> | -------- | --------------------------------------------- |
34+
> | CF Deployment (CFD) | `< 24.3` |
35+
> | Tanzu Application Service (TAS) | `< 4.0` |
36+
>
37+
> **Migration:** Replace any call to `DopplerClient.recentLogs()` with [`LogCacheClient.read()`][log-cache-api] (available via `org.cloudfoundry.logcache.v1.LogCacheClient`).
38+
>
39+
> ```java
40+
> // Before (deprecated)
41+
> dopplerClient.recentLogs(RecentLogsRequest.builder()
42+
> .applicationId(appId)
43+
> .build());
44+
>
45+
> // After
46+
> logCacheClient.read(ReadRequest.builder()
47+
> .sourceId(appId)
48+
> .envelopeTypes(EnvelopeType.LOG)
49+
> .build());
50+
> ```
51+
52+
> [!NOTE]
53+
> **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.
54+
55+
[loggregator]: https://github.com/cloudfoundry/loggregator
56+
[log-cache-api]: https://github.com/cloudfoundry/log-cache
57+
2058
## Dependencies
2159
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:
2260
@@ -76,6 +114,9 @@ Both the `cloudfoundry-operations` and `cloudfoundry-client` projects follow a [
76114

77115
### `CloudFoundryClient`, `DopplerClient`, `UaaClient` Builders
78116

117+
> [!NOTE]
118+
> **`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`.
119+
79120
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:
80121

81122
```java

cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,15 @@ public interface DopplerClient {
4242
/**
4343
* Makes the <a href="https://github.com/cloudfoundry/loggregator/tree/develop/src/trafficcontroller#endpoints">Recent Logs</a> request
4444
*
45+
* @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#read(org.cloudfoundry.logcache.v1.ReadRequest)} instead.
46+
* Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} and {@code TAS < 4.0}.
4547
* @param request the Recent Logs request
46-
* @return the events from the recent logs
48+
* @return a flux of events from the recent logs
49+
* @see <a href="https://github.com/cloudfoundry/loggregator">Loggregator</a>
50+
* @see <a href="https://github.com/cloudfoundry/log-cache">Log Cache</a>
51+
* @see org.cloudfoundry.logcache.v1.LogCacheClient#read(org.cloudfoundry.logcache.v1.ReadRequest)
4752
*/
53+
@Deprecated
4854
Flux<Envelope> recentLogs(RecentLogsRequest request);
4955

5056
/**

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.cloudfoundry.client.v3.spaces.ListSpacesRequest;
2424
import org.cloudfoundry.client.v3.spaces.SpaceResource;
2525
import org.cloudfoundry.doppler.DopplerClient;
26+
import org.cloudfoundry.logcache.v1.LogCacheClient;
2627
import org.cloudfoundry.networking.NetworkingClient;
2728
import org.cloudfoundry.operations.advanced.Advanced;
2829
import org.cloudfoundry.operations.advanced.DefaultAdvanced;
@@ -79,7 +80,7 @@ public Advanced advanced() {
7980
@Override
8081
@Value.Derived
8182
public Applications applications() {
82-
return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getSpaceId());
83+
return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getLogCacheClientPublisher(), getSpaceId());
8384
}
8485

8586
@Override
@@ -185,6 +186,19 @@ Mono<DopplerClient> getDopplerClientPublisher() {
185186
.orElse(Mono.error(new IllegalStateException("DopplerClient must be set")));
186187
}
187188

189+
/**
190+
* The {@link LogCacheClient} to use for operations functionality
191+
*/
192+
@Nullable
193+
abstract LogCacheClient getLogCacheClient();
194+
195+
@Value.Derived
196+
Mono<LogCacheClient> getLogCacheClientPublisher() {
197+
return Optional.ofNullable(getLogCacheClient())
198+
.map(Mono::just)
199+
.orElse(Mono.error(new IllegalStateException("LogCacheClient must be set")));
200+
}
201+
188202
/**
189203
* The {@link NetworkingClient} to use for operations functionality
190204
*/

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,9 @@ public interface Applications {
128128

129129
/**
130130
* List the applications logs.
131-
* Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3}
132-
* and {@code TAS < 4.0}.
131+
* Uses Log Cache under the hood when {@link ApplicationLogsRequest#getRecent()} is {@code true}.
132+
* Log streaming still uses Doppler, which is not available in CF deployments following
133+
* <a href="https://docs.cloudfoundry.org/loggregator/architecture.html#shared-nothing-architecture">shared-nothing architecture</a>.
133134
*
134135
* @param request the application logs request
135136
* @return the applications logs

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@
154154
import org.cloudfoundry.doppler.LogMessage;
155155
import org.cloudfoundry.doppler.RecentLogsRequest;
156156
import org.cloudfoundry.doppler.StreamRequest;
157+
import org.cloudfoundry.logcache.v1.EnvelopeBatch;
158+
import org.cloudfoundry.logcache.v1.LogCacheClient;
159+
import org.cloudfoundry.logcache.v1.ReadRequest;
157160
import org.cloudfoundry.operations.util.OperationsLogging;
158161
import org.cloudfoundry.util.DateUtils;
159162
import org.cloudfoundry.util.DelayTimeoutException;
@@ -200,6 +203,10 @@ public final class DefaultApplications implements Applications {
200203
private static final Comparator<LogMessage> LOG_MESSAGE_COMPARATOR =
201204
Comparator.comparing(LogMessage::getTimestamp);
202205

206+
private static final Comparator<org.cloudfoundry.logcache.v1.Envelope>
207+
LOG_MESSAGE_COMPARATOR_LOG_CACHE =
208+
Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp);
209+
203210
private static final Duration LOG_MESSAGE_TIMESPAN = Duration.ofMillis(500);
204211

205212
private static final int MAX_NUMBER_OF_RECENT_EVENTS = 50;
@@ -214,24 +221,29 @@ public final class DefaultApplications implements Applications {
214221

215222
private final Mono<DopplerClient> dopplerClient;
216223

224+
private final Mono<LogCacheClient> logCacheClient;
225+
217226
private final RandomWords randomWords;
218227

219228
private final Mono<String> spaceId;
220229

221230
public DefaultApplications(
222231
Mono<CloudFoundryClient> cloudFoundryClient,
223232
Mono<DopplerClient> dopplerClient,
233+
Mono<LogCacheClient> logCacheClient,
224234
Mono<String> spaceId) {
225-
this(cloudFoundryClient, dopplerClient, new WordListRandomWords(), spaceId);
235+
this(cloudFoundryClient, dopplerClient, logCacheClient, new WordListRandomWords(), spaceId);
226236
}
227237

228238
DefaultApplications(
229239
Mono<CloudFoundryClient> cloudFoundryClient,
230240
Mono<DopplerClient> dopplerClient,
241+
Mono<LogCacheClient> logCacheClient,
231242
RandomWords randomWords,
232243
Mono<String> spaceId) {
233244
this.cloudFoundryClient = cloudFoundryClient;
234245
this.dopplerClient = dopplerClient;
246+
this.logCacheClient = logCacheClient;
235247
this.randomWords = randomWords;
236248
this.spaceId = spaceId;
237249
}
@@ -529,6 +541,7 @@ public Flux<Task> listTasks(ListApplicationTasksRequest request) {
529541
.checkpoint();
530542
}
531543

544+
@Deprecated
532545
@Override
533546
public Flux<LogMessage> logs(LogsRequest request) {
534547
return Mono.zip(this.cloudFoundryClient, this.spaceId)
@@ -546,22 +559,23 @@ public Flux<LogMessage> logs(LogsRequest request) {
546559

547560
@Override
548561
public Flux<ApplicationLog> logs(ApplicationLogsRequest request) {
549-
return logs(LogsRequest.builder()
550-
.name(request.getName())
551-
.recent(request.getRecent())
552-
.build())
553-
.map(
554-
logMessage ->
555-
ApplicationLog.builder()
556-
.sourceId(logMessage.getApplicationId())
557-
.sourceType(logMessage.getSourceType())
558-
.instanceId(logMessage.getSourceInstance())
559-
.message(logMessage.getMessage())
560-
.timestamp(logMessage.getTimestamp())
561-
.logType(
562-
ApplicationLogType.from(
563-
logMessage.getMessageType().name()))
564-
.build());
562+
if (request.getRecent() == null || request.getRecent()) {
563+
return Mono.zip(this.cloudFoundryClient, this.spaceId)
564+
.flatMap(
565+
function(
566+
(cloudFoundryClient, spaceId) ->
567+
getApplicationId(
568+
cloudFoundryClient,
569+
request.getName(),
570+
spaceId)))
571+
.flatMapMany(
572+
applicationId -> getLogsLogCache(this.logCacheClient, applicationId))
573+
.transform(OperationsLogging.log("Get Application Logs"))
574+
.checkpoint();
575+
} else {
576+
return logs(LogsRequest.builder().name(request.getName()).recent(false).build())
577+
.map(DefaultApplications::toApplicationLog);
578+
}
565579
}
566580

567581
@Override
@@ -673,7 +687,6 @@ public Mono<Void> pushManifestV3(PushManifestV3Request request) {
673687
} catch (IOException e) {
674688
throw new RuntimeException("Could not serialize manifest", e);
675689
}
676-
677690
return Mono.zip(this.cloudFoundryClient, this.spaceId)
678691
.flatMap(
679692
function(
@@ -1617,6 +1630,32 @@ private static Flux<LogMessage> getLogs(
16171630
}
16181631
}
16191632

1633+
private static Flux<ApplicationLog> getLogsLogCache(
1634+
Mono<LogCacheClient> logCacheClient, String applicationId) {
1635+
return requestLogsRecentLogCache(logCacheClient, applicationId)
1636+
.filter(e -> e.getLog() != null)
1637+
.sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE)
1638+
.map(
1639+
envelope ->
1640+
ApplicationLog.builder()
1641+
.sourceId(
1642+
Optional.ofNullable(envelope.getSourceId())
1643+
.orElse(""))
1644+
.sourceType(
1645+
envelope.getTags().getOrDefault("source_type", ""))
1646+
.instanceId(
1647+
Optional.ofNullable(envelope.getInstanceId())
1648+
.orElse(""))
1649+
.message(envelope.getLog().getPayloadAsText())
1650+
.timestamp(
1651+
Optional.ofNullable(envelope.getTimestamp())
1652+
.orElse(0L))
1653+
.logType(
1654+
ApplicationLogType.from(
1655+
envelope.getLog().getType().name()))
1656+
.build());
1657+
}
1658+
16201659
@SuppressWarnings("unchecked")
16211660
private static Map<String, Object> getMetadataRequest(EventEntity entity) {
16221661
Map<String, Optional<Object>> metadata =
@@ -2501,6 +2540,7 @@ private static Flux<TaskResource> requestListTasks(
25012540
.build()));
25022541
}
25032542

2543+
@Deprecated
25042544
private static Flux<Envelope> requestLogsRecent(
25052545
Mono<DopplerClient> dopplerClient, String applicationId) {
25062546
return dopplerClient.flatMapMany(
@@ -2509,6 +2549,16 @@ private static Flux<Envelope> requestLogsRecent(
25092549
RecentLogsRequest.builder().applicationId(applicationId).build()));
25102550
}
25112551

2552+
private static Flux<org.cloudfoundry.logcache.v1.Envelope> requestLogsRecentLogCache(
2553+
Mono<LogCacheClient> logCacheClient, String applicationId) {
2554+
return logCacheClient
2555+
.flatMap(
2556+
client ->
2557+
client.read(ReadRequest.builder().sourceId(applicationId).build()))
2558+
.flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))
2559+
.flatMapIterable(EnvelopeBatch::getBatch);
2560+
}
2561+
25122562
private static Flux<Envelope> requestLogsStream(
25132563
Mono<DopplerClient> dopplerClient, String applicationId) {
25142564
return dopplerClient.flatMapMany(
@@ -2914,6 +2964,17 @@ private static Mono<AbstractApplicationResource> stopApplicationIfNotStopped(
29142964
: Mono.just(resource);
29152965
}
29162966

2967+
private static ApplicationLog toApplicationLog(LogMessage logMessage) {
2968+
return ApplicationLog.builder()
2969+
.sourceId(logMessage.getApplicationId())
2970+
.sourceType(logMessage.getSourceType())
2971+
.instanceId(logMessage.getSourceInstance())
2972+
.message(logMessage.getMessage())
2973+
.timestamp(logMessage.getTimestamp())
2974+
.logType(ApplicationLogType.from(logMessage.getMessageType().name()))
2975+
.build();
2976+
}
2977+
29172978
private static ApplicationDetail toApplicationDetail(
29182979
List<String> buildpacks,
29192980
SummaryApplicationResponse summaryApplicationResponse,

cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.cloudfoundry.client.v3.spaces.SpacesV3;
5252
import org.cloudfoundry.client.v3.tasks.Tasks;
5353
import org.cloudfoundry.doppler.DopplerClient;
54+
import org.cloudfoundry.logcache.v1.LogCacheClient;
5455
import org.cloudfoundry.routing.RoutingClient;
5556
import org.cloudfoundry.routing.v1.routergroups.RouterGroups;
5657
import org.cloudfoundry.uaa.UaaClient;
@@ -101,6 +102,8 @@ public abstract class AbstractOperationsTest {
101102

102103
protected final DopplerClient dopplerClient = mock(DopplerClient.class, RETURNS_SMART_NULLS);
103104

105+
protected final LogCacheClient logCacheClient = mock(LogCacheClient.class, RETURNS_SMART_NULLS);
106+
104107
protected final Events events = mock(Events.class, RETURNS_SMART_NULLS);
105108

106109
protected final FeatureFlags featureFlags = mock(FeatureFlags.class, RETURNS_SMART_NULLS);

0 commit comments

Comments
 (0)