diff --git a/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledThrowableHandler.java b/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledThrowableHandler.java new file mode 100644 index 0000000..919b193 --- /dev/null +++ b/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledThrowableHandler.java @@ -0,0 +1,29 @@ +package org.commonjava.util.gateway.exception.mapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This default mapper allows any exception to be captured ensuring a ResponseContextFilter is always called. + */ +@Provider +public class UnhandledThrowableHandler + implements ExceptionMapper//, RestProvider +{ + public Response toResponse( Throwable exception ) + { + Logger logger = LoggerFactory.getLogger( getClass() ); + logger.error( "Unhandled exception: " + exception.getMessage(), exception ); + + return Response.status( Response.Status.INTERNAL_SERVER_ERROR ) + .entity( ExceptionUtils.getStackTrace( exception ) ) + .type( MediaType.TEXT_PLAIN ) + .build(); + } +} diff --git a/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledWebApplicationExceptionHandler.java b/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledWebApplicationExceptionHandler.java new file mode 100644 index 0000000..128c8a8 --- /dev/null +++ b/src/main/java/org/commonjava/util/gateway/exception/mapper/UnhandledWebApplicationExceptionHandler.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2011-2021 Red Hat, Inc. (https://github.com/Commonjava/indy) + * + * 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.commonjava.util.gateway.exception.mapper; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This WebApplicationException handler will check for a HTTP response status + * code (SC) in the exception and ensure the client will receive the corresponding SC code. + */ +public class UnhandledWebApplicationExceptionHandler + implements ExceptionMapper +{ + @Override + public Response toResponse( WebApplicationException exception ) + { + Response response = null; + if ( exception.getResponse() != null && exception.getResponse().getStatusInfo() != null ) + { + logger.error( "Unhandled exception: " + exception.getMessage(), exception ); + + response = Response.status( exception.getResponse().getStatusInfo() ) + .entity( ExceptionUtils.getStackTrace( exception ) ) + .type( MediaType.TEXT_PLAIN ) + .build(); + } + else + { + response = new UnhandledThrowableHandler().toResponse( exception ); + } + return response; + } + + private static final Logger logger = LoggerFactory + .getLogger( UnhandledWebApplicationExceptionHandler.class.getName() ); + +} diff --git a/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderFilter.java b/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderFilter.java new file mode 100644 index 0000000..b9b23b8 --- /dev/null +++ b/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderFilter.java @@ -0,0 +1,94 @@ +package org.commonjava.util.gateway.metrics.jfr; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +import org.commonjava.util.gateway.metrics.jfr.events.JaxRSEvent; +import org.jboss.resteasy.core.ResourceMethodInvoker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Purpose of this filter is to generate events for JaxRS calls when profiling is enabled. + * + */ +@Provider +public class FlightRecorderFilter implements ContainerRequestFilter, ContainerResponseFilter +{ + @Override + public void filter( ContainerRequestContext requestContext ) throws IOException + { + logger.trace("processing request context"); + final JaxRSEvent event = new JaxRSEvent(); + final boolean isEnabled = event.isEnabled(); + if ( !isEnabled ) + { + return; + } + event.begin(); + requestContext.setProperty( JaxRSEvent.NAME, event ); + } + + @Override + public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext ) + throws IOException + { + logger.trace("processing response context"); + if ( requestContext == null ) + { + logger.error( "request context is null" ); + return; + } + Object prop = requestContext.getProperty( JaxRSEvent.NAME ); + if ( prop == null ) + { + return; + } + JaxRSEvent event = ( JaxRSEvent ) prop; + if ( !event.isEnabled() ) + { + return; + } + + event.end(); + + if ( event.shouldCommit() ) + { + event.method = requestContext.getMethod(); + event.mediaType = String.valueOf ( requestContext.getMediaType() ) ; + event.length = requestContext.getLength(); + event.methodFrameName = getMethodName( requestContext ); + event.path = requestContext.getUriInfo().getPath(); + event.responseLength = responseContext.getLength(); + event.status = responseContext.getStatus(); + + event.commit(); + } + } + + private String getMethodName( ContainerRequestContext context ) + { + Object p = context.getProperty( METHOD_NAME ); + if ( p == null ) + { + return ""; + } + if ( p instanceof ResourceMethodInvoker ) + { + ResourceMethodInvoker invoker = (ResourceMethodInvoker) p; + return invoker.getMethod().getName(); + } + else + { + return ""; + } + } + + private static final String METHOD_NAME = ResourceMethodInvoker.class.getName(); + private static final Logger logger = LoggerFactory.getLogger( FlightRecorderFilter.class.getName() ); +} diff --git a/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderMetrics.java b/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderMetrics.java new file mode 100644 index 0000000..74ef4f1 --- /dev/null +++ b/src/main/java/org/commonjava/util/gateway/metrics/jfr/FlightRecorderMetrics.java @@ -0,0 +1,18 @@ +package org.commonjava.util.gateway.metrics.jfr; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; + +import org.commonjava.util.gateway.metrics.jfr.events.JaxRSEvent; + +import io.quarkus.runtime.StartupEvent; +import jdk.jfr.FlightRecorder; + +@ApplicationScoped +public class FlightRecorderMetrics +{ + public void registerEvents( @Observes StartupEvent e ) + { + FlightRecorder.register( JaxRSEvent.class ); + } +} diff --git a/src/main/java/org/commonjava/util/gateway/metrics/jfr/events/JaxRSEvent.java b/src/main/java/org/commonjava/util/gateway/metrics/jfr/events/JaxRSEvent.java new file mode 100644 index 0000000..28d8608 --- /dev/null +++ b/src/main/java/org/commonjava/util/gateway/metrics/jfr/events/JaxRSEvent.java @@ -0,0 +1,42 @@ +package org.commonjava.util.gateway.metrics.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.DataAmount; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name ( JaxRSEvent.NAME ) +@Label ( "Invocation" ) +@Category ( "JaxRS" ) +@Description ( "JaxRS invocation event" ) +@StackTrace ( false ) +public class JaxRSEvent extends Event +{ + public static final String NAME = "o.c.u.g.m.j.e.JaxRSEvent"; + + @Label ( "Resource Method" ) + public String method; + + @Label ( "Media type" ) + public String mediaType; + + @Label ( "Java method" ) + public String methodFrameName; + + @Label ( "Path" ) + public String path; + + @Label ( "Request length" ) + @DataAmount + public int length; + + @Label ( "Response length" ) + @DataAmount + public int responseLength; + + @Label ( "Status" ) + public int status; +}