Skip to content

Commit

Permalink
Merge pull request newrelic#538 from newrelic/spring-security
Browse files Browse the repository at this point in the history
Spring security
  • Loading branch information
jasonjkeller authored Nov 17, 2021
2 parents 1f4607e + 49f8c10 commit 7980165
Show file tree
Hide file tree
Showing 19 changed files with 222 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class TokenLinkingSubscriber<T> implements CoreSubscriber<T> {
public TokenLinkingSubscriber(Subscriber<? super T> subscriber, Context ctx) {
this.subscriber = subscriber;
this.context = ctx;
// newrelic-token is added by spring-webflux-5.1 instrumentation
// newrelic-token is added by spring-webflux-5.1 instrumentation of ServerWebExchange
this.token = ctx.getOrDefault("newrelic-token", null);
}

Expand Down
36 changes: 36 additions & 0 deletions instrumentation/netty-reactor-0.8.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Reactor Netty Instrumentation

Instrumentation for Reactor Netty server and also some widely used Reactor Core library code.

This module is largely responsible for instrumenting the Reactor Core library to facilitate the passing, retrieval,
and linking of `Tokens` across contexts to tie asynchronous threads together for individual `Transactions`.

This instrumentation is dependent on other instrumentation modules to start a `Transaction`.
Typically, the `netty-n.n` modules work with this instrumentation and will start a `Transaction` (see `NettyDispatcher#channelRead`).

Most commonly this instrumentation comes into play with SpringBoot usage, in which case the `spring` and `spring-webflux`
instrumentation modules also apply and should result in `Transactions` being renamed after the Spring controller.

## Key Components

* `TokenLinkingSubscriber`
Implementation of a `reactor.core.CoreSubscriber` (a `Context` aware subscriber) that can be added as
a lifecycle hook on `Flux`/`Mono` operators to propagate, retrieve, and link `Tokens` across async contexts. This is done in various places as follows:

```java
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}
```

* `Schedulers_Instrumentation` and `HttpTrafficHandler_Instrumentation`
Both of these classes serve as entry points to add the `TokenLinkingSubscriber` sub-hook.

* Scheduler `Task`s
Reactor Core Scheduler tasks that execute on asynchronous threads. These are instrumented as points to link `Tokens`.

## Troubleshooting

In cases where a `Transaction` gets named `/NettyDispatcher` (or named after a security `Filter`) it usually indicates that context was lost somewhere in
reactor code and that activity on threads where other instrumentation would typically apply could not be linked to the originating `Transaction` thread.
Figuring out how to propagate and link a `Token` across the threads should resolve the issue.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
import java.util.function.BiFunction;
import java.util.function.Function;

// Based on OpenTelemetry code
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/TracingSubscriber.java
/**
* Implementation of a reactor.core.CoreSubscriber (a Context aware subscriber) that can be added as
* a lifecycle hook on Flux/Mono operators to propagate, retrieve, and link Tokens across async contexts.
*
* Based on OpenTelemetry code:
* https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/TracingSubscriber.java
* @param <T>
*/
public class TokenLinkingSubscriber<T> implements CoreSubscriber<T> {
private final Token token;
private final Subscriber<? super T> subscriber;
Expand All @@ -27,7 +33,7 @@ public class TokenLinkingSubscriber<T> implements CoreSubscriber<T> {
public TokenLinkingSubscriber(Subscriber<? super T> subscriber, Context ctx) {
this.subscriber = subscriber;
this.context = ctx;
// newrelic-token is added by spring-webflux instrumentation
// newrelic-token is added by spring-webflux instrumentation of ServerWebExchange
this.token = ctx.getOrDefault("newrelic-token", null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

@Weave(originalName = "reactor.core.publisher.Hooks")
public abstract class Hooks_Instrumentation {

/*
* Note that sub-hooks are cumulative. We want to avoid setting the same sub-hooks
* more than once, so we set this boolean to true the first time we set a sub-hook.
* if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { Hooks.onEachOperator(...) }
*/
@NewField
public static AtomicBoolean instrumented = new AtomicBoolean(false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.InstantPeriodicWorkerTask")
final class InstantPeriodicWorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.PeriodicWorkerTask")
final class PeriodicWorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
final class SchedulerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler via Mono::publishOn
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2021 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package reactor.core.scheduler;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import com.nr.instrumentation.reactor.netty.TokenLinkingSubscriber;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Hooks_Instrumentation;

import static com.nr.instrumentation.reactor.netty.TokenLinkingSubscriber.tokenLift;

@Weave(type = MatchType.BaseClass, originalName = "reactor.core.scheduler.Schedulers")
public abstract class Schedulers_Instrumentation {

@Weave(type = MatchType.ExactClass, originalName = "reactor.core.scheduler.Schedulers$CachedScheduler")
static class CachedScheduler {
final Scheduler cached;
final String key;

CachedScheduler(String key, Scheduler cached) {
/*
* Add tokenLift hook if it hasn't already been added. This allows for tokens to be retrieved from
* the current context and linked across threads at various points of the Flux/Mono lifecycle.
*
* When using Netty Reactor with SpringBoot this hook will be added by the HttpTrafficHandler_Instrumentation
* but when using other embedded web servers (e.g. Tomcat, Jetty, Undertow) the HttpTrafficHandler class
* doesn't get loaded and thus the hook isn't added. This ensures that the hook is added in a common code
* path before any Scheduler Tasks are spun off on new threads.
*/
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}

this.cached = Weaver.callOriginal();
this.key = Weaver.callOriginal();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.WorkerTask")
final class WorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
@Weave(originalName = "reactor.netty.http.server.HttpTrafficHandler")
class HttpTrafficHandler_Instrumentation {
public void channelRead(ChannelHandlerContext ctx, Object msg) {

/*
* Add tokenLift hook if it hasn't already been added. This allows for tokens to be retrieved from
* the current context and linked across threads at various points of the Flux/Mono lifecycle.
*
* This hook will only be added when using Netty Reactor with SpringBoot. When using other embedded web
* servers (e.g. Tomcat, Jetty, Undertow) the Schedulers_Instrumentation class will add the hook.
*/
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}
Expand Down
36 changes: 36 additions & 0 deletions instrumentation/netty-reactor-0.9.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Reactor Netty Instrumentation

Instrumentation for Reactor Netty server and also some widely used Reactor Core library code.

This module is largely responsible for instrumenting the Reactor Core library to facilitate the passing, retrieval,
and linking of `Tokens` across contexts to tie asynchronous threads together for individual `Transactions`.

This instrumentation is dependent on other instrumentation modules to start a `Transaction`.
Typically, the `netty-n.n` modules work with this instrumentation and will start a `Transaction` (see `NettyDispatcher#channelRead`).

Most commonly this instrumentation comes into play with SpringBoot usage, in which case the `spring` and `spring-webflux`
instrumentation modules also apply and should result in `Transactions` being renamed after the Spring controller.

## Key Components

* `TokenLinkingSubscriber`
Implementation of a `reactor.core.CoreSubscriber` (a `Context` aware subscriber) that can be added as
a lifecycle hook on `Flux`/`Mono` operators to propagate, retrieve, and link `Tokens` across async contexts. This is done in various places as follows:

```java
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}
```

* `Schedulers_Instrumentation` and `HttpTrafficHandler_Instrumentation`
Both of these classes serve as entry points to add the `TokenLinkingSubscriber` sub-hook.

* Scheduler `Task`s
Reactor Core Scheduler tasks that execute on asynchronous threads. These are instrumented as points to link `Tokens`.

## Troubleshooting

In cases where a `Transaction` gets named `/NettyDispatcher` (or named after a security `Filter`) it usually indicates that context was lost somewhere in
reactor code and that activity on threads where other instrumentation would typically apply could not be linked to the originating `Transaction` thread.
Figuring out how to propagate and link a `Token` across the threads should resolve the issue.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
import java.util.function.BiFunction;
import java.util.function.Function;

// Based on OpenTelemetry code
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/TracingSubscriber.java
/**
* Implementation of a reactor.core.CoreSubscriber (a Context aware subscriber) that can be added as
* a lifecycle hook on Flux/Mono operators to propagate, retrieve, and link Tokens across async contexts.
*
* Based on OpenTelemetry code:
* https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/TracingSubscriber.java
* @param <T>
*/
public class TokenLinkingSubscriber<T> implements CoreSubscriber<T> {
private final Token token;
private final Subscriber<? super T> subscriber;
Expand All @@ -27,7 +33,7 @@ public class TokenLinkingSubscriber<T> implements CoreSubscriber<T> {
public TokenLinkingSubscriber(Subscriber<? super T> subscriber, Context ctx) {
this.subscriber = subscriber;
this.context = ctx;
// newrelic-token is added by spring-webflux instrumentation
// newrelic-token is added by spring-webflux instrumentation of ServerWebExchange
this.token = ctx.getOrDefault("newrelic-token", null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

@Weave(originalName = "reactor.core.publisher.Hooks")
public abstract class Hooks_Instrumentation {

/*
* Note that sub-hooks are cumulative. We want to avoid setting the same sub-hooks
* more than once, so we set this boolean to true the first time we set a sub-hook.
* if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { Hooks.onEachOperator(...) }
*/
@NewField
public static AtomicBoolean instrumented = new AtomicBoolean(false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.InstantPeriodicWorkerTask")
final class InstantPeriodicWorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.PeriodicWorkerTask")
final class PeriodicWorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
final class SchedulerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler via Mono::publishOn
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2021 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package reactor.core.scheduler;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import com.nr.instrumentation.reactor.netty.TokenLinkingSubscriber;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Hooks_Instrumentation;

import static com.nr.instrumentation.reactor.netty.TokenLinkingSubscriber.tokenLift;

@Weave(type = MatchType.BaseClass, originalName = "reactor.core.scheduler.Schedulers")
public abstract class Schedulers_Instrumentation {

@Weave(type = MatchType.ExactClass, originalName = "reactor.core.scheduler.Schedulers$CachedScheduler")
static class CachedScheduler {
final Scheduler cached;
final String stringRepresentation;

CachedScheduler(String key, Scheduler cached) {
/*
* Add tokenLift hook if it hasn't already been added. This allows for tokens to be retrieved from
* the current context and linked across threads at various points of the Flux/Mono lifecycle.
*
* When using Netty Reactor with SpringBoot this hook will be added by the HttpTrafficHandler_Instrumentation
* but when using other embedded web servers (e.g. Tomcat, Jetty, Undertow) the HttpTrafficHandler class
* doesn't get loaded and thus the hook isn't added. This ensures that the hook is added in a common code
* path before any Scheduler Tasks are spun off on new threads.
*/
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}

this.cached = Weaver.callOriginal();
this.stringRepresentation = Weaver.callOriginal();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@Weave(originalName = "reactor.core.scheduler.WorkerTask")
final class WorkerTask_Instrumentation {

// We need to be able to link the Token here when executing on a supplied Scheduler
// A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator
@Trace(async = true, excludeFromTransactionTrace = true)
public Void call() {
return Weaver.callOriginal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
@Weave(originalName = "reactor.netty.http.server.HttpTrafficHandler")
class HttpTrafficHandler_Instrumentation {
public void channelRead(ChannelHandlerContext ctx, Object msg) {

/*
* Add tokenLift hook if it hasn't already been added. This allows for tokens to be retrieved from
* the current context and linked across threads at various points of the Flux/Mono lifecycle.
*
* This hook will only be added when using Netty Reactor with SpringBoot. When using other embedded web
* servers (e.g. Tomcat, Jetty, Undertow) the Schedulers_Instrumentation class will add the hook.
*/
if (!Hooks_Instrumentation.instrumented.getAndSet(true)) {
Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift());
}
Expand Down

0 comments on commit 7980165

Please sign in to comment.