Skip to content

Commit

Permalink
WIP on Micrometer Observation
Browse files Browse the repository at this point in the history
- adds a VaadinFilter mechanism to mimic an around-aspect
- adds a Micrometer Observation implementation
- configures VaadinServlet to automatically update the VaadinService with filters
- configures Spring to automatically update the VaadinServlet with filters
  • Loading branch information
marcingrzejszczak committed Aug 24, 2023
1 parent 25c6f2e commit d80ecec
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 89 deletions.
14 changes: 14 additions & 0 deletions flow-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@
<artifactId>commons-compress</artifactId>
<version>1.23.0</version>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-jakarta</artifactId>
<version>${micrometer.version}</version>
<optional>true</optional>
</dependency>

<!-- TESTING DEPENDENCIES -->
<dependency>
<groupId>com.vaadin</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.vaadin.flow.server;

import io.micrometer.common.lang.Nullable;
import io.micrometer.jakarta.instrument.binder.http.DefaultHttpJakartaServerServletRequestObservationConvention;
import io.micrometer.jakarta.instrument.binder.http.HttpJakartaServerServletRequestObservationContext;
import io.micrometer.jakarta.instrument.binder.http.HttpJakartaServerServletRequestObservationConvention;
import io.micrometer.jakarta.instrument.binder.http.JakartaHttpObservationDocumentation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletResponse;

/**
* Micrometer Observation {@link VaadinFilter} that will start
* observations around processing of a request.
*
* @author Marcin Grzejszczak
* @since 24.2
*/
public class ObservedVaadinFilter implements VaadinFilter {

private static final String SCOPE_ATTRIBUTE = ObservedVaadinFilter.class.getName() + ".scope";

private final ObservationRegistry observationRegistry;

private final HttpJakartaServerServletRequestObservationConvention convention;

public ObservedVaadinFilter(ObservationRegistry observationRegistry,
@Nullable HttpJakartaServerServletRequestObservationConvention convention) {
this.observationRegistry = observationRegistry;
this.convention = convention;
}

@Override
public void requestStart(VaadinRequest request, VaadinResponse response) {
if (request instanceof VaadinServletRequest servletRequest && response instanceof VaadinServletResponse servletResponse) {
HttpJakartaServerServletRequestObservationContext context = new HttpJakartaServerServletRequestObservationContext(servletRequest, servletResponse);
Observation observation = JakartaHttpObservationDocumentation.JAKARTA_SERVLET_SERVER_OBSERVATION.start(this.convention, DefaultHttpJakartaServerServletRequestObservationConvention.INSTANCE, () -> context, observationRegistry);
request.setAttribute(SCOPE_ATTRIBUTE, observation.openScope());
}
}

@Override
public void handleException(VaadinRequest request, VaadinResponse response, VaadinSession vaadinSession, Exception t) {
Observation.Scope scope = (Observation.Scope) request.getAttribute(SCOPE_ATTRIBUTE);
if (scope == null) {
return;
}
scope.getCurrentObservation().error(t);
}

@Override
public void requestEnd(VaadinRequest request, VaadinResponse response, VaadinSession session) {
Observation.Scope scope = (Observation.Scope) request.getAttribute(SCOPE_ATTRIBUTE);
if (scope == null) {
return;
}
scope.close();
Observation observation = scope.getCurrentObservation();
if (!observation.isNoop() && response instanceof HttpServletResponse httpServletResponse) {
HttpJakartaServerServletRequestObservationContext ctx = (HttpJakartaServerServletRequestObservationContext) observation.getContext();
ctx.setResponse(httpServletResponse);
}
observation.stop();
}
}
53 changes: 53 additions & 0 deletions flow-server/src/main/java/com/vaadin/flow/server/VaadinFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2000-2023 Vaadin Ltd.
*
* 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 com.vaadin.flow.server;

/**
* Used to provide an around-like aspect option around request processing.
*
* @author Marcin Grzejszczak
* @since 24.2
*/
public interface VaadinFilter {

/**
* Called when request is about to be processed.
* @param request request
* @param response response
*/
void requestStart(VaadinRequest request, VaadinResponse response);

/**
* Called when an exception occurred
* @param request request
* @param response response
* @param vaadinSession session
* @param t exception
*/
void handleException(VaadinRequest request,
VaadinResponse response, VaadinSession vaadinSession, Exception t);

/**
* Called in the finally block of processing a request. Will be called
* regardless of whether there was an exception or not.
* @param request request
* @param response response
* @param session session
*/
void requestEnd(VaadinRequest request, VaadinResponse response,
VaadinSession session);
}
76 changes: 29 additions & 47 deletions flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,6 @@

package com.vaadin.flow.server;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.di.DefaultInstantiator;
import com.vaadin.flow.di.Instantiator;
Expand All @@ -64,26 +29,30 @@
import com.vaadin.flow.router.RouteData;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.server.HandlerHelper.RequestType;
import com.vaadin.flow.server.communication.AtmospherePushConnection;
import com.vaadin.flow.server.communication.HeartbeatHandler;
import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
import com.vaadin.flow.server.communication.IndexHtmlResponse;
import com.vaadin.flow.server.communication.JavaScriptBootstrapHandler;
import com.vaadin.flow.server.communication.PwaHandler;
import com.vaadin.flow.server.communication.SessionRequestHandler;
import com.vaadin.flow.server.communication.StreamRequestHandler;
import com.vaadin.flow.server.communication.UidlRequestHandler;
import com.vaadin.flow.server.communication.WebComponentBootstrapHandler;
import com.vaadin.flow.server.communication.WebComponentProvider;
import com.vaadin.flow.server.communication.*;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.shared.JsonConstants;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.shared.communication.PushMode;

import elemental.json.Json;
import elemental.json.JsonException;
import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static java.nio.charset.StandardCharsets.UTF_8;

Expand Down Expand Up @@ -114,6 +83,8 @@ public abstract class VaadinService implements Serializable {
+ PushMode.class.getSimpleName() + "." + PushMode.DISABLED.name()
+ "." + SEPARATOR;

private List<VaadinFilter> vaadinFilters = new ArrayList<>();

/**
* Attribute name for telling
* {@link VaadinSession#valueUnbound(jakarta.servlet.http.HttpSessionBindingEvent)}
Expand Down Expand Up @@ -1433,6 +1404,7 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
}
setCurrentInstances(request, response);
request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime());
vaadinFilters.forEach(vaadinFilter -> vaadinFilter.requestStart(request, response));
}

/**
Expand All @@ -1449,6 +1421,7 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
*/
public void requestEnd(VaadinRequest request, VaadinResponse response,
VaadinSession session) {
vaadinFilters.forEach(vaadinFilter -> vaadinFilter.requestEnd(request, response, session));
if (session != null) {
assert VaadinSession.getCurrent() == session;
session.lock();
Expand Down Expand Up @@ -1544,6 +1517,7 @@ private void handleExceptionDuringRequest(VaadinRequest request,
vaadinSession.lock();
}
try {
vaadinFilters.forEach(vaadinFilter -> vaadinFilter.handleException(request, response, vaadinSession, t));
if (vaadinSession != null) {
vaadinSession.getErrorHandler().error(new ErrorEvent(t));
}
Expand Down Expand Up @@ -2374,6 +2348,14 @@ public static String getCsrfTokenAttributeName() {
+ ApplicationConstants.CSRF_TOKEN;
}

public List<VaadinFilter> getVaadinFilters() {
return vaadinFilters;
}

public void setVaadinFilters(List<VaadinFilter> vaadinFilters) {
this.vaadinFilters = vaadinFilters;
}

private void doSetClassLoader() {
final String classLoaderName = getDeploymentConfiguration() == null
? null
Expand Down
31 changes: 17 additions & 14 deletions flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,6 @@
*/
package com.vaadin.flow.server;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.function.DeploymentConfiguration;
Expand All @@ -37,14 +24,20 @@
import com.vaadin.flow.server.HandlerHelper.RequestType;
import com.vaadin.flow.server.startup.ApplicationConfiguration;
import com.vaadin.flow.shared.JsonConstants;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
* The main servlet, which handles all incoming requests to the application.
Expand Down Expand Up @@ -72,6 +65,8 @@ public class VaadinServlet extends HttpServlet {

private static List<Runnable> whenFrontendMappingAvailable = new ArrayList<>();

private List<VaadinFilter> vaadinFilters = new ArrayList<>();

/**
* Called by the servlet container to indicate to a servlet that the servlet
* is being placed into service.
Expand Down Expand Up @@ -647,6 +642,14 @@ private VaadinServletContext initializeContext() {
return vaadinServletContext;
}

public List<VaadinFilter> getVaadinFilters() {
return vaadinFilters;
}

public void setVaadinFilters(List<VaadinFilter> vaadinFilters) {
this.vaadinFilters = vaadinFilters;
}

/**
* For internal use only.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,19 @@

package com.vaadin.flow.server;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.DevModeHandlerManager;
import com.vaadin.flow.server.communication.FaviconHandler;
import com.vaadin.flow.server.communication.IndexHtmlRequestHandler;
import com.vaadin.flow.server.communication.PushRequestHandler;
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
import com.vaadin.flow.shared.ApplicationConstants;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.net.MalformedURLException;
Expand All @@ -27,18 +37,6 @@
import java.util.Objects;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.DevModeHandlerManager;
import com.vaadin.flow.server.communication.FaviconHandler;
import com.vaadin.flow.server.communication.IndexHtmlRequestHandler;
import com.vaadin.flow.server.communication.PushRequestHandler;
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
import com.vaadin.flow.shared.ApplicationConstants;

/**
* A service implementation connected to a {@link VaadinServlet}.
*
Expand Down Expand Up @@ -66,6 +64,7 @@ public VaadinServletService(VaadinServlet servlet,
DeploymentConfiguration deploymentConfiguration) {
super(deploymentConfiguration);
this.servlet = servlet;
setVaadinFilters(servlet.getVaadinFilters());
}

/**
Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
<jaxb.version>4.0.3</jaxb.version>
<guava.version>32.1.2-jre</guava.version>
<jakarta.websocket.version>2.1.1</jakarta.websocket.version>
<micrometer.version>1.12.0-SNAPSHOT</micrometer.version>
<micrometer-tracing.version>1.2.0-SNAPSHOT</micrometer-tracing.version>

<!-- Plugins -->
<frontend.maven.plugin.version>1.5</frontend.maven.plugin.version>
Expand Down
Loading

0 comments on commit d80ecec

Please sign in to comment.