Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Jetty 12 server support #4753

Merged
merged 7 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ javax-inject = "1"
jaxb = "2.3.1"
jetty9 = "9.4.53.v20231009"
jetty11 = "11.0.16"
jetty12 = "12.0.3"
jetty12 = "12.0.6"
jersey2 = "2.41"
jersey3 = "3.0.11"
jmh = "1.37"
Expand Down
36 changes: 36 additions & 0 deletions micrometer-jetty12/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
description 'Micrometer instrumentation for Jetty 12'

// skip this module when building with jdk <17
if (!javaLanguageVersion.canCompileOrRun(17)) {
project.tasks.configureEach { task -> task.enabled = false }
}

dependencies {
api project(":micrometer-core")
api libs.jetty12Server

// Test sample project with SLFJ4 2.x / Logback 1.4
runtimeOnly(libs.logback14) {
version {
strictly libs.logback14.get().version
}
}
testRuntimeOnly(libs.logback14) {
version {
strictly libs.logback14.get().version
}
}

testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.assertj:assertj-core'
}

java {
targetCompatibility = 17
}

compileJava {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
options.release = 17
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2020 VMware, Inc.
*
* 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
*
* https://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 io.micrometer.jetty12;

import io.micrometer.core.annotation.Incubating;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.http.Outcome;
import org.eclipse.jetty.server.Request;

/**
* Default {@link JettyCoreRequestTagsProvider}.
*
* @author Joakim Erdfelt
* @since 1.11.0
*/
@Incubating(since = "1.11.0")
public class DefaultJettyCoreRequestTagsProvider implements JettyCoreRequestTagsProvider {

private static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN");

private static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN");

@Override
public Iterable<Tag> getTags(Request request) {
return Tags.of(method(request), status(request), outcome(request));
}

private Tag method(Request request) {
return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN;
}

private Tag status(Request request) {
if (request == null)
return STATUS_UNKNOWN;

Object status = request.getAttribute(TimedHandler.RESPONSE_STATUS_ATTRIBUTE);
if (status instanceof Integer statusInt)
return Tag.of("status", Integer.toString(statusInt));
return STATUS_UNKNOWN;
}

private Tag outcome(Request request) {
Outcome outcome = Outcome.UNKNOWN;
if (request != null) {
Object status = request.getAttribute(TimedHandler.RESPONSE_STATUS_ATTRIBUTE);
if (status instanceof Integer statusInt)
outcome = Outcome.forStatus(statusInt);
}
return outcome.asTag();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2022 VMware, Inc.
*
* 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
*
* https://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 io.micrometer.jetty12;

import io.micrometer.core.annotation.Incubating;
import io.micrometer.core.instrument.Tag;
import org.eclipse.jetty.server.Request;

/**
* Provides {@link Tag Tags} for Jetty Core request handling.
*
* @author Joakim Erdfelt
* @since 1.11.0
*/
@Incubating(since = "1.11.0")
@FunctionalInterface
public interface JettyCoreRequestTagsProvider {

/**
* Provides tags to be associated with metrics for the given {@code request}.
* @param request the request
* @return tags to associate with metrics for the request
*/
Iterable<Tag> getTags(Request request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2022 VMware, Inc.
*
* 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
*
* https://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 io.micrometer.jetty12;

import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.EventsHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Graceful;

import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;

/**
* Jetty 12 Metrics Handler.
*
* @author Jon Schneider
* @author Joakim Erdfelt
* @since 1.10.0
*/
public class TimedHandler extends EventsHandler implements Graceful {

private static final String SAMPLE_TIMER_ATTRIBUTE = "__micrometer_timer_sample";

private static final String SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE = "__micrometer_request_ltt_sample";

private static final String SAMPLE_HANDLER_LONG_TASK_TIMER_ATTRIBUTE = "__micrometer_handler_ltt_sample";

protected static final String RESPONSE_STATUS_ATTRIBUTE = "__micrometer_jetty_core_response_status";

private final MeterRegistry registry;

private final Iterable<Tag> tags;

private final JettyCoreRequestTagsProvider tagsProvider;

private final Shutdown shutdown = new Shutdown(this) {
@Override
public boolean isShutdownDone() {
return timerRequest.activeTasks() == 0;
}
};

/**
* Full Request LifeCycle (inside and out of Handlers)
*/
private final LongTaskTimer timerRequest;

/**
* How many Request are inside handle() calls.
*/
private final LongTaskTimer timerHandle;

public TimedHandler(MeterRegistry registry, Iterable<Tag> tags) {
this(registry, tags, new DefaultJettyCoreRequestTagsProvider());
}

public TimedHandler(MeterRegistry registry, Iterable<Tag> tags, JettyCoreRequestTagsProvider tagsProvider) {
this.registry = registry;
this.tags = tags;
this.tagsProvider = tagsProvider;

this.timerRequest = LongTaskTimer.builder("jetty.server.requests.open")
.description("Jetty requests that are currently in progress")
.tags(tags)
.register(registry);

this.timerHandle = LongTaskTimer.builder("jetty.server.handling.open")
.description("Jetty requests inside handle() calls")
.tags(tags)
.register(registry);
}

@Override
protected void onBeforeHandling(Request request) {
beginRequestTiming(request);
beginHandlerTiming(request);
super.onBeforeHandling(request);
}

@Override
protected void onRequestRead(Request request, Content.Chunk chunk) {
super.onRequestRead(request, chunk);
}

@Override
protected void onAfterHandling(Request request, boolean handled, Throwable failure) {
stopHandlerTiming(request);
super.onAfterHandling(request, handled, failure);
}

@Override
protected void onResponseBegin(Request request, int status, HttpFields headers) {
// If we see a status of 0 here, that mean the Handler hasn't set a status code.
request.setAttribute(RESPONSE_STATUS_ATTRIBUTE, status);
super.onResponseBegin(request, status, headers);
}

@Override
protected void onResponseWrite(Request request, boolean last, ByteBuffer content) {
super.onResponseWrite(request, last, content);
}

@Override
protected void onResponseWriteComplete(Request request, Throwable failure) {
super.onResponseWriteComplete(request, failure);
}

@Override
protected void onResponseTrailersComplete(Request request, HttpFields trailers) {
super.onResponseTrailersComplete(request, trailers);
}

@Override
protected void onComplete(Request request, Throwable failure) {
stopRequestTiming(request);
super.onComplete(request, failure);
}

private void beginRequestTiming(Request request) {
LongTaskTimer.Sample requestSample = timerRequest.start();
request.setAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE, requestSample);
}

private void stopRequestTiming(Request request) {
Timer.Sample sample = getTimerSample(request);
LongTaskTimer.Sample requestSample = (LongTaskTimer.Sample) request
.getAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE);
if (requestSample == null)
return; // timing complete

sample.stop(Timer.builder("jetty.server.requests")
.description("HTTP requests to the Jetty server")
.tags(tagsProvider.getTags(request))
.tags(tags)
.register(registry));

request.removeAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE);

requestSample.stop();
}

private void beginHandlerTiming(Request request) {
LongTaskTimer.Sample handlerSample = timerHandle.start();
request.setAttribute(SAMPLE_HANDLER_LONG_TASK_TIMER_ATTRIBUTE, handlerSample);
}

private void stopHandlerTiming(Request request) {
Timer.Sample sample = getTimerSample(request);
LongTaskTimer.Sample handlerSample = (LongTaskTimer.Sample) request
.getAttribute(SAMPLE_HANDLER_LONG_TASK_TIMER_ATTRIBUTE);
if (handlerSample == null)
return; // timing complete

sample.stop(Timer.builder("jetty.server.handling")
.description("Requests being processed by Jetty handlers")
.tags(tagsProvider.getTags(request))
.tags(tags)
.register(registry));

request.removeAttribute(SAMPLE_HANDLER_LONG_TASK_TIMER_ATTRIBUTE);

handlerSample.stop();
}

private Timer.Sample getTimerSample(Request request) {
return (Timer.Sample) request.getAttribute(SAMPLE_TIMER_ATTRIBUTE);
}

@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception {
Timer.Sample sample = Timer.start(registry);
request.setAttribute(SAMPLE_TIMER_ATTRIBUTE, sample);
return super.handle(request, response, callback);
}

@Override
protected void doStart() throws Exception {
shutdown.cancel();
super.doStart();
}

@Override
protected void doStop() throws Exception {
shutdown.cancel();
super.doStop();
}

@Override
public CompletableFuture<Void> shutdown() {
return shutdown.shutdown();
}

@Override
public boolean isShutdown() {
return shutdown.isShutdown();
}

protected Shutdown getShutdown() {
return shutdown;
}

}
Loading