Deleting logging modules
This commit is contained in:
@@ -426,8 +426,7 @@ def getTestProjects() {
|
||||
|
||||
def getStarterProjects() {
|
||||
[project(":spring-cloud-starter-app-broker"),
|
||||
project(":spring-cloud-starter-app-broker-cloudfoundry"),
|
||||
project(":spring-cloud-starter-app-broker-logging")] as Set
|
||||
project(":spring-cloud-starter-app-broker-cloudfoundry")] as Set
|
||||
}
|
||||
|
||||
def getLibraryProjects() {
|
||||
|
||||
@@ -32,7 +32,5 @@ include "spring-cloud-app-broker-autoconfigure"
|
||||
include "spring-cloud-app-broker-integration-tests"
|
||||
include "spring-cloud-app-broker-acceptance-tests"
|
||||
include "spring-cloud-app-broker-security-credhub"
|
||||
include "spring-cloud-app-broker-logging"
|
||||
include "spring-cloud-starter-app-broker"
|
||||
include "spring-cloud-starter-app-broker-cloudfoundry"
|
||||
include "spring-cloud-starter-app-broker-logging"
|
||||
|
||||
@@ -22,12 +22,6 @@ plugins {
|
||||
|
||||
description = "Spring Cloud App Broker Autoconfiguration"
|
||||
|
||||
java {
|
||||
registerFeature("logging") {
|
||||
usingSourceSet(sourceSets.main)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor platform(SpringBootPlugin.BOM_COORDINATES)
|
||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
@@ -43,8 +37,6 @@ dependencies {
|
||||
api "org.cloudfoundry:cloudfoundry-operations:${cfJavaClientVersion}"
|
||||
api "org.springframework.credhub:spring-credhub-starter:${springCredhubVersion}"
|
||||
|
||||
loggingImplementation project(":spring-cloud-app-broker-logging")
|
||||
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-webflux"
|
||||
testImplementation "io.projectreactor.tools:blockhound-junit-platform:${blockHoundVersion}"
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.doppler.Envelope;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.ApplicationLogStreamPublisher;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.DopplerLogStreamPublisher;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.LogStreamPublisher;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.endpoint.StreamingLogWebSocketHandler;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApplicationLogStreamPublisher.class)
|
||||
@ConditionalOnBean(ApplicationIdsProvider.class)
|
||||
public class ServiceInstanceLogStreamAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public StreamingLogWebSocketHandler streamingLogWebSocketHandler(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
return new StreamingLogWebSocketHandler(applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebSocketHandlerAdapter handlerAdapter() {
|
||||
return new WebSocketHandlerAdapter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerMapping logsHandlerMapping(StreamingLogWebSocketHandler webSocketHandler) {
|
||||
Map<String, WebSocketHandler> map = new HashMap<>();
|
||||
map.put("/logs/**", webSocketHandler);
|
||||
|
||||
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
|
||||
handlerMapping.setOrder(1);
|
||||
handlerMapping.setUrlMap(map);
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogStreamPublisher<Envelope> streamLogsPublisher(CloudFoundryClient cloudFoundryClient,
|
||||
DopplerClient dopplerClient, ApplicationIdsProvider applicationIdsProvider) {
|
||||
return new DopplerLogStreamPublisher(cloudFoundryClient, dopplerClient, applicationIdsProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ApplicationLogStreamPublisher applicationLogsPublisher(LogStreamPublisher<Envelope> logStreamPublisher,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
return new ApplicationLogStreamPublisher(logStreamPublisher, eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.autoconfigure;
|
||||
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.ApplicationRecentLogsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.RecentLogsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.endpoint.RecentLogsController;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApplicationRecentLogsProvider.class)
|
||||
@ConditionalOnBean(ApplicationIdsProvider.class)
|
||||
public class ServiceInstanceRecentLogsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public RecentLogsProvider recentLogsProvider(CloudFoundryClient cloudFoundryClient, DopplerClient dopplerClient,
|
||||
ApplicationIdsProvider applicationIdsProvider) {
|
||||
return new ApplicationRecentLogsProvider(cloudFoundryClient, dopplerClient, applicationIdsProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RecentLogsController recentLogsController(RecentLogsProvider recentLogsProvider) {
|
||||
return new RecentLogsController(recentLogsProvider);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
org.springframework.cloud.appbroker.autoconfigure.AppBrokerAutoConfiguration
|
||||
org.springframework.cloud.appbroker.autoconfigure.CloudFoundryAppDeployerAutoConfiguration
|
||||
org.springframework.cloud.appbroker.autoconfigure.CredHubAutoConfiguration
|
||||
org.springframework.cloud.appbroker.autoconfigure.ServiceInstanceRecentLogsAutoConfiguration
|
||||
org.springframework.cloud.appbroker.autoconfigure.ServiceInstanceLogStreamAutoConfiguration
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.ApplicationLogStreamPublisher;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.LogStreamPublisher;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.endpoint.StreamingLogWebSocketHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ServiceInstanceLogStreamAutoConfigurationTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(
|
||||
AppBrokerAutoConfiguration.class,
|
||||
CloudFoundryAppDeployerAutoConfiguration.class,
|
||||
ServiceInstanceLogStreamAutoConfiguration.class
|
||||
))
|
||||
.withPropertyValues(
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.api-host=https://api.example.local",
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.username=user",
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.password=secret"
|
||||
);
|
||||
|
||||
@Test
|
||||
void servicesAreNotCreatedWithoutLoggingOnClasspath() {
|
||||
contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ApplicationLogStreamPublisher.class))
|
||||
.withUserConfiguration(LoggingConfiguration.class)
|
||||
.run(context -> assertThat(context)
|
||||
.doesNotHaveBean(StreamingLogWebSocketHandler.class)
|
||||
.doesNotHaveBean(WebSocketHandlerAdapter.class)
|
||||
.doesNotHaveBean(HandlerMapping.class)
|
||||
.doesNotHaveBean(LogStreamPublisher.class)
|
||||
.doesNotHaveBean(ApplicationLogStreamPublisher.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void servicesAreNotCreatedWithoutRequiredBeansOnClasspath() {
|
||||
contextRunner
|
||||
.run(context -> assertThat(context)
|
||||
.doesNotHaveBean(StreamingLogWebSocketHandler.class)
|
||||
.doesNotHaveBean(WebSocketHandlerAdapter.class)
|
||||
.doesNotHaveBean(HandlerMapping.class)
|
||||
.doesNotHaveBean(LogStreamPublisher.class)
|
||||
.doesNotHaveBean(ApplicationLogStreamPublisher.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void servicesAreCreatedWithLoggingConfigured() {
|
||||
contextRunner
|
||||
.withUserConfiguration(LoggingConfiguration.class)
|
||||
.run(context -> assertThat(context)
|
||||
.hasSingleBean(StreamingLogWebSocketHandler.class)
|
||||
.hasSingleBean(WebSocketHandlerAdapter.class)
|
||||
.hasSingleBean(HandlerMapping.class)
|
||||
.hasSingleBean(LogStreamPublisher.class)
|
||||
.hasSingleBean(ApplicationLogStreamPublisher.class));
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
public static class LoggingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ApplicationIdsProvider applicationIdsProvider() {
|
||||
return serviceInstanceId -> Flux.just("app1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.ApplicationRecentLogsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.RecentLogsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.recent.endpoint.RecentLogsController;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ServiceInstanceRecentLogsAutoConfigurationTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(
|
||||
AppBrokerAutoConfiguration.class,
|
||||
CloudFoundryAppDeployerAutoConfiguration.class,
|
||||
ServiceInstanceRecentLogsAutoConfiguration.class
|
||||
))
|
||||
.withPropertyValues(
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.api-host=https://api.example.local",
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.username=user",
|
||||
"spring.cloud.appbroker.deployer.cloudfoundry.password=secret"
|
||||
);
|
||||
|
||||
@Test
|
||||
void servicesAreNotCreatedWithoutLoggingOnClasspath() {
|
||||
contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(ApplicationRecentLogsProvider.class))
|
||||
.withUserConfiguration(LoggingConfiguration.class)
|
||||
.run(context -> assertThat(context)
|
||||
.doesNotHaveBean(RecentLogsProvider.class)
|
||||
.doesNotHaveBean(RecentLogsController.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void servicesAreNotCreatedWithoutRequiredBeansOnClasspath() {
|
||||
contextRunner
|
||||
.run(context -> assertThat(context)
|
||||
.doesNotHaveBean(RecentLogsProvider.class)
|
||||
.doesNotHaveBean(RecentLogsController.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void servicesAreCreatedWithLoggingConfigured() {
|
||||
contextRunner
|
||||
.withUserConfiguration(LoggingConfiguration.class)
|
||||
.run(context -> assertThat(context)
|
||||
.hasSingleBean(RecentLogsProvider.class)
|
||||
.hasSingleBean(RecentLogsController.class));
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
public static class LoggingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ApplicationIdsProvider applicationIdsProvider() {
|
||||
return serviceInstanceId -> Flux.just("app1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.springframework.boot.gradle.plugin.SpringBootPlugin
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' apply false
|
||||
}
|
||||
|
||||
description = "Spring Cloud App Broker Logging"
|
||||
|
||||
dependencies {
|
||||
api platform(SpringBootPlugin.BOM_COORDINATES)
|
||||
api "org.springframework.boot:spring-boot-starter-webflux"
|
||||
api "org.cloudfoundry:cloudfoundry-client-reactor:${cfJavaClientVersion}"
|
||||
api "org.cloudfoundry:cloudfoundry-operations:${cfJavaClientVersion}"
|
||||
|
||||
testImplementation project(":spring-cloud-starter-app-broker-logging")
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testImplementation "org.awaitility:awaitility"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ApplicationIdsProvider {
|
||||
|
||||
Flux<String> getApplicationIds(String serviceInstanceId);
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging;
|
||||
|
||||
import okio.ByteString;
|
||||
import org.cloudfoundry.doppler.EventType;
|
||||
import org.cloudfoundry.doppler.MessageType;
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import org.cloudfoundry.dropsonde.events.LogMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class LoggingUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LoggingUtils.class);
|
||||
|
||||
private LoggingUtils() {
|
||||
}
|
||||
|
||||
public static Envelope convertDopplerEnvelopeToDropsonde(org.cloudfoundry.doppler.Envelope envelope) {
|
||||
final Envelope.Builder builder = new Envelope.Builder()
|
||||
.deployment(envelope.getDeployment())
|
||||
.eventType(toDropsondeEventType(envelope.getEventType()))
|
||||
.index(envelope.getIndex())
|
||||
.ip(envelope.getIp())
|
||||
.job(envelope.getJob())
|
||||
.origin(envelope.getOrigin())
|
||||
.tags(envelope.getTags())
|
||||
.timestamp(envelope.getTimestamp());
|
||||
|
||||
if (envelope.getEventType() == EventType.LOG_MESSAGE) {
|
||||
final org.cloudfoundry.doppler.LogMessage logMessage = envelope.getLogMessage();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Decoding message [" + logMessage.getTimestamp() + "]: " + logMessage.getMessage());
|
||||
}
|
||||
|
||||
builder.logMessage(new LogMessage.Builder()
|
||||
.app_id(logMessage.getApplicationId())
|
||||
.message(ByteString.encodeUtf8(logMessage.getMessage()))
|
||||
.message_type(toDropsondeMessageType(logMessage.getMessageType()))
|
||||
.source_instance(logMessage.getSourceInstance())
|
||||
.source_type(logMessage.getSourceType())
|
||||
.timestamp(logMessage.getTimestamp())
|
||||
.build());
|
||||
}
|
||||
else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Unable to decode message of type " + envelope.getEventType());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static org.cloudfoundry.doppler.Envelope injectAppNameIntoLogSourceInstance(String appName,
|
||||
org.cloudfoundry.doppler.Envelope envelope) {
|
||||
if (envelope.getEventType() != EventType.LOG_MESSAGE) {
|
||||
return envelope;
|
||||
}
|
||||
return org.cloudfoundry.doppler.Envelope.builder().from(envelope).logMessage(
|
||||
org.cloudfoundry.doppler.LogMessage.builder().from(envelope.getLogMessage())
|
||||
.sourceInstance(appName + " " + envelope.getLogMessage().getSourceInstance())
|
||||
.build()
|
||||
).build();
|
||||
}
|
||||
|
||||
|
||||
private static LogMessage.MessageType toDropsondeMessageType(MessageType messageType) {
|
||||
switch (messageType) {
|
||||
case ERR:
|
||||
return LogMessage.MessageType.ERR;
|
||||
case OUT:
|
||||
return LogMessage.MessageType.OUT;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown message type " + messageType);
|
||||
}
|
||||
}
|
||||
|
||||
private static Envelope.EventType toDropsondeEventType(EventType eventType) {
|
||||
switch (eventType) {
|
||||
case ERROR:
|
||||
return Envelope.EventType.Error;
|
||||
case CONTAINER_METRIC:
|
||||
return Envelope.EventType.ContainerMetric;
|
||||
case COUNTER_EVENT:
|
||||
return Envelope.EventType.CounterEvent;
|
||||
case HTTP_START_STOP:
|
||||
return Envelope.EventType.HttpStartStop;
|
||||
case LOG_MESSAGE:
|
||||
return Envelope.EventType.LogMessage;
|
||||
case VALUE_METRIC:
|
||||
return Envelope.EventType.ValueMetric;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown event type: " + eventType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent;
|
||||
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.doppler.RecentLogsRequest;
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.LoggingUtils;
|
||||
|
||||
public class ApplicationRecentLogsProvider implements RecentLogsProvider {
|
||||
|
||||
private final CloudFoundryClient client;
|
||||
|
||||
private final DopplerClient dopplerClient;
|
||||
|
||||
private final ApplicationIdsProvider applicationIdsProvider;
|
||||
|
||||
public ApplicationRecentLogsProvider(CloudFoundryClient client, DopplerClient dopplerClient,
|
||||
ApplicationIdsProvider applicationIdsProvider) {
|
||||
this.client = client;
|
||||
this.dopplerClient = dopplerClient;
|
||||
this.applicationIdsProvider = applicationIdsProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Envelope> getLogs(String serviceInstanceId) {
|
||||
return this.applicationIdsProvider.getApplicationIds(serviceInstanceId)
|
||||
.flatMap(this::recentLogs)
|
||||
.map(LoggingUtils::convertDopplerEnvelopeToDropsonde);
|
||||
}
|
||||
|
||||
protected Flux<org.cloudfoundry.doppler.Envelope> recentLogs(String applicationId) {
|
||||
return client.applicationsV2().get(GetApplicationRequest.builder().applicationId(applicationId).build())
|
||||
.map(response -> response.getEntity().getName())
|
||||
.flatMapMany(appName ->
|
||||
dopplerClient.recentLogs(RecentLogsRequest.builder().applicationId(applicationId).build())
|
||||
.map(envelope -> LoggingUtils.injectAppNameIntoLogSourceInstance(appName, envelope))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent;
|
||||
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface RecentLogsProvider {
|
||||
|
||||
Flux<Envelope> getLogs(String serviceInstanceId);
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent.endpoint;
|
||||
|
||||
class EncodingException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1837485200518028161L;
|
||||
|
||||
public EncodingException(Throwable throwable) {
|
||||
super("Failed to encode: " + throwable.getMessage(), throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent.endpoint;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
|
||||
class LogMessageComparator implements Comparator<Envelope> {
|
||||
|
||||
@Override
|
||||
public int compare(Envelope o1, Envelope o2) {
|
||||
return Long.compare(getTimestamp(o1), getTimestamp(o2));
|
||||
}
|
||||
|
||||
private long getTimestamp(Envelope e) {
|
||||
if (e.logMessage != null && e.logMessage.timestamp != null) {
|
||||
return e.logMessage.timestamp;
|
||||
}
|
||||
if (e.timestamp == null) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return e.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent.endpoint;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
class MultipartEncoder {
|
||||
|
||||
private final byte[] bytesCRLF = {'\r', '\n'};
|
||||
|
||||
private final byte[] bytesSEP = {'-', '-'};
|
||||
|
||||
private final byte[] boundary;
|
||||
|
||||
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
public MultipartEncoder(String boundary) {
|
||||
this.boundary = boundary.getBytes();
|
||||
}
|
||||
|
||||
public void append(byte[] part) {
|
||||
try {
|
||||
out.write(bytesCRLF);
|
||||
out.write(bytesSEP);
|
||||
out.write(boundary);
|
||||
out.write(bytesCRLF);
|
||||
out.write(bytesCRLF);
|
||||
out.write(part);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EncodingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] terminateAndGetBytes() {
|
||||
try {
|
||||
out.write(bytesCRLF);
|
||||
out.write(bytesSEP);
|
||||
out.write(boundary);
|
||||
out.write(bytesSEP);
|
||||
out.write(bytesCRLF);
|
||||
final byte[] bytes = out.toByteArray();
|
||||
out.close();
|
||||
return bytes;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EncodingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent.endpoint;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.appbroker.logging.recent.RecentLogsProvider;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class RecentLogsController {
|
||||
|
||||
private static final LogMessageComparator LOG_MESSAGE_COMPARATOR = new LogMessageComparator();
|
||||
|
||||
private final RecentLogsProvider recentLogsProviders;
|
||||
|
||||
public RecentLogsController(@Autowired(required = false) RecentLogsProvider recentLogsProviders) {
|
||||
this.recentLogsProviders = recentLogsProviders;
|
||||
}
|
||||
|
||||
@RequestMapping("/logs/{serviceInstanceId}/recentlogs")
|
||||
public Mono<ResponseEntity<byte[]>> recentLogs(@PathVariable("serviceInstanceId") String serviceInstanceId) {
|
||||
final String multipartBoundary = UUID.randomUUID().toString();
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_TYPE, "multipart/mixed; boundary=" + multipartBoundary);
|
||||
|
||||
return recentLogsProviders.getLogs(serviceInstanceId)
|
||||
.collectList()
|
||||
.doOnNext(envelopes -> envelopes.sort(LOG_MESSAGE_COMPARATOR))
|
||||
.map(envelopes -> {
|
||||
final MultipartEncoder multipart = new MultipartEncoder(multipartBoundary);
|
||||
for (Envelope message : envelopes) {
|
||||
multipart.append(Envelope.ADAPTER.encode(message));
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(multipart.terminateAndGetBytes(), headers, HttpStatus.OK);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.cloudfoundry.doppler.Envelope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.appbroker.logging.LoggingUtils;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.ServiceInstanceLogEvent;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.ServiceInstanceLoggingEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
public class ApplicationLogStreamPublisher implements ApplicationListener<ServiceInstanceLoggingEvent> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ApplicationLogStreamPublisher.class);
|
||||
|
||||
private final Map<String, Registration> registry = new HashMap<>();
|
||||
|
||||
private final LogStreamPublisher<Envelope> logStreamPublisher;
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
|
||||
public ApplicationLogStreamPublisher(LogStreamPublisher<Envelope> logStreamPublisher,
|
||||
ApplicationEventPublisher publisher) {
|
||||
this.logStreamPublisher = logStreamPublisher;
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ServiceInstanceLoggingEvent event) {
|
||||
final String serviceInstanceId = event.getServiceInstanceId();
|
||||
switch (event.getOperation()) {
|
||||
case START:
|
||||
LOG.debug("Received event to begin listening to logs for {}", serviceInstanceId);
|
||||
this.startPublishing(serviceInstanceId);
|
||||
return;
|
||||
case STOP:
|
||||
LOG.debug("Received event to stop listening to logs for {}", serviceInstanceId);
|
||||
this.stopPublishing(serviceInstanceId);
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown operation: " + event.getOperation());
|
||||
}
|
||||
}
|
||||
|
||||
private void startPublishing(String serviceInstanceId) {
|
||||
synchronized (registry) {
|
||||
final Registration registration = registry.get(serviceInstanceId);
|
||||
if (registration != null) {
|
||||
LOG.debug("Incrementing registration subscription count for {}", serviceInstanceId);
|
||||
registration.increment();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Flux<Envelope> logStream = this.logStreamPublisher
|
||||
.getLogStream(serviceInstanceId);
|
||||
|
||||
final Disposable subscription = logStream
|
||||
.map(LoggingUtils::convertDopplerEnvelopeToDropsonde)
|
||||
.doOnNext(
|
||||
envelope -> publisher.publishEvent(new ServiceInstanceLogEvent(this, serviceInstanceId, envelope)))
|
||||
.subscribe();
|
||||
|
||||
LOG.debug("Creating new registration for {}", serviceInstanceId);
|
||||
registry.put(serviceInstanceId, new Registration(subscription));
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPublishing(String serviceInstanceId) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Received event to stop listening to logs for {}", serviceInstanceId);
|
||||
}
|
||||
|
||||
synchronized (registry) {
|
||||
final Registration registration = registry.get(serviceInstanceId);
|
||||
if (registration == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Received deregister event for service instance {} but there no event handler registered",
|
||||
serviceInstanceId);
|
||||
}
|
||||
}
|
||||
else if (registration.decrement() == 0) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Disposing of registration since there are no more subscriptions");
|
||||
}
|
||||
|
||||
registration.getSubscription().dispose();
|
||||
registry.remove(serviceInstanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class Registration {
|
||||
|
||||
private final Disposable subscription;
|
||||
|
||||
private int count = 1;
|
||||
|
||||
private Registration(Disposable subscription) {
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incrementing subscription count from {} to {}", count, count + 1);
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
public int decrement() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Decrementing subscription count from {} to {}", count, count - 1);
|
||||
}
|
||||
|
||||
return --count;
|
||||
}
|
||||
|
||||
public Disposable getSubscription() {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming;
|
||||
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.doppler.Envelope;
|
||||
import org.cloudfoundry.doppler.StreamRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.LoggingUtils;
|
||||
|
||||
public class DopplerLogStreamPublisher implements LogStreamPublisher<Envelope> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DopplerLogStreamPublisher.class);
|
||||
|
||||
private final CloudFoundryClient client;
|
||||
|
||||
private final DopplerClient dopplerClient;
|
||||
|
||||
private final ApplicationIdsProvider applicationIdsProvider;
|
||||
|
||||
public DopplerLogStreamPublisher(
|
||||
CloudFoundryClient client,
|
||||
DopplerClient dopplerClient,
|
||||
ApplicationIdsProvider applicationIdsProvider
|
||||
) {
|
||||
this.client = client;
|
||||
this.dopplerClient = dopplerClient;
|
||||
this.applicationIdsProvider = applicationIdsProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Envelope> getLogStream(String serviceInstanceId) {
|
||||
return this.applicationIdsProvider
|
||||
.getApplicationIds(serviceInstanceId)
|
||||
.doOnNext(id -> LOG.debug("Starting log streaming for app with ID {}", id))
|
||||
.flatMap(this::createApplicationStreamer);
|
||||
}
|
||||
|
||||
protected Flux<Envelope> createApplicationStreamer(String applicationId) {
|
||||
return client.applicationsV2().get(GetApplicationRequest.builder().applicationId(applicationId).build())
|
||||
.map(response -> response.getEntity().getName())
|
||||
.flatMapMany(appName ->
|
||||
dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())
|
||||
.map(envelope -> LoggingUtils.injectAppNameIntoLogSourceInstance(appName, envelope))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface LogStreamPublisher<T> {
|
||||
|
||||
Flux<T> getLogStream(String serviceInstanceId);
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.endpoint;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
class ServiceInstanceNotFoundException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8672053621926030384L;
|
||||
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.endpoint;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.ServiceInstanceLogEvent;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.StartServiceInstanceLoggingEvent;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.StopServiceInstanceLoggingEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.WebSocketSession;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
public class StreamingLogWebSocketHandler implements WebSocketHandler, ApplicationListener<ServiceInstanceLogEvent> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StreamingLogWebSocketHandler.class);
|
||||
|
||||
private static final UriTemplate LOGGING_URI_TEMPLATE = new UriTemplate("/logs/{serviceInstanceId}/stream");
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final ConcurrentHashMap<String, Sinks.One<Envelope>> envelopeSinks = new ConcurrentHashMap<>();
|
||||
|
||||
public StreamingLogWebSocketHandler(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(WebSocketSession session) {
|
||||
String serviceInstanceId = getServiceInstanceId(session);
|
||||
LOG.info("Connection established [{}}], service instance {}",
|
||||
session.getHandshakeInfo().getRemoteAddress(),
|
||||
serviceInstanceId);
|
||||
|
||||
Sinks.One<Envelope> envelopeSink = envelopeSinks
|
||||
.computeIfAbsent(serviceInstanceId, s -> Sinks.one());
|
||||
|
||||
eventPublisher.publishEvent(new StartServiceInstanceLoggingEvent(this, serviceInstanceId));
|
||||
LOG.info("Published event to start streaming logs for service instance with ID {}", serviceInstanceId);
|
||||
|
||||
return session
|
||||
.send(envelopeSink.asMono().map(envelope -> session
|
||||
.binaryMessage(dataBufferFactory -> dataBufferFactory.wrap(Envelope.ADAPTER.encode(envelope)))))
|
||||
.then()
|
||||
.doFinally(signalType -> afterConnectionClosed(session))
|
||||
.doOnError(throwable -> LOG
|
||||
.error("Error handling logging stream for service instance " + serviceInstanceId, throwable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ServiceInstanceLogEvent event) {
|
||||
broadcastLogMessage(event);
|
||||
}
|
||||
|
||||
public void broadcastLogMessage(ServiceInstanceLogEvent event) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Received event to broadcast log message for " + event.getServiceInstanceId());
|
||||
}
|
||||
|
||||
Sinks.One<Envelope> envelopeSink = this.envelopeSinks.get(event.getServiceInstanceId());
|
||||
if (envelopeSink == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("No sink found for {}, stopping log streaming", event.getServiceInstanceId());
|
||||
}
|
||||
|
||||
eventPublisher.publishEvent(new StopServiceInstanceLoggingEvent(this, event.getServiceInstanceId()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Sending message to client for {}", event.getServiceInstanceId());
|
||||
}
|
||||
|
||||
envelopeSink.tryEmitValue(event.getEnvelope());
|
||||
}
|
||||
|
||||
private void afterConnectionClosed(WebSocketSession webSocketSession) {
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info("Connection closed [" + webSocketSession.getHandshakeInfo().getRemoteAddress() + "]");
|
||||
}
|
||||
|
||||
final String serviceInstanceId = getServiceInstanceId(webSocketSession);
|
||||
eventPublisher.publishEvent(new StopServiceInstanceLoggingEvent(this, serviceInstanceId));
|
||||
envelopeSinks.computeIfPresent(serviceInstanceId, (s, envelopeSink) -> null);
|
||||
}
|
||||
|
||||
private String getServiceInstanceId(WebSocketSession webSocketSession) {
|
||||
URI uri = webSocketSession.getHandshakeInfo().getUri();
|
||||
final Map<String, String> match = LOGGING_URI_TEMPLATE.match(uri.getPath());
|
||||
if (match.isEmpty()) {
|
||||
throw new ServiceInstanceNotFoundException();
|
||||
}
|
||||
|
||||
return match.get("serviceInstanceId");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.events;
|
||||
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class ServiceInstanceLogEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = 4391048666734944603L;
|
||||
|
||||
private final String serviceInstanceId;
|
||||
|
||||
private final Envelope envelope;
|
||||
|
||||
public ServiceInstanceLogEvent(Object source, String serviceInstanceId, Envelope envelope) {
|
||||
super(source);
|
||||
this.serviceInstanceId = serviceInstanceId;
|
||||
this.envelope = envelope;
|
||||
}
|
||||
|
||||
public String getServiceInstanceId() {
|
||||
return serviceInstanceId;
|
||||
}
|
||||
|
||||
public Envelope getEnvelope() {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.events;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class ServiceInstanceLoggingEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = 3721553379568462887L;
|
||||
|
||||
public enum Operation {
|
||||
|
||||
/**
|
||||
* Start publishing log stream for a given service instance id
|
||||
*/
|
||||
START,
|
||||
|
||||
/**
|
||||
* Stop publishing log stream for a given service instance id
|
||||
*/
|
||||
STOP
|
||||
}
|
||||
|
||||
private final String serviceInstanceId;
|
||||
|
||||
private final Operation operation;
|
||||
|
||||
public ServiceInstanceLoggingEvent(Object source, String serviceInstanceId, Operation operation) {
|
||||
super(source);
|
||||
this.serviceInstanceId = serviceInstanceId;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public String getServiceInstanceId() {
|
||||
return serviceInstanceId;
|
||||
}
|
||||
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.events;
|
||||
|
||||
public class StartServiceInstanceLoggingEvent extends ServiceInstanceLoggingEvent {
|
||||
|
||||
private static final long serialVersionUID = -5940715663862240039L;
|
||||
|
||||
public StartServiceInstanceLoggingEvent(Object source, String serviceInstanceId) {
|
||||
super(source, serviceInstanceId, Operation.START);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming.events;
|
||||
|
||||
public class StopServiceInstanceLoggingEvent extends ServiceInstanceLoggingEvent {
|
||||
|
||||
private static final long serialVersionUID = 2858399700112202361L;
|
||||
|
||||
public StopServiceInstanceLoggingEvent(Object source, String serviceInstanceId) {
|
||||
super(source, serviceInstanceId, Operation.STOP);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.recentlog;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.servicebroker.autoconfigure.web.ServiceBrokerAutoConfiguration;
|
||||
import org.springframework.cloud.servicebroker.autoconfigure.web.reactive.ServiceBrokerWebFluxAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
ServiceBrokerAutoConfiguration.class,
|
||||
ServiceBrokerWebFluxAutoConfiguration.class
|
||||
})
|
||||
public class RecentLogsTestApp {
|
||||
|
||||
static final String APP_ID = UUID.randomUUID().toString();
|
||||
|
||||
public static String getAppId() {
|
||||
return APP_ID;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationIdsProvider applicationIdsProvider() {
|
||||
return serviceInstanceId -> Flux.just(APP_ID);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.streaming;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.StopServiceInstanceLoggingEvent;
|
||||
import org.springframework.cloud.servicebroker.autoconfigure.web.ServiceBrokerAutoConfiguration;
|
||||
import org.springframework.cloud.servicebroker.autoconfigure.web.reactive.ServiceBrokerWebFluxAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
ServiceBrokerAutoConfiguration.class,
|
||||
ServiceBrokerWebFluxAutoConfiguration.class
|
||||
})
|
||||
public class LogStreamingTestApp {
|
||||
|
||||
private static final String APP_ID = UUID.randomUUID().toString();
|
||||
|
||||
private static boolean receivedStopEvent;
|
||||
private static String receivedStopEventServiceInstanceId;
|
||||
|
||||
public static String getAppId() {
|
||||
return APP_ID;
|
||||
}
|
||||
|
||||
public static boolean isReceivedStopEvent() {
|
||||
return receivedStopEvent;
|
||||
}
|
||||
|
||||
public static String getReceivedStopEventServiceInstanceId() {
|
||||
return receivedStopEventServiceInstanceId;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationIdsProvider applicationIdsProvider() {
|
||||
return serviceInstanceId -> Flux.just(APP_ID);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onStop(StopServiceInstanceLoggingEvent stopServiceInstanceLoggingEvent) {
|
||||
receivedStopEventServiceInstanceId = stopServiceInstanceLoggingEvent.getServiceInstanceId();
|
||||
receivedStopEvent = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.recent;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.example.recentlog.RecentLogsTestApp;
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.v2.applications.ApplicationEntity;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationResponse;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.doppler.Envelope;
|
||||
import org.cloudfoundry.doppler.EventType;
|
||||
import org.cloudfoundry.doppler.LogMessage;
|
||||
import org.cloudfoundry.doppler.MessageType;
|
||||
import org.cloudfoundry.doppler.RecentLogsRequest;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Answers;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RecentLogsTestApp.class)
|
||||
class ServiceInstanceRecentLogsTest {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@MockBean
|
||||
DopplerClient dopplerClient;
|
||||
|
||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
CloudFoundryClient cloudFoundryClient;
|
||||
|
||||
private String expectedTestMessage;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
expectedTestMessage = "test message " + UUID.randomUUID();
|
||||
|
||||
RecentLogsRequest request = RecentLogsRequest.builder().applicationId(RecentLogsTestApp.getAppId()).build();
|
||||
LogMessage testMessage = LogMessage
|
||||
.builder()
|
||||
.message(expectedTestMessage)
|
||||
.timestamp(Instant.now().toEpochMilli())
|
||||
.messageType(MessageType.OUT).build();
|
||||
|
||||
Envelope testEnvelope = Envelope
|
||||
.builder()
|
||||
.eventType(EventType.LOG_MESSAGE).origin("test")
|
||||
.logMessage(testMessage).build();
|
||||
|
||||
given(dopplerClient.recentLogs(request))
|
||||
.willReturn(Flux.just(testEnvelope));
|
||||
|
||||
given(cloudFoundryClient.applicationsV2()
|
||||
.get(GetApplicationRequest.builder().applicationId(RecentLogsTestApp.getAppId()).build()))
|
||||
.willReturn(Mono.just(
|
||||
GetApplicationResponse.builder().entity(ApplicationEntity.builder().name("test-app").build()).build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchLogs() {
|
||||
String serviceInstanceId = UUID.randomUUID().toString();
|
||||
WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
|
||||
|
||||
client.get().uri("/logs/{serviceInstanceId}/recentlogs", serviceInstanceId)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class)
|
||||
.value(Matchers.containsString(expectedTestMessage));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.appbroker.logging.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.example.streaming.LogStreamingTestApp;
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.v2.applications.ApplicationEntity;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.client.v2.applications.GetApplicationResponse;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.doppler.EventType;
|
||||
import org.cloudfoundry.doppler.MessageType;
|
||||
import org.cloudfoundry.doppler.StreamRequest;
|
||||
import org.cloudfoundry.dropsonde.events.Envelope;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Answers;
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.appbroker.logging.LoggingUtils;
|
||||
import org.springframework.cloud.appbroker.logging.streaming.events.ServiceInstanceLogEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||
import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LogStreamingTestApp.class)
|
||||
class ServiceInstanceLogStreamingTest {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@MockBean
|
||||
DopplerClient dopplerClient;
|
||||
|
||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
CloudFoundryClient cloudFoundryClient;
|
||||
|
||||
@Autowired
|
||||
ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private Envelope expectedEnvelope;
|
||||
|
||||
private final AtomicReference<Envelope> actualEnvelope = new AtomicReference<>();
|
||||
|
||||
private String serviceInstanceId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
serviceInstanceId = UUID.randomUUID().toString();
|
||||
String expectedTestMessage = "test message " + serviceInstanceId;
|
||||
|
||||
org.cloudfoundry.doppler.LogMessage testMessage = org.cloudfoundry.doppler.LogMessage
|
||||
.builder()
|
||||
.message(expectedTestMessage)
|
||||
.timestamp(Instant.now().toEpochMilli())
|
||||
.messageType(MessageType.OUT).build();
|
||||
|
||||
org.cloudfoundry.doppler.Envelope testEnvelope = org.cloudfoundry.doppler.Envelope
|
||||
.builder()
|
||||
.eventType(EventType.LOG_MESSAGE).origin("test")
|
||||
.logMessage(testMessage).build();
|
||||
|
||||
// expectations are instances of Dropsnode Envelope + LogMessage
|
||||
Envelope envelope = LoggingUtils.convertDopplerEnvelopeToDropsonde(testEnvelope);
|
||||
org.cloudfoundry.dropsonde.events.LogMessage expectedMessage = new org.cloudfoundry.dropsonde.events.LogMessage.Builder()
|
||||
.message_type(envelope.logMessage.message_type)
|
||||
.source_instance("test-app null")
|
||||
.timestamp(envelope.logMessage.timestamp)
|
||||
.message(envelope.logMessage.message)
|
||||
.build();
|
||||
|
||||
expectedEnvelope = new Envelope.Builder()
|
||||
.eventType(envelope.eventType)
|
||||
.logMessage(expectedMessage)
|
||||
.origin("test")
|
||||
.build();
|
||||
|
||||
StreamRequest request = StreamRequest.builder().applicationId(LogStreamingTestApp.getAppId()).build();
|
||||
given(dopplerClient.stream(request)).willReturn(Flux.just(testEnvelope));
|
||||
given(cloudFoundryClient.applicationsV2().get(
|
||||
GetApplicationRequest.builder().applicationId(LogStreamingTestApp.getAppId()).build()))
|
||||
.willReturn(Mono.just(
|
||||
GetApplicationResponse.builder().entity(ApplicationEntity.builder().name("test-app").build()).build()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPublishWebSocketEndpoint() {
|
||||
Disposable subscription = connectToLogsStreamEndpoint();
|
||||
|
||||
await().untilAsserted(() -> assertThat(actualEnvelope).hasValue(expectedEnvelope));
|
||||
|
||||
subscription.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPublishEventOnDisconnect() {
|
||||
Disposable subscription = connectToLogsStreamEndpoint();
|
||||
|
||||
await().untilAsserted(() -> assertThat(actualEnvelope.get()).isNotNull());
|
||||
|
||||
subscription.dispose();
|
||||
|
||||
await().untilAsserted(() -> assertThat(LogStreamingTestApp.isReceivedStopEvent()).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStopStreamingIfNoClient() {
|
||||
// CLI plugin doesn't always handle disconnect gracefully, so sometimes it is possible that log stream is
|
||||
// being published but there is no listener. In this case log streaming should be stopped.
|
||||
|
||||
Disposable subscription = connectToLogsStreamEndpoint();
|
||||
subscription.dispose();
|
||||
|
||||
applicationEventPublisher.publishEvent(new ServiceInstanceLogEvent(this, serviceInstanceId, expectedEnvelope));
|
||||
await().untilAsserted(() -> assertThat(LogStreamingTestApp.getReceivedStopEventServiceInstanceId()).isEqualTo(serviceInstanceId));
|
||||
}
|
||||
|
||||
private Disposable connectToLogsStreamEndpoint() {
|
||||
URI uri = URI.create("ws://localhost:" + port + "/logs/" + serviceInstanceId + "/stream");
|
||||
|
||||
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||
return client.execute(uri, getWebSocketHandler()).subscribe();
|
||||
}
|
||||
|
||||
private WebSocketHandler getWebSocketHandler() {
|
||||
return session -> session
|
||||
.receive()
|
||||
.doOnNext(message -> {
|
||||
DataBuffer buffer = message.getPayload();
|
||||
try {
|
||||
actualEnvelope.set(Envelope.ADAPTER.decode(
|
||||
buffer.asInputStream()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
description = "Spring Cloud App Broker Starter for Logging"
|
||||
|
||||
dependencies {
|
||||
api project(":spring-cloud-starter-app-broker")
|
||||
api project(":spring-cloud-app-broker-logging")
|
||||
}
|
||||
Reference in New Issue
Block a user