Implement custom trace repository

- Upgrade to boot 2.0.0.RC1
- Copy original boot's tracing features to get used
  as a base impl for statemachine.
- Fix samples, tests.
- Fixes #491
This commit is contained in:
jvalkeal
2018-02-11 09:24:13 +02:00
parent c693e79f12
commit 328a2e9161
12 changed files with 378 additions and 80 deletions

View File

@@ -1,7 +1,7 @@
buildscript {
ext {
log4jVersion = '1.2.17'
springBootVersion = '2.0.0.M7'
springBootVersion = '2.0.0.RC1'
eclipsePersistenceVersion = '2.1.1'
kryoVersion = '3.0.3'
springCloudClusterVersion = '1.0.2.RELEASE'
@@ -195,6 +195,7 @@ project('spring-statemachine-boot') {
dependencies {
compile project(":spring-statemachine-core")
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.springframework.boot:spring-boot-actuator-autoconfigure"
compile "org.springframework.boot:spring-boot-actuator"
optional project(":spring-statemachine-data-common:spring-statemachine-data-jpa")
optional project(":spring-statemachine-data-common:spring-statemachine-data-redis")

View File

@@ -1605,21 +1605,57 @@ Metrics can be viewed from Boot.
[source,json]
----
# curl http://localhost:8080/application/metrics
# curl http://localhost:8080/actuator/metrics/ssm.transition.duration
{
"gauge.ssm.transition.INITIAL_S1.duration":0.0,
"gauge.ssm.transition.EXTERNAL_S2_S3.duration":0.0,
"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0.duration":0.0,
"gauge.ssm.transition.EXTERNAL_S1_S2.duration":1.0,
"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333.duration":0.0,
"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9.duration":0.0,
"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0.execute":1,
"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9.execute":1,
"counter.ssm.transition.EXTERNAL_S2_S3.transit":1,
"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333.execute":1,
"counter.ssm.transition.EXTERNAL_S1_S2.transit":1,
"counter.ssm.transition.INITIAL_S1.transit":2,
"name":"ssm.transition.duration",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
},
{
"statistic":"TOTAL_TIME",
"value":0.007
},
{
"statistic":"MAX",
"value":0.004
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"INITIAL_S1",
"EXTERNAL_S1_S2"
]
}
]
}
----
[source,json]
----
# curl http://localhost:8080/actuator/metrics/ssm.transition.transit
{
"name":"ssm.transition.transit",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"EXTERNAL_S1_S2",
"INITIAL_S1"
]
}
]
}
----
@@ -1627,49 +1663,40 @@ Tracing can be viewed from Boot.
[source,json]
----
# curl http://localhost:8080/application/trace
# curl http://localhost:8080/actuator/statemachinetrace
[{
"timestamp":1478419121956,
"info":{
"duration":0,
"machine":null,
"transition":
"EXTERNAL_S2_S3"
[
{
"timestamp":"2018-02-11T06:44:12.723+0000",
"info":{
"duration":2,
"machine":null,
"transition":"EXTERNAL_S1_S2"
}
},
{
"timestamp":"2018-02-11T06:44:12.720+0000",
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f"
}
},
{
"timestamp":"2018-02-11T06:44:12.714+0000",
"info":{
"duration":1,
"machine":null,
"transition":"INITIAL_S1"
}
},
{
"timestamp":"2018-02-11T06:44:09.689+0000",
"info":{
"duration":4,
"machine":null,
"transition":"INITIAL_S1"
}
}
},
{
"timestamp":1478419121956,
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9"
}
},
{
"timestamp":1478419121956,
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0"
}
},
{
"timestamp":1478419121956,
"info":{
"duration":1,
"machine":null,
"transition":"EXTERNAL_S1_S2"
}
},
{
"timestamp":1478419121955,
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333"
}
}
]
----

View File

@@ -1926,13 +1926,15 @@ integration logic with _Spring Boot_ providing functionality i.e. for
auto-config and actuators. All what is needed is to have _State
Machine_ as part of a boot application together with this library.
[[sm-boot-monitoring]]
=== Monitoring and Tracing
`BootStateMachineMonitor` is created automatically and associated with
a state machine. This `BootStateMachineMonitor` will hook into Boot's
`CounterService`, `GaugeService` and `TraceRepository` if available.
Optionally this auto-configuration can be disabled by setting key
`spring.statemachine.monitor.enabled` to `false`. Use of this
auto-config is shown in sample <<statemachine-examples-monitoring>>.
a state machine. `BootStateMachineMonitor` is a custom `StateMachineMonitor`
implementation which integrates with boot's `MeterRegistry` and endpoints
via a custom `StateMachineTraceRepository`. Optionally this auto-configuration
can be disabled by setting key `spring.statemachine.monitor.enabled` to
`false`. Use of this auto-config is shown in sample
<<statemachine-examples-monitoring>>.
=== Repository Config
Spring Data Repositories and Entity class scanning is auto-configured

View File

@@ -47,3 +47,11 @@ _Spring Statemachine 1.3_.
* Transition conflict policy mentioned in
<<statemachine-config-commonsettings>>
== In 2.0
_Spring Statemachine 2.0_ is focusing on _Spring Boot 2.x_ support.
=== In 2.0.0
* Format of monitoring and trancing has beenn changed <<sm-boot-monitoring>>

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2018 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
*
* http://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.statemachine.boot.actuate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* In-memory implementation of {@link StateMachineTraceRepository}.
*
* @author Janne Valkealahti
*
*/
public class InMemoryStateMachineTraceRepository implements StateMachineTraceRepository {
private int capacity = 100;
private boolean reverse = true;
private final List<StateMachineTrace> traces = new LinkedList<StateMachineTrace>();
/**
* Flag to say that the repository lists traces in reverse order.
* @param reverse flag value (default true)
*/
public void setReverse(boolean reverse) {
synchronized (this.traces) {
this.reverse = reverse;
}
}
/**
* Set the capacity of the in-memory repository.
* @param capacity the capacity
*/
public void setCapacity(int capacity) {
synchronized (this.traces) {
this.capacity = capacity;
}
}
@Override
public List<StateMachineTrace> findAll() {
synchronized (this.traces) {
return Collections.unmodifiableList(new ArrayList<StateMachineTrace>(this.traces));
}
}
@Override
public void add(Map<String, Object> map) {
StateMachineTrace trace = new StateMachineTrace(new Date(), map);
synchronized (this.traces) {
while (this.traces.size() >= this.capacity) {
this.traces.remove(this.reverse ? this.capacity - 1 : 0);
}
if (this.reverse) {
this.traces.add(0, trace);
}
else {
this.traces.add(trace);
}
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018 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
*
* http://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.statemachine.boot.actuate;
import java.util.Date;
import java.util.Map;
import org.springframework.util.Assert;
/**
* A value object representing a statemachine trace event: at a particular time
* with a simple (map) information.
*
* @author Janne Valkealahti
*
*/
public final class StateMachineTrace {
private final Date timestamp;
private final Map<String, Object> info;
/**
* Instantiate a new {@code StateMachineTrace}.
*
* @param timestamp the timestamp
* @param info the trace info
*/
public StateMachineTrace(Date timestamp, Map<String, Object> info) {
super();
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(info, "Info must not be null");
this.timestamp = timestamp;
this.info = info;
}
/**
* Gets a timestamp
*
* @return a trace timestamp
*/
public Date getTimestamp() {
return this.timestamp;
}
/**
* Gets a trace info.
*
* @return a trace info
*/
public Map<String, Object> getInfo() {
return this.info;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2018 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
*
* http://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.statemachine.boot.actuate;
import java.util.List;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.util.Assert;
/**
* {@code Endpoint} to expose {@link StateMachineTrace} information.
*
* @author Janne Valkealahti
*
*/
@Endpoint(id = "statemachinetrace")
public class StateMachineTraceEndpoint {
private final StateMachineTraceRepository repository;
/**
* Create a new {@link StateMachineTraceEndpoint} instance.
*
* @param repository the trace repository
*/
public StateMachineTraceEndpoint(StateMachineTraceRepository repository) {
Assert.notNull(repository, "Repository must not be null");
this.repository = repository;
}
@ReadOperation
public List<StateMachineTrace> invoke() {
return this.repository.findAll();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2018 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
*
* http://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.statemachine.boot.actuate;
import java.util.List;
import java.util.Map;
/**
* A repository for {@link StateMachineTrace}s.
*
* @author Janne Valkealahti
*
*/
public interface StateMachineTraceRepository {
/**
* Find all {@link StateMachineTrace} objects contained in the repository.
*
* @return the results
*/
List<StateMachineTrace> findAll();
/**
* Add a new {@link StateMachineTrace} object at the current time.
*
* @param traceInfo trace information
*/
void add(Map<String, Object> traceInfo);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-2018 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.
@@ -16,14 +16,18 @@
package org.springframework.statemachine.boot.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.boot.StateMachineProperties;
import org.springframework.statemachine.boot.actuate.InMemoryStateMachineTraceRepository;
import org.springframework.statemachine.boot.actuate.StateMachineTraceEndpoint;
import org.springframework.statemachine.boot.actuate.StateMachineTraceRepository;
import org.springframework.statemachine.boot.support.BootStateMachineMonitor;
import io.micrometer.core.instrument.MeterRegistry;
@@ -36,25 +40,44 @@ import io.micrometer.core.instrument.MeterRegistry;
*/
@Configuration
@EnableConfigurationProperties({ StateMachineProperties.class })
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnProperty(prefix = "spring.statemachine.monitor", name = "enabled", havingValue = "true", matchIfMissing = true)
public class StateMachineAutoConfiguration {
@ManagementContextConfiguration
public static class StateMachineTraceEndpointConfiguration {
@Bean
public StateMachineTraceEndpoint stateMachieTraceEndpoint(StateMachineTraceRepository stateMachineTraceRepository) {
return new StateMachineTraceEndpoint(stateMachineTraceRepository);
}
}
@Configuration
public static class StateMachineTraceRepositoryConfiguration {
@ConditionalOnMissingBean(StateMachineTraceRepository.class)
@Bean
public InMemoryStateMachineTraceRepository stateMachineTraceRepository() {
return new InMemoryStateMachineTraceRepository();
}
}
@Configuration
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnProperty(prefix = "spring.statemachine.monitor", name = "enabled", havingValue = "true", matchIfMissing = true)
public static class StateMachineMonitoringConfiguration {
private final MeterRegistry meterRegistry;
private final TraceRepository traceRepository;
private final StateMachineTraceRepository stateMachineTraceRepository;
public StateMachineMonitoringConfiguration(ObjectProvider<MeterRegistry> meterRegistryProvider,
ObjectProvider<TraceRepository> traceRepositoryProvider) {
ObjectProvider<StateMachineTraceRepository> traceRepositoryProvider) {
this.meterRegistry = meterRegistryProvider.getIfAvailable();
this.traceRepository = traceRepositoryProvider.getIfAvailable();
this.stateMachineTraceRepository = traceRepositoryProvider.getIfAvailable();
}
@Bean
public BootStateMachineMonitor<?, ?> bootStateMachineMonitor() {
return new BootStateMachineMonitor<>(meterRegistry, traceRepository);
return new BootStateMachineMonitor<>(meterRegistry, stateMachineTraceRepository);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-2018 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.
@@ -19,9 +19,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.boot.actuate.StateMachineTraceRepository;
import org.springframework.statemachine.monitor.AbstractStateMachineMonitor;
import org.springframework.statemachine.monitor.StateMachineMonitor;
import org.springframework.statemachine.state.State;
@@ -44,18 +44,19 @@ import io.micrometer.core.instrument.Timer;
*/
public class BootStateMachineMonitor<S, E> extends AbstractStateMachineMonitor<S, E> {
private final TraceRepository traceRepository;
private final StateMachineTraceRepository traceRepository;
private final MeterRegistry meterRegistry;
/**
* Instantiates a new boot state machine monitor.
*
* @param meterRegistry the meter registry
* @param traceRepository the trace repository
* @param stateMachineTraceRepository the statemachine trace repository
*/
public BootStateMachineMonitor(MeterRegistry meterRegistry, TraceRepository traceRepository) {
public BootStateMachineMonitor(MeterRegistry meterRegistry,
StateMachineTraceRepository stateMachineTraceRepository) {
this.meterRegistry = meterRegistry;
this.traceRepository = traceRepository;
this.traceRepository = stateMachineTraceRepository;
}
@Override

View File

@@ -4,7 +4,7 @@ logging:
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
endpoints:
default:
web:
@@ -12,4 +12,4 @@ endpoints:
management:
endpoints:
web:
expose: metrics,trace
expose: "*"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-2018 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.
@@ -99,8 +99,8 @@ public class MonitoringTests {
perform(get("/state")).
andExpect(status().isOk());
mvc.
perform(get("/actuator/trace")).
andExpect(jsonPath("$.traces.*.info.transition", containsInAnyOrder("INITIAL_S1")));
perform(get("/actuator/statemachinetrace")).
andExpect(jsonPath("$.*.info.transition", containsInAnyOrder("INITIAL_S1")));
}
@Before