Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
4bc32e63
Commit
4bc32e63
authored
Nov 23, 2018
by
Andy Wilkinson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use a HandlerInterceptor for timing long tasks
Closes gh-15204
parent
958c3861
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
433 additions
and
137 deletions
+433
-137
WebMvcMetricsAutoConfiguration.java
...e/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java
+32
-5
WebMvcMetricsAutoConfigurationTests.java
...rics/web/servlet/WebMvcMetricsAutoConfigurationTests.java
+18
-0
LongTaskTimingHandlerInterceptor.java
...metrics/web/servlet/LongTaskTimingHandlerInterceptor.java
+156
-0
WebMvcMetricsFilter.java
...boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java
+34
-116
LongTaskTimingHandlerInterceptorTests.java
...cs/web/servlet/LongTaskTimingHandlerInterceptorTests.java
+192
-0
WebMvcMetricsFilterTests.java
...actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java
+1
-16
No files found.
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java
View file @
4bc32e63
...
@@ -27,6 +27,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.
...
@@ -27,6 +27,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.
import
org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter
;
import
org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter
;
import
org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration
;
import
org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration
;
import
org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.LongTaskTimingHandlerInterceptor
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider
;
import
org.springframework.boot.autoconfigure.AutoConfigureAfter
;
import
org.springframework.boot.autoconfigure.AutoConfigureAfter
;
...
@@ -41,8 +42,9 @@ import org.springframework.context.annotation.Bean;
...
@@ -41,8 +42,9 @@ import org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.web.context.WebApplicationContext
;
import
org.springframework.web.servlet.DispatcherServlet
;
import
org.springframework.web.servlet.DispatcherServlet
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
;
/**
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring Web
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring Web
...
@@ -75,11 +77,10 @@ public class WebMvcMetricsAutoConfiguration {
...
@@ -75,11 +77,10 @@ public class WebMvcMetricsAutoConfiguration {
@Bean
@Bean
public
FilterRegistrationBean
<
WebMvcMetricsFilter
>
webMvcMetricsFilter
(
public
FilterRegistrationBean
<
WebMvcMetricsFilter
>
webMvcMetricsFilter
(
MeterRegistry
registry
,
WebMvcTagsProvider
tagsProvider
,
MeterRegistry
registry
,
WebMvcTagsProvider
tagsProvider
)
{
WebApplicationContext
context
)
{
Server
serverProperties
=
this
.
properties
.
getWeb
().
getServer
();
Server
serverProperties
=
this
.
properties
.
getWeb
().
getServer
();
WebMvcMetricsFilter
filter
=
new
WebMvcMetricsFilter
(
context
,
registry
,
WebMvcMetricsFilter
filter
=
new
WebMvcMetricsFilter
(
registry
,
tagsProvider
,
tagsProvider
,
serverProperties
.
getRequestsMetricName
(),
serverProperties
.
getRequestsMetricName
(),
serverProperties
.
isAutoTimeRequests
());
serverProperties
.
isAutoTimeRequests
());
FilterRegistrationBean
<
WebMvcMetricsFilter
>
registration
=
new
FilterRegistrationBean
<>(
FilterRegistrationBean
<
WebMvcMetricsFilter
>
registration
=
new
FilterRegistrationBean
<>(
filter
);
filter
);
...
@@ -98,4 +99,30 @@ public class WebMvcMetricsAutoConfiguration {
...
@@ -98,4 +99,30 @@ public class WebMvcMetricsAutoConfiguration {
this
.
properties
.
getWeb
().
getServer
().
getMaxUriTags
(),
filter
);
this
.
properties
.
getWeb
().
getServer
().
getMaxUriTags
(),
filter
);
}
}
@Bean
public
MetricsWebMvcConfigurer
metricsWebMvcConfigurer
(
MeterRegistry
meterRegistry
,
WebMvcTagsProvider
tagsProvider
)
{
return
new
MetricsWebMvcConfigurer
(
meterRegistry
,
tagsProvider
);
}
static
class
MetricsWebMvcConfigurer
implements
WebMvcConfigurer
{
private
final
MeterRegistry
meterRegistry
;
private
final
WebMvcTagsProvider
tagsProvider
;
MetricsWebMvcConfigurer
(
MeterRegistry
meterRegistry
,
WebMvcTagsProvider
tagsProvider
)
{
this
.
meterRegistry
=
meterRegistry
;
this
.
tagsProvider
=
tagsProvider
;
}
@Override
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
registry
.
addInterceptor
(
new
LongTaskTimingHandlerInterceptor
(
this
.
meterRegistry
,
this
.
tagsProvider
));
}
}
}
}
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfigurationTests.java
View file @
4bc32e63
...
@@ -33,6 +33,7 @@ import org.junit.Test;
...
@@ -33,6 +33,7 @@ import org.junit.Test;
import
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration
;
import
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration
;
import
org.springframework.boot.actuate.autoconfigure.metrics.web.TestController
;
import
org.springframework.boot.actuate.autoconfigure.metrics.web.TestController
;
import
org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.LongTaskTimingHandlerInterceptor
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider
;
import
org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider
;
import
org.springframework.boot.autoconfigure.AutoConfigurations
;
import
org.springframework.boot.autoconfigure.AutoConfigurations
;
...
@@ -47,6 +48,7 @@ import org.springframework.core.Ordered;
...
@@ -47,6 +48,7 @@ import org.springframework.core.Ordered;
import
org.springframework.test.web.servlet.MockMvc
;
import
org.springframework.test.web.servlet.MockMvc
;
import
org.springframework.test.web.servlet.request.MockMvcRequestBuilders
;
import
org.springframework.test.web.servlet.request.MockMvcRequestBuilders
;
import
org.springframework.test.web.servlet.setup.MockMvcBuilders
;
import
org.springframework.test.web.servlet.setup.MockMvcBuilders
;
import
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
status
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
status
;
...
@@ -140,6 +142,22 @@ public class WebMvcMetricsAutoConfigurationTests {
...
@@ -140,6 +142,22 @@ public class WebMvcMetricsAutoConfigurationTests {
});
});
}
}
@Test
@SuppressWarnings
(
"rawtypes"
)
public
void
longTaskTimingInterceptorIsRegistered
()
{
this
.
contextRunner
.
withUserConfiguration
(
TestController
.
class
,
MeterRegistryConfiguration
.
class
)
.
withConfiguration
(
AutoConfigurations
.
of
(
MetricsAutoConfiguration
.
class
,
WebMvcAutoConfiguration
.
class
))
.
run
((
context
)
->
{
assertThat
(
context
.
getBean
(
RequestMappingHandlerMapping
.
class
))
.
extracting
(
"interceptors"
).
element
(
0
).
asList
()
.
extracting
((
item
)
->
(
Class
)
item
.
getClass
())
.
contains
(
LongTaskTimingHandlerInterceptor
.
class
);
});
}
private
MeterRegistry
getInitializedMeterRegistry
(
private
MeterRegistry
getInitializedMeterRegistry
(
AssertableWebApplicationContext
context
)
throws
Exception
{
AssertableWebApplicationContext
context
)
throws
Exception
{
assertThat
(
context
).
hasSingleBean
(
FilterRegistrationBean
.
class
);
assertThat
(
context
).
hasSingleBean
(
FilterRegistrationBean
.
class
);
...
...
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor.java
0 → 100644
View file @
4bc32e63
/*
* Copyright 2012-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
.
boot
.
actuate
.
metrics
.
web
.
servlet
;
import
java.lang.reflect.AnnotatedElement
;
import
java.util.ArrayList
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Set
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
io.micrometer.core.annotation.Timed
;
import
io.micrometer.core.instrument.LongTaskTimer
;
import
io.micrometer.core.instrument.MeterRegistry
;
import
io.micrometer.core.instrument.Tag
;
import
org.springframework.core.annotation.AnnotationUtils
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.HandlerInterceptor
;
/**
* A {@link HandlerInterceptor} that supports Micrometer's long task timers configured on
* a handler using {@link Timed} with {@link Timed#longTask()} set to {@code true}.
*
* @author Andy Wilkinson
* @since 2.0.7
*/
public
class
LongTaskTimingHandlerInterceptor
implements
HandlerInterceptor
{
private
final
MeterRegistry
registry
;
private
final
WebMvcTagsProvider
tagsProvider
;
/**
* Creates a new {@ode LongTaskTimingHandlerInterceptor} that will create
* {@link LongTaskTimer LongTaskTimers} using the given registry. Timers will be
* tagged using the given {@code tagsProvider}.
* @param registry the registry
* @param tagsProvider the tags provider
*/
public
LongTaskTimingHandlerInterceptor
(
MeterRegistry
registry
,
WebMvcTagsProvider
tagsProvider
)
{
this
.
registry
=
registry
;
this
.
tagsProvider
=
tagsProvider
;
}
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
throws
Exception
{
LongTaskTimingContext
timingContext
=
LongTaskTimingContext
.
get
(
request
);
if
(
timingContext
==
null
)
{
startAndAttachTimingContext
(
request
,
handler
);
}
return
true
;
}
@Override
public
void
afterCompletion
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
,
Exception
ex
)
throws
Exception
{
if
(!
request
.
isAsyncStarted
())
{
stopLongTaskTimers
(
LongTaskTimingContext
.
get
(
request
));
}
}
private
void
startAndAttachTimingContext
(
HttpServletRequest
request
,
Object
handler
)
{
Set
<
Timed
>
annotations
=
getTimedAnnotations
(
handler
);
Collection
<
LongTaskTimer
.
Sample
>
longTaskTimerSamples
=
getLongTaskTimerSamples
(
request
,
handler
,
annotations
);
LongTaskTimingContext
timingContext
=
new
LongTaskTimingContext
(
longTaskTimerSamples
);
timingContext
.
attachTo
(
request
);
}
private
Collection
<
LongTaskTimer
.
Sample
>
getLongTaskTimerSamples
(
HttpServletRequest
request
,
Object
handler
,
Set
<
Timed
>
annotations
)
{
List
<
LongTaskTimer
.
Sample
>
samples
=
new
ArrayList
<>();
annotations
.
stream
().
filter
(
Timed:
:
longTask
).
forEach
((
annotation
)
->
{
Iterable
<
Tag
>
tags
=
this
.
tagsProvider
.
getLongRequestTags
(
request
,
handler
);
LongTaskTimer
.
Builder
builder
=
LongTaskTimer
.
builder
(
annotation
).
tags
(
tags
);
LongTaskTimer
timer
=
builder
.
register
(
this
.
registry
);
samples
.
add
(
timer
.
start
());
});
return
samples
;
}
private
Set
<
Timed
>
getTimedAnnotations
(
Object
handler
)
{
if
(!(
handler
instanceof
HandlerMethod
))
{
return
Collections
.
emptySet
();
}
return
getTimedAnnotations
((
HandlerMethod
)
handler
);
}
private
Set
<
Timed
>
getTimedAnnotations
(
HandlerMethod
handler
)
{
Set
<
Timed
>
timed
=
findTimedAnnotations
(
handler
.
getMethod
());
if
(
timed
.
isEmpty
())
{
return
findTimedAnnotations
(
handler
.
getBeanType
());
}
return
timed
;
}
private
Set
<
Timed
>
findTimedAnnotations
(
AnnotatedElement
element
)
{
return
AnnotationUtils
.
getDeclaredRepeatableAnnotations
(
element
,
Timed
.
class
);
}
private
void
stopLongTaskTimers
(
LongTaskTimingContext
timingContext
)
{
for
(
LongTaskTimer
.
Sample
sample
:
timingContext
.
getLongTaskTimerSamples
())
{
sample
.
stop
();
}
}
/**
* Context object attached to a request to retain information across the multiple
* interceptor calls that happen with async requests.
*/
static
class
LongTaskTimingContext
{
private
static
final
String
ATTRIBUTE
=
LongTaskTimingContext
.
class
.
getName
();
private
final
Collection
<
LongTaskTimer
.
Sample
>
longTaskTimerSamples
;
LongTaskTimingContext
(
Collection
<
LongTaskTimer
.
Sample
>
longTaskTimerSamples
)
{
this
.
longTaskTimerSamples
=
longTaskTimerSamples
;
}
Collection
<
LongTaskTimer
.
Sample
>
getLongTaskTimerSamples
()
{
return
this
.
longTaskTimerSamples
;
}
void
attachTo
(
HttpServletRequest
request
)
{
request
.
setAttribute
(
ATTRIBUTE
,
this
);
}
static
LongTaskTimingContext
get
(
HttpServletRequest
request
)
{
return
(
LongTaskTimingContext
)
request
.
getAttribute
(
ATTRIBUTE
);
}
}
}
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java
View file @
4bc32e63
...
@@ -18,28 +18,21 @@ package org.springframework.boot.actuate.metrics.web.servlet;
...
@@ -18,28 +18,21 @@ package org.springframework.boot.actuate.metrics.web.servlet;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.lang.reflect.AnnotatedElement
;
import
java.lang.reflect.AnnotatedElement
;
import
java.util.ArrayList
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.Set
;
import
java.util.function.Supplier
;
import
java.util.function.Supplier
;
import
javax.servlet.FilterChain
;
import
javax.servlet.FilterChain
;
import
javax.servlet.ServletException
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletRequestWrapper
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.servlet.http.HttpServletResponse
;
import
io.micrometer.core.annotation.Timed
;
import
io.micrometer.core.annotation.Timed
;
import
io.micrometer.core.instrument.LongTaskTimer
;
import
io.micrometer.core.instrument.MeterRegistry
;
import
io.micrometer.core.instrument.MeterRegistry
;
import
io.micrometer.core.instrument.Tag
;
import
io.micrometer.core.instrument.Tag
;
import
io.micrometer.core.instrument.Timer
;
import
io.micrometer.core.instrument.Timer
;
import
io.micrometer.core.instrument.Timer.Builder
;
import
io.micrometer.core.instrument.Timer.Builder
;
import
io.micrometer.core.instrument.Timer.Sample
;
import
io.micrometer.core.instrument.Timer.Sample
;
import
org.apache.commons.logging.Log
;
import
org.apache.commons.logging.LogFactory
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.core.annotation.AnnotationUtils
;
import
org.springframework.core.annotation.AnnotationUtils
;
...
@@ -47,10 +40,7 @@ import org.springframework.http.HttpStatus;
...
@@ -47,10 +40,7 @@ import org.springframework.http.HttpStatus;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.DispatcherServlet
;
import
org.springframework.web.servlet.DispatcherServlet
;
import
org.springframework.web.servlet.HandlerExecutionChain
;
import
org.springframework.web.servlet.HandlerMapping
;
import
org.springframework.web.servlet.HandlerMapping
;
import
org.springframework.web.servlet.handler.HandlerMappingIntrospector
;
import
org.springframework.web.servlet.handler.MatchableHandlerMapping
;
import
org.springframework.web.util.NestedServletException
;
import
org.springframework.web.util.NestedServletException
;
/**
/**
...
@@ -63,10 +53,6 @@ import org.springframework.web.util.NestedServletException;
...
@@ -63,10 +53,6 @@ import org.springframework.web.util.NestedServletException;
*/
*/
public
class
WebMvcMetricsFilter
extends
OncePerRequestFilter
{
public
class
WebMvcMetricsFilter
extends
OncePerRequestFilter
{
private
static
final
Log
logger
=
LogFactory
.
getLog
(
WebMvcMetricsFilter
.
class
);
private
final
ApplicationContext
context
;
private
final
MeterRegistry
registry
;
private
final
MeterRegistry
registry
;
private
final
WebMvcTagsProvider
tagsProvider
;
private
final
WebMvcTagsProvider
tagsProvider
;
...
@@ -75,8 +61,6 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -75,8 +61,6 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
private
final
boolean
autoTimeRequests
;
private
final
boolean
autoTimeRequests
;
private
volatile
HandlerMappingIntrospector
introspector
;
/**
/**
* Create a new {@link WebMvcMetricsFilter} instance.
* Create a new {@link WebMvcMetricsFilter} instance.
* @param context the source application context
* @param context the source application context
...
@@ -84,11 +68,26 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -84,11 +68,26 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
* @param tagsProvider the tags provider
* @param tagsProvider the tags provider
* @param metricName the metric name
* @param metricName the metric name
* @param autoTimeRequests if requests should be automatically timed
* @param autoTimeRequests if requests should be automatically timed
* @deprecated since 2.0.7 in favor of
* {@link #WebMvcMetricsFilter(MeterRegistry, WebMvcTagsProvider, String, boolean)}
*/
*/
@Deprecated
public
WebMvcMetricsFilter
(
ApplicationContext
context
,
MeterRegistry
registry
,
public
WebMvcMetricsFilter
(
ApplicationContext
context
,
MeterRegistry
registry
,
WebMvcTagsProvider
tagsProvider
,
String
metricName
,
WebMvcTagsProvider
tagsProvider
,
String
metricName
,
boolean
autoTimeRequests
)
{
boolean
autoTimeRequests
)
{
this
.
context
=
context
;
this
(
registry
,
tagsProvider
,
metricName
,
autoTimeRequests
);
}
/**
* Create a new {@link WebMvcMetricsFilter} instance.
* @param registry the meter registry
* @param tagsProvider the tags provider
* @param metricName the metric name
* @param autoTimeRequests if requests should be automatically timed
* @since 2.0.7
*/
public
WebMvcMetricsFilter
(
MeterRegistry
registry
,
WebMvcTagsProvider
tagsProvider
,
String
metricName
,
boolean
autoTimeRequests
)
{
this
.
registry
=
registry
;
this
.
registry
=
registry
;
this
.
tagsProvider
=
tagsProvider
;
this
.
tagsProvider
=
tagsProvider
;
this
.
metricName
=
metricName
;
this
.
metricName
=
metricName
;
...
@@ -110,45 +109,9 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -110,45 +109,9 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
private
void
filterAndRecordMetrics
(
HttpServletRequest
request
,
private
void
filterAndRecordMetrics
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
IOException
,
ServletException
{
throws
IOException
,
ServletException
{
Object
handler
;
try
{
handler
=
getHandler
(
request
);
}
catch
(
Exception
ex
)
{
logger
.
debug
(
"Unable to time request"
,
ex
);
filterChain
.
doFilter
(
request
,
response
);
return
;
}
filterAndRecordMetrics
(
request
,
response
,
filterChain
,
handler
);
}
private
Object
getHandler
(
HttpServletRequest
request
)
throws
Exception
{
HttpServletRequest
wrapper
=
new
UnmodifiableAttributesRequestWrapper
(
request
);
for
(
HandlerMapping
mapping
:
getMappingIntrospector
().
getHandlerMappings
())
{
HandlerExecutionChain
chain
=
mapping
.
getHandler
(
wrapper
);
if
(
chain
!=
null
)
{
if
(
mapping
instanceof
MatchableHandlerMapping
)
{
return
chain
.
getHandler
();
}
return
null
;
}
}
return
null
;
}
private
HandlerMappingIntrospector
getMappingIntrospector
()
{
if
(
this
.
introspector
==
null
)
{
this
.
introspector
=
this
.
context
.
getBean
(
HandlerMappingIntrospector
.
class
);
}
return
this
.
introspector
;
}
private
void
filterAndRecordMetrics
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
,
Object
handler
)
throws
IOException
,
ServletException
{
TimingContext
timingContext
=
TimingContext
.
get
(
request
);
TimingContext
timingContext
=
TimingContext
.
get
(
request
);
if
(
timingContext
==
null
)
{
if
(
timingContext
==
null
)
{
timingContext
=
startAndAttachTimingContext
(
request
,
handler
);
timingContext
=
startAndAttachTimingContext
(
request
);
}
}
try
{
try
{
filterChain
.
doFilter
(
request
,
response
);
filterChain
.
doFilter
(
request
,
response
);
...
@@ -159,24 +122,19 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -159,24 +122,19 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
// TimingContext that was attached to the first)
// TimingContext that was attached to the first)
Throwable
exception
=
(
Throwable
)
request
Throwable
exception
=
(
Throwable
)
request
.
getAttribute
(
DispatcherServlet
.
EXCEPTION_ATTRIBUTE
);
.
getAttribute
(
DispatcherServlet
.
EXCEPTION_ATTRIBUTE
);
record
(
timingContext
,
response
,
request
,
handler
,
exception
);
record
(
timingContext
,
response
,
request
,
exception
);
}
}
}
}
catch
(
NestedServletException
ex
)
{
catch
(
NestedServletException
ex
)
{
response
.
setStatus
(
HttpStatus
.
INTERNAL_SERVER_ERROR
.
value
());
response
.
setStatus
(
HttpStatus
.
INTERNAL_SERVER_ERROR
.
value
());
record
(
timingContext
,
response
,
request
,
handler
,
ex
.
getCause
());
record
(
timingContext
,
response
,
request
,
ex
.
getCause
());
throw
ex
;
throw
ex
;
}
}
}
}
private
TimingContext
startAndAttachTimingContext
(
HttpServletRequest
request
,
private
TimingContext
startAndAttachTimingContext
(
HttpServletRequest
request
)
{
Object
handler
)
{
Set
<
Timed
>
annotations
=
getTimedAnnotations
(
handler
);
Timer
.
Sample
timerSample
=
Timer
.
start
(
this
.
registry
);
Timer
.
Sample
timerSample
=
Timer
.
start
(
this
.
registry
);
Collection
<
LongTaskTimer
.
Sample
>
longTaskTimerSamples
=
getLongTaskTimerSamples
(
TimingContext
timingContext
=
new
TimingContext
(
timerSample
);
request
,
handler
,
annotations
);
TimingContext
timingContext
=
new
TimingContext
(
annotations
,
timerSample
,
longTaskTimerSamples
);
timingContext
.
attachTo
(
request
);
timingContext
.
attachTo
(
request
);
return
timingContext
;
return
timingContext
;
}
}
...
@@ -200,31 +158,23 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -200,31 +158,23 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
return
AnnotationUtils
.
getDeclaredRepeatableAnnotations
(
element
,
Timed
.
class
);
return
AnnotationUtils
.
getDeclaredRepeatableAnnotations
(
element
,
Timed
.
class
);
}
}
private
Collection
<
LongTaskTimer
.
Sample
>
getLongTaskTimerSamples
(
HttpServletRequest
request
,
Object
handler
,
Set
<
Timed
>
annotations
)
{
List
<
LongTaskTimer
.
Sample
>
samples
=
new
ArrayList
<>();
annotations
.
stream
().
filter
(
Timed:
:
longTask
).
forEach
((
annotation
)
->
{
Iterable
<
Tag
>
tags
=
this
.
tagsProvider
.
getLongRequestTags
(
request
,
handler
);
LongTaskTimer
.
Builder
builder
=
LongTaskTimer
.
builder
(
annotation
).
tags
(
tags
);
LongTaskTimer
timer
=
builder
.
register
(
this
.
registry
);
samples
.
add
(
timer
.
start
());
});
return
samples
;
}
private
void
record
(
TimingContext
timingContext
,
HttpServletResponse
response
,
private
void
record
(
TimingContext
timingContext
,
HttpServletResponse
response
,
HttpServletRequest
request
,
Object
handlerObject
,
Throwable
exception
)
{
HttpServletRequest
request
,
Throwable
exception
)
{
Object
handlerObject
=
request
.
getAttribute
(
HandlerMapping
.
BEST_MATCHING_HANDLER_ATTRIBUTE
);
Set
<
Timed
>
annotations
=
getTimedAnnotations
(
handlerObject
);
Timer
.
Sample
timerSample
=
timingContext
.
getTimerSample
();
Timer
.
Sample
timerSample
=
timingContext
.
getTimerSample
();
Supplier
<
Iterable
<
Tag
>>
tags
=
()
->
this
.
tagsProvider
.
getTags
(
request
,
response
,
Supplier
<
Iterable
<
Tag
>>
tags
=
()
->
this
.
tagsProvider
.
getTags
(
request
,
response
,
handlerObject
,
exception
);
handlerObject
,
exception
);
for
(
Timed
annotation
:
timingContext
.
getAnnotations
())
{
if
(
annotations
.
isEmpty
())
{
stop
(
timerSample
,
tags
,
Timer
.
builder
(
annotation
,
this
.
metricName
));
if
(
this
.
autoTimeRequests
)
{
}
stop
(
timerSample
,
tags
,
Timer
.
builder
(
this
.
metricName
));
if
(
timingContext
.
getAnnotations
().
isEmpty
()
&&
this
.
autoTimeRequests
)
{
}
stop
(
timerSample
,
tags
,
Timer
.
builder
(
this
.
metricName
));
}
}
for
(
LongTaskTimer
.
Sample
sample
:
timingContext
.
getLongTaskTimerSamples
())
{
else
{
sample
.
stop
();
for
(
Timed
annotation
:
annotations
)
{
stop
(
timerSample
,
tags
,
Timer
.
builder
(
annotation
,
this
.
metricName
));
}
}
}
}
}
...
@@ -241,31 +191,16 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -241,31 +191,16 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
private
static
final
String
ATTRIBUTE
=
TimingContext
.
class
.
getName
();
private
static
final
String
ATTRIBUTE
=
TimingContext
.
class
.
getName
();
private
final
Set
<
Timed
>
annotations
;
private
final
Timer
.
Sample
timerSample
;
private
final
Timer
.
Sample
timerSample
;
private
final
Collection
<
LongTaskTimer
.
Sample
>
longTaskTimerSamples
;
TimingContext
(
Sample
timerSample
)
{
TimingContext
(
Set
<
Timed
>
annotations
,
Sample
timerSample
,
Collection
<
io
.
micrometer
.
core
.
instrument
.
LongTaskTimer
.
Sample
>
longTaskTimerSamples
)
{
this
.
annotations
=
annotations
;
this
.
timerSample
=
timerSample
;
this
.
timerSample
=
timerSample
;
this
.
longTaskTimerSamples
=
longTaskTimerSamples
;
}
public
Set
<
Timed
>
getAnnotations
()
{
return
this
.
annotations
;
}
}
public
Timer
.
Sample
getTimerSample
()
{
public
Timer
.
Sample
getTimerSample
()
{
return
this
.
timerSample
;
return
this
.
timerSample
;
}
}
public
Collection
<
LongTaskTimer
.
Sample
>
getLongTaskTimerSamples
()
{
return
this
.
longTaskTimerSamples
;
}
public
void
attachTo
(
HttpServletRequest
request
)
{
public
void
attachTo
(
HttpServletRequest
request
)
{
request
.
setAttribute
(
ATTRIBUTE
,
this
);
request
.
setAttribute
(
ATTRIBUTE
,
this
);
}
}
...
@@ -276,21 +211,4 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
...
@@ -276,21 +211,4 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
}
}
/**
* An {@link HttpServletRequestWrapper} that prevents modification of the request's
* attributes.
*/
private
static
final
class
UnmodifiableAttributesRequestWrapper
extends
HttpServletRequestWrapper
{
private
UnmodifiableAttributesRequestWrapper
(
HttpServletRequest
request
)
{
super
(
request
);
}
@Override
public
void
setAttribute
(
String
name
,
Object
value
)
{
}
}
}
}
spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptorTests.java
0 → 100644
View file @
4bc32e63
/*
* Copyright 2012-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
.
boot
.
actuate
.
metrics
.
web
.
servlet
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.CompletableFuture
;
import
java.util.concurrent.CyclicBarrier
;
import
java.util.concurrent.atomic.AtomicReference
;
import
io.micrometer.core.annotation.Timed
;
import
io.micrometer.core.instrument.Clock
;
import
io.micrometer.core.instrument.MeterRegistry
;
import
io.micrometer.core.instrument.MockClock
;
import
io.micrometer.core.instrument.simple.SimpleConfig
;
import
io.micrometer.core.instrument.simple.SimpleMeterRegistry
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Import
;
import
org.springframework.test.context.junit4.SpringRunner
;
import
org.springframework.test.context.web.WebAppConfiguration
;
import
org.springframework.test.web.servlet.MockMvc
;
import
org.springframework.test.web.servlet.MvcResult
;
import
org.springframework.test.web.servlet.setup.MockMvcBuilders
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
org.springframework.web.context.WebApplicationContext
;
import
org.springframework.web.servlet.config.annotation.EnableWebMvc
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
;
import
org.springframework.web.util.NestedServletException
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThatExceptionOfType
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
fail
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
request
.
MockMvcRequestBuilders
.
asyncDispatch
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
request
.
MockMvcRequestBuilders
.
get
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
request
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
status
;
/**
* Tests for {@link LongTaskTimingHandlerInterceptor}.
*
* @author Andy Wilkinson
*/
@RunWith
(
SpringRunner
.
class
)
@WebAppConfiguration
public
class
LongTaskTimingHandlerInterceptorTests
{
@Autowired
private
SimpleMeterRegistry
registry
;
@Autowired
private
WebApplicationContext
context
;
@Autowired
private
CyclicBarrier
callableBarrier
;
private
MockMvc
mvc
;
@Before
public
void
setUpMockMvc
()
{
this
.
mvc
=
MockMvcBuilders
.
webAppContextSetup
(
this
.
context
).
build
();
}
@Test
public
void
asyncRequestThatThrowsUncheckedException
()
throws
Exception
{
MvcResult
result
=
this
.
mvc
.
perform
(
get
(
"/api/c1/completableFutureException"
))
.
andExpect
(
request
().
asyncStarted
()).
andReturn
();
assertThat
(
this
.
registry
.
get
(
"my.long.request.exception"
).
longTaskTimer
()
.
activeTasks
()).
isEqualTo
(
1
);
assertThatExceptionOfType
(
NestedServletException
.
class
)
.
isThrownBy
(()
->
this
.
mvc
.
perform
(
asyncDispatch
(
result
)))
.
withRootCauseInstanceOf
(
RuntimeException
.
class
);
assertThat
(
this
.
registry
.
get
(
"my.long.request.exception"
).
longTaskTimer
()
.
activeTasks
()).
isEqualTo
(
0
);
}
@Test
public
void
asyncCallableRequest
()
throws
Exception
{
AtomicReference
<
MvcResult
>
result
=
new
AtomicReference
<>();
Thread
backgroundRequest
=
new
Thread
(()
->
{
try
{
result
.
set
(
this
.
mvc
.
perform
(
get
(
"/api/c1/callable/10"
))
.
andExpect
(
request
().
asyncStarted
()).
andReturn
());
}
catch
(
Exception
ex
)
{
fail
(
"Failed to execute async request"
,
ex
);
}
});
backgroundRequest
.
start
();
this
.
callableBarrier
.
await
();
assertThat
(
this
.
registry
.
get
(
"my.long.request"
).
tags
(
"region"
,
"test"
)
.
longTaskTimer
().
activeTasks
()).
isEqualTo
(
1
);
this
.
callableBarrier
.
await
();
backgroundRequest
.
join
();
this
.
mvc
.
perform
(
asyncDispatch
(
result
.
get
())).
andExpect
(
status
().
isOk
());
assertThat
(
this
.
registry
.
get
(
"my.long.request"
).
tags
(
"region"
,
"test"
)
.
longTaskTimer
().
activeTasks
()).
isEqualTo
(
0
);
}
@Configuration
@EnableWebMvc
@Import
(
Controller1
.
class
)
static
class
MetricsInterceptorConfiguration
{
@Bean
Clock
micrometerClock
()
{
return
new
MockClock
();
}
@Bean
SimpleMeterRegistry
simple
(
Clock
clock
)
{
return
new
SimpleMeterRegistry
(
SimpleConfig
.
DEFAULT
,
clock
);
}
@Bean
CyclicBarrier
callableBarrier
()
{
return
new
CyclicBarrier
(
2
);
}
@Bean
WebMvcConfigurer
handlerInterceptorConfigurer
(
MeterRegistry
meterRegistry
)
{
return
new
WebMvcConfigurer
()
{
@Override
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
registry
.
addInterceptor
(
new
LongTaskTimingHandlerInterceptor
(
meterRegistry
,
new
DefaultWebMvcTagsProvider
()));
}
};
}
}
@RestController
@RequestMapping
(
"/api/c1"
)
static
class
Controller1
{
@Autowired
private
CyclicBarrier
callableBarrier
;
@Timed
@Timed
(
value
=
"my.long.request"
,
extraTags
=
{
"region"
,
"test"
},
longTask
=
true
)
@GetMapping
(
"/callable/{id}"
)
public
Callable
<
String
>
asyncCallable
(
@PathVariable
Long
id
)
throws
Exception
{
this
.
callableBarrier
.
await
();
return
()
->
{
try
{
this
.
callableBarrier
.
await
();
}
catch
(
InterruptedException
ex
)
{
throw
new
RuntimeException
(
ex
);
}
return
id
.
toString
();
};
}
@Timed
@Timed
(
value
=
"my.long.request.exception"
,
longTask
=
true
)
@GetMapping
(
"/completableFutureException"
)
CompletableFuture
<
String
>
asyncCompletableFutureException
()
{
return
CompletableFuture
.
supplyAsync
(()
->
{
throw
new
RuntimeException
(
"boom"
);
});
}
}
}
spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java
View file @
4bc32e63
...
@@ -219,39 +219,24 @@ public class WebMvcMetricsFilterTests {
...
@@ -219,39 +219,24 @@ public class WebMvcMetricsFilterTests {
// once the mapping completes, we can gather information about status, etc.
// once the mapping completes, we can gather information about status, etc.
this
.
callableBarrier
.
await
();
this
.
callableBarrier
.
await
();
MockClock
.
clock
(
this
.
registry
).
add
(
Duration
.
ofSeconds
(
2
));
MockClock
.
clock
(
this
.
registry
).
add
(
Duration
.
ofSeconds
(
2
));
// while the mapping is running, it contributes to the activeTasks count
assertThat
(
this
.
registry
.
get
(
"my.long.request"
).
tags
(
"region"
,
"test"
)
.
longTaskTimer
().
activeTasks
()).
isEqualTo
(
1
);
this
.
callableBarrier
.
await
();
this
.
callableBarrier
.
await
();
backgroundRequest
.
join
();
backgroundRequest
.
join
();
this
.
mvc
.
perform
(
asyncDispatch
(
result
.
get
())).
andExpect
(
status
().
isOk
());
this
.
mvc
.
perform
(
asyncDispatch
(
result
.
get
())).
andExpect
(
status
().
isOk
());
assertThat
(
this
.
registry
.
get
(
"http.server.requests"
).
tags
(
"status"
,
"200"
)
assertThat
(
this
.
registry
.
get
(
"http.server.requests"
).
tags
(
"status"
,
"200"
)
.
tags
(
"uri"
,
"/api/c1/callable/{id}"
).
timer
().
totalTime
(
TimeUnit
.
SECONDS
))
.
tags
(
"uri"
,
"/api/c1/callable/{id}"
).
timer
().
totalTime
(
TimeUnit
.
SECONDS
))
.
isEqualTo
(
2L
);
.
isEqualTo
(
2L
);
// once the async dispatch is complete, it should no longer contribute to the
// activeTasks count
assertThat
(
this
.
registry
.
get
(
"my.long.request"
).
tags
(
"region"
,
"test"
)
.
longTaskTimer
().
activeTasks
()).
isEqualTo
(
0
);
}
}
@Test
@Test
public
void
asyncRequestThatThrowsUncheckedException
()
throws
Exception
{
public
void
asyncRequestThatThrowsUncheckedException
()
throws
Exception
{
MvcResult
result
=
this
.
mvc
.
perform
(
get
(
"/api/c1/completableFutureException"
))
MvcResult
result
=
this
.
mvc
.
perform
(
get
(
"/api/c1/completableFutureException"
))
.
andExpect
(
request
().
asyncStarted
()).
andReturn
();
.
andExpect
(
request
().
asyncStarted
()).
andReturn
();
// once the async dispatch is complete, it should no longer contribute to the
// activeTasks count
assertThat
(
this
.
registry
.
get
(
"my.long.request.exception"
).
longTaskTimer
()
.
activeTasks
()).
isEqualTo
(
1
);
assertThatExceptionOfType
(
NestedServletException
.
class
)
assertThatExceptionOfType
(
NestedServletException
.
class
)
.
isThrownBy
(()
->
this
.
mvc
.
perform
(
asyncDispatch
(
result
)))
.
isThrownBy
(()
->
this
.
mvc
.
perform
(
asyncDispatch
(
result
)))
.
withRootCauseInstanceOf
(
RuntimeException
.
class
);
.
withRootCauseInstanceOf
(
RuntimeException
.
class
);
assertThat
(
this
.
registry
.
get
(
"http.server.requests"
)
assertThat
(
this
.
registry
.
get
(
"http.server.requests"
)
.
tags
(
"uri"
,
"/api/c1/completableFutureException"
).
timer
().
count
())
.
tags
(
"uri"
,
"/api/c1/completableFutureException"
).
timer
().
count
())
.
isEqualTo
(
1
);
.
isEqualTo
(
1
);
// once the async dispatch is complete, it should no longer contribute to the
// activeTasks count
assertThat
(
this
.
registry
.
get
(
"my.long.request.exception"
).
longTaskTimer
()
.
activeTasks
()).
isEqualTo
(
0
);
}
}
@Test
@Test
...
@@ -375,7 +360,7 @@ public class WebMvcMetricsFilterTests {
...
@@ -375,7 +360,7 @@ public class WebMvcMetricsFilterTests {
@Bean
@Bean
WebMvcMetricsFilter
webMetricsFilter
(
MeterRegistry
registry
,
WebMvcMetricsFilter
webMetricsFilter
(
MeterRegistry
registry
,
WebApplicationContext
ctx
)
{
WebApplicationContext
ctx
)
{
return
new
WebMvcMetricsFilter
(
ctx
,
registry
,
new
DefaultWebMvcTagsProvider
(),
return
new
WebMvcMetricsFilter
(
registry
,
new
DefaultWebMvcTagsProvider
(),
"http.server.requests"
,
true
);
"http.server.requests"
,
true
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment