package org.stagemonitor.web.monitor.spring; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletConfig; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationRegistry; import org.stagemonitor.core.CorePlugin; import org.stagemonitor.core.MeasurementSession; import org.stagemonitor.core.Stagemonitor; import org.stagemonitor.core.elasticsearch.ElasticsearchClient; import org.stagemonitor.core.metrics.metrics2.Metric2Filter; import org.stagemonitor.core.metrics.metrics2.Metric2Registry; import org.stagemonitor.tracing.MockTracer; import org.stagemonitor.tracing.RequestMonitor; import org.stagemonitor.tracing.SpanContextInformation; import org.stagemonitor.tracing.TagRecordingSpanEventListener; import org.stagemonitor.tracing.TracingPlugin; import org.stagemonitor.tracing.reporter.ReportingSpanEventListener; import org.stagemonitor.tracing.sampling.SamplePriorityDeterminingSpanEventListener; import org.stagemonitor.tracing.wrapper.SpanWrappingTracer; import org.stagemonitor.web.WebPlugin; import org.stagemonitor.web.monitor.MonitoredHttpRequest; import org.stagemonitor.web.monitor.filter.StatusExposingByteCountingServletResponse; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import io.opentracing.tag.Tags; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.stagemonitor.core.metrics.metrics2.MetricName.name; import static org.stagemonitor.tracing.BusinessTransactionNamingStrategy.METHOD_NAME_SPLIT_CAMEL_CASE; import static org.stagemonitor.tracing.metrics.ServerRequestMetricsSpanEventListener.getTimerMetricName; public class SpringRequestMonitorTest { private MockHttpServletRequest mvcRequest = new MockHttpServletRequest("GET", "/test/requestName"); private MockHttpServletRequest nonMvcRequest = new MockHttpServletRequest("GET", "/META-INF/resources/stagemonitor/static/jquery.js"); private ConfigurationRegistry configuration = mock(ConfigurationRegistry.class); private TracingPlugin tracingPlugin = mock(TracingPlugin.class); private WebPlugin webPlugin = mock(WebPlugin.class); private CorePlugin corePlugin = mock(CorePlugin.class); private RequestMonitor requestMonitor; private Metric2Registry registry = new Metric2Registry(); private HandlerMapping getRequestNameHandlerMapping; private DispatcherServlet dispatcherServlet; private Map<String, Object> tags = new HashMap<>(); // the purpose of this class is to obtain a instance to a Method, // because Method objects can't be mocked as they are final private static class TestController { public void testGetRequestName() { } } @Before public void before() throws Exception { Stagemonitor.reset(); registry.removeMatching(Metric2Filter.ALL); Stagemonitor.getMetric2Registry().removeMatching(Metric2Filter.ALL); Stagemonitor.startMonitoring(new MeasurementSession("MonitoredHttpRequestTest", "testHost", "testInstance")); getRequestNameHandlerMapping = createHandlerMapping(mvcRequest, TestController.class.getMethod("testGetRequestName")); when(configuration.getConfig(TracingPlugin.class)).thenReturn(tracingPlugin); when(configuration.getConfig(WebPlugin.class)).thenReturn(webPlugin); when(configuration.getConfig(CorePlugin.class)).thenReturn(corePlugin); when(corePlugin.isStagemonitorActive()).thenReturn(true); when(corePlugin.getThreadPoolQueueCapacityLimit()).thenReturn(1000); when(corePlugin.getMetricRegistry()).thenReturn(registry); when(corePlugin.getElasticsearchClient()).thenReturn(mock(ElasticsearchClient.class)); when(tracingPlugin.getBusinessTransactionNamingStrategy()).thenReturn(METHOD_NAME_SPLIT_CAMEL_CASE); when(tracingPlugin.getRateLimitServerSpansPerMinute()).thenReturn(1_000_000.0); when(tracingPlugin.getRateLimitServerSpansPerMinuteOption()).thenReturn(mock(ConfigurationOption.class)); when(tracingPlugin.getRateLimitClientSpansPerMinuteOption()).thenReturn(mock(ConfigurationOption.class)); when(tracingPlugin.getRateLimitClientSpansPerTypePerMinuteOption()).thenReturn(mock(ConfigurationOption.class)); when(tracingPlugin.getProfilerRateLimitPerMinuteOption()).thenReturn(mock(ConfigurationOption.class)); when(webPlugin.getGroupUrls()).thenReturn(Collections.singletonMap(Pattern.compile("(.*).js$"), "*.js")); requestMonitor = new RequestMonitor(configuration, registry); dispatcherServlet = new DispatcherServlet(new StaticWebApplicationContext()); dispatcherServlet.init(new MockServletConfig()); final Field handlerMappings = DispatcherServlet.class.getDeclaredField("handlerMappings"); handlerMappings.setAccessible(true); handlerMappings.set(dispatcherServlet, Collections.singletonList(getRequestNameHandlerMapping)); final Field handlerAdapters = DispatcherServlet.class.getDeclaredField("handlerAdapters"); handlerAdapters.setAccessible(true); final HandlerAdapter handlerAdapter = mock(HandlerAdapter.class); when(handlerAdapter.supports(any())).thenReturn(true); handlerAdapters.set(dispatcherServlet, Collections.singletonList(handlerAdapter)); final SpanWrappingTracer tracer = TracingPlugin.createSpanWrappingTracer(new MockTracer(), configuration, registry, TagRecordingSpanEventListener.asList(tags), new SamplePriorityDeterminingSpanEventListener(configuration), new ReportingSpanEventListener(configuration)); when(tracingPlugin.getTracer()).thenReturn(tracer); when(tracingPlugin.getRequestMonitor()).thenReturn(requestMonitor); } private HandlerMapping createHandlerMapping(MockHttpServletRequest request, Method requestMappingMethod) throws Exception { System.out.println("createHandlerMapping" + request); HandlerMapping requestMappingHandlerMapping = mock(HandlerMapping.class); HandlerExecutionChain handlerExecutionChain = mock(HandlerExecutionChain.class); HandlerMethod handlerMethod = mock(HandlerMethod.class); when(handlerMethod.getMethod()).thenReturn(requestMappingMethod); doReturn(TestController.class).when(handlerMethod).getBeanType(); when(handlerExecutionChain.getHandler()).thenReturn(handlerMethod); when(requestMappingHandlerMapping.getHandler(ArgumentMatchers.argThat(item -> item.getRequestURI().equals("/test/requestName")))).thenReturn(handlerExecutionChain); return requestMappingHandlerMapping; } @Test public void testRequestMonitorMvcRequest() throws Exception { when(webPlugin.isMonitorOnlySpringMvcRequests()).thenReturn(false); MonitoredHttpRequest monitoredRequest = createMonitoredHttpRequest(mvcRequest); final SpanContextInformation spanContext = requestMonitor.monitor(monitoredRequest); assertEquals("Test Get Request Name", spanContext.getOperationName()); assertEquals(1, registry.timer(getTimerMetricName(spanContext.getOperationName())).getCount()); assertEquals("Test Get Request Name", spanContext.getOperationName()); assertEquals("/test/requestName", tags.get(Tags.HTTP_URL.getKey())); assertEquals("GET", tags.get("method")); assertNotNull(registry.getTimers().get(name("response_time_server").tag("request_name", "Test Get Request Name").layer("All").build())); } @Test public void testRequestMonitorNonMvcRequestDoMonitor() throws Exception { when(webPlugin.isMonitorOnlySpringMvcRequests()).thenReturn(false); final MonitoredHttpRequest monitoredRequest = createMonitoredHttpRequest(nonMvcRequest); SpanContextInformation spanContext = requestMonitor.monitor(monitoredRequest); assertEquals("GET *.js", spanContext.getOperationName()); assertEquals("GET *.js", spanContext.getOperationName()); assertNotNull(registry.getTimers().get(name("response_time_server").tag("request_name", "GET *.js").layer("All").build())); assertEquals(1, registry.timer(getTimerMetricName(spanContext.getOperationName())).getCount()); verify(monitoredRequest, times(1)).getRequestName(); assertTrue(spanContext.isSampled()); } @Test public void testRequestMonitorNonMvcRequestDontMonitor() throws Exception { when(webPlugin.isMonitorOnlySpringMvcRequests()).thenReturn(true); final MonitoredHttpRequest monitoredRequest = createMonitoredHttpRequest(nonMvcRequest); SpanContextInformation spanContext = requestMonitor.monitor(monitoredRequest); assertNull(spanContext.getOperationName()); assertNull(registry.getTimers().get(name("response_time_server").tag("request_name", "GET *.js").layer("All").build())); assertFalse(spanContext.isSampled()); } private SpanContextInformation anyRequestInformation() { return any(); } private MonitoredHttpRequest createMonitoredHttpRequest(HttpServletRequest request) throws Exception { final StatusExposingByteCountingServletResponse response = mock(StatusExposingByteCountingServletResponse.class); final FilterChain filterChain = mock(FilterChain.class); doAnswer(invocation -> { dispatcherServlet.service(request, response); return null; }).when(filterChain).doFilter(any(), any()); return Mockito.spy(new MonitoredHttpRequest(request, response, filterChain, configuration)); } @Test public void testGetRequestNameFromHandler() throws Exception { requestMonitor.monitorStart(createMonitoredHttpRequest(mvcRequest)); final SpanContextInformation spanContext = SpanContextInformation.getCurrent(); assertNotNull(spanContext); try { dispatcherServlet.service(mvcRequest, new MockHttpServletResponse()); } finally { requestMonitor.monitorStop(); } assertEquals("Test Get Request Name", spanContext.getOperationName()); } }