-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Jetty 12 server instrumentation (#4753)
Ports the existing Jetty server instrumentation to support Jetty 12. This decouples the instrumentation from Servlet and instead depends only on Jetty core classes. Due to backward incompatibility with previous major versions of Jetty, this instrumentation needs to be in a new module micrometer-jetty12. Resolves gh-4261
- Loading branch information
Showing
10 changed files
with
648 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
67 changes: 67 additions & 0 deletions
67
...eter-jetty12/src/main/java/io/micrometer/jetty12/DefaultJettyCoreRequestTagsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
|
||
} |
40 changes: 40 additions & 0 deletions
40
micrometer-jetty12/src/main/java/io/micrometer/jetty12/JettyCoreRequestTagsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
} |
222 changes: 222 additions & 0 deletions
222
micrometer-jetty12/src/main/java/io/micrometer/jetty12/TimedHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
Oops, something went wrong.