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

Support micrometer context-propagation #5577

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
faffdd6
Support micrometer context-propagation : Skeleton code
chickenchickenlove Apr 8, 2024
0e1fd49
Merge branch 'main' into 240408-context-propagation
chickenchickenlove Apr 8, 2024
48b4039
Resolve lint error
chickenchickenlove Apr 8, 2024
65aa41b
Add java docs
chickenchickenlove Apr 10, 2024
1c3cda0
Add java docs and modify unit test
chickenchickenlove Apr 10, 2024
8030e8f
Apply comment
chickenchickenlove Apr 10, 2024
46cd890
remove ContextPropagation from boot3-autoconfigure
chickenchickenlove Apr 12, 2024
67a4f14
Move RequestContextAccessor to core module.
chickenchickenlove Apr 12, 2024
ae022ad
remove useless dependency
chickenchickenlove Apr 12, 2024
c633eaa
add dependencies
chickenchickenlove Apr 12, 2024
8710f1d
resolve java doc error
chickenchickenlove Apr 12, 2024
64cb196
fix test fail
chickenchickenlove Apr 12, 2024
9b32927
remove unused imported
chickenchickenlove Apr 12, 2024
6d4762a
Add test cases
chickenchickenlove Apr 12, 2024
4209756
Add test case
chickenchickenlove Apr 13, 2024
747fa36
Update core/src/main/java/com/linecorp/armeria/common/RequestContextA…
chickenchickenlove Apr 17, 2024
83a84b0
Update core/src/main/java/com/linecorp/armeria/common/RequestContextA…
chickenchickenlove Apr 17, 2024
3bdbd3e
Update core/src/main/java/com/linecorp/armeria/common/RequestContextA…
chickenchickenlove Apr 17, 2024
767bd9a
Fix lint error and test case
chickenchickenlove Apr 17, 2024
88b1475
resolve lint error
chickenchickenlove Apr 17, 2024
4a3ebfc
remove RequestContextPropagationHooks.
chickenchickenlove Apr 21, 2024
6a3d4f4
remove init context for Stepverifier.
chickenchickenlove Apr 21, 2024
7d2970d
Add test cases for contextCapture().
chickenchickenlove Apr 21, 2024
24c6f38
Should not ctx in main thread.
chickenchickenlove Apr 21, 2024
4a00814
apply review
chickenchickenlove Apr 22, 2024
f23f670
add @UnstableAPI and remove whitespace.
chickenchickenlove Apr 22, 2024
eb0c71e
Update core/src/test/java/com/linecorp/armeria/common/RequestContextA…
chickenchickenlove Apr 23, 2024
8a46ca5
apply review
chickenchickenlove Apr 23, 2024
aca31f8
Merge branch 'main' into 240408-context-propagation
chickenchickenlove Apr 23, 2024
9cd60e1
apply review
chickenchickenlove Apr 23, 2024
4b5015f
add test case
chickenchickenlove Apr 23, 2024
90f76a7
fix lint error
chickenchickenlove Apr 24, 2024
230a664
Update core/src/main/java/com/linecorp/armeria/internal/common/Reques…
chickenchickenlove Apr 24, 2024
3b9de29
Update core/src/test/java/com/linecorp/armeria/internal/common/Reques…
chickenchickenlove Apr 24, 2024
890a725
apply review
chickenchickenlove Apr 24, 2024
6a3e46e
Update core/src/main/java/com/linecorp/armeria/internal/common/Reques…
chickenchickenlove May 8, 2024
e80117c
apply review
chickenchickenlove May 8, 2024
12a2929
Merge branch 'main' into 240408-context-propagation
trustin Jun 5, 2024
2ca6b1b
Merge branch 'main' into 240408-context-propagation
jrhee17 Jun 17, 2024
79f16af
Update reactor3/src/test/java/com/linecorp/armeria/common/reactor3/Re…
chickenchickenlove Jun 20, 2024
fb2db7d
Update reactor3/src/test/java/com/linecorp/armeria/common/reactor3/Re…
chickenchickenlove Jun 20, 2024
f087912
Update core/src/main/java/com/linecorp/armeria/internal/common/Reques…
chickenchickenlove Jun 20, 2024
1e6cd9b
Add validation callback for context-propagation.
chickenchickenlove Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ dependencies {
optionalApi libs.micrometer.prometheus
optionalApi libs.dropwizard.metrics.core
optionalApi libs.prometheus
implementation 'io.micrometer:context-propagation:1.1.1'
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved

// Netty
api libs.netty.transport
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2020 LINE Corporation
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
*
* LINE Corporation licenses this file to you 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 com.linecorp.armeria.common;
trustin marked this conversation as resolved.
Show resolved Hide resolved

import org.reactivestreams.Subscription;

import com.linecorp.armeria.internal.common.RequestContextUtil;

import io.micrometer.context.ContextRegistry;
import io.micrometer.context.ContextSnapshot;
import io.micrometer.context.ContextSnapshot.Scope;
import io.micrometer.context.ThreadLocalAccessor;

/**
* This class works with the
* <a href="https://docs.micrometer.io/context-propagation/reference/index.html">
* Context-propagation</a> library and keep the {@link RequestContext} during
* <a href="https://github.com/reactor/reactor-core">Reactor</a> operations.
* Get the {@link RequestContextAccessor} to register it to the {@link ContextRegistry}.
* Then, {@link ContextRegistry} will use {@link RequestContextAccessor} to
* propagate context during the
* <a href="https://github.com/reactor/reactor-core">Reactor</a> operations
* so that you can get the context using {@link RequestContext#current()}.
* However, please note that you should include Mono#contextWrite(ContextView) or
* Flux#contextWrite(ContextView) to end of the Reactor codes.
* If not, {@link RequestContext} will not be keep during Reactor Operation.
*/
public final class RequestContextAccessor implements ThreadLocalAccessor<RequestContext> {
minwoox marked this conversation as resolved.
Show resolved Hide resolved
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
trustin marked this conversation as resolved.
Show resolved Hide resolved

private static final String KEY = RequestContextAccessor.class.getName();

/**
* The value which obtained through {@link RequestContextAccessor},
* will be stored in the Context under this {@code KEY}.
* This method will be called by {@link ContextSnapshot} internally.
*/
@Override
public Object key() {
return KEY;
}

/**
* The value which obtained through {@link RequestContextAccessor},
* will be stored in the Context under this {@code KEY}.
* User can use this method to register {@link RequestContext} to
* Reactor Context.
*/

chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
public static String accessorKey() {
minwoox marked this conversation as resolved.
Show resolved Hide resolved
return KEY;
}

/**
* {@link ContextSnapshot} will call this method during the execution
* of lambda functions in {@link ContextSnapshot#wrap(Runnable)},
* as well as during Mono#subscribe(), Flux#subscribe(),
* {@link Subscription#request(long)}, and CoreSubscriber#onSubscribe(Subscription).
* Following these calls, {@link ContextSnapshot#setThreadLocals()} is
* invoked to restore the state of {@link RequestContextStorage}.
* Furthermore, at the end of these methods, {@link Scope#close()} is executed
* to revert the {@link RequestContextStorage} to its original state.
*/
@Override
public RequestContext getValue() {
return RequestContextUtil.get();
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* {@link ContextSnapshot} will call this method during the execution
* of lambda functions in {@link ContextSnapshot#wrap(Runnable)},
* as well as during Mono#subscribe(), Flux#subscribe(),
* {@link Subscription#request(long)}, and CoreSubscriber#onSubscribe(Subscription).
* Following these calls, {@link ContextSnapshot#setThreadLocals()} is
* invoked to restore the state of {@link RequestContextStorage}.
* Furthermore, at the end of these methods, {@link Scope#close()} is executed
* to revert the {@link RequestContextStorage} to its original state.
*/
@Override
public void setValue(RequestContext value) {
RequestContextUtil.getAndSet(value);
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* This method will be called at the start of {@link ContextSnapshot.Scope} and
* the end of {@link ContextSnapshot.Scope}. If reactor Context does not
* contains {@link RequestContextAccessor#KEY}, {@link ContextSnapshot} will use
* this method to remove the value from {@link ThreadLocal}.
* Please note that {@link RequestContextUtil#pop()} return {@link AutoCloseable} instance,
* but it is not used in `Try with Resources` syntax. this is because {@link ContextSnapshot.Scope}
* will handle the {@link AutoCloseable} instance returned by {@link RequestContextUtil#pop()}.
*/
@Override
@SuppressWarnings("MustBeClosedChecker")
public void setValue() {
RequestContextUtil.pop();
}

/**
* {@link ContextSnapshot} will call this method during the execution
* of lambda functions in {@link ContextSnapshot#wrap(Runnable)},
* as well as during Mono#subscribe(), Flux#subscribe(),
* {@link Subscription#request(long)}, and CoreSubscriber#onSubscribe(Subscription).
* Following these calls, {@link ContextSnapshot#setThreadLocals()} is
* invoked to restore the state of {@link RequestContextStorage}.
* Furthermore, at the end of these methods, {@link Scope#close()} is executed
* to revert the {@link RequestContextStorage} to its original state.
*/
@Override
public void restore(RequestContext previousValue) {
RequestContextUtil.getAndSet(previousValue);
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* This method will be called at the start of {@link ContextSnapshot.Scope} and
* the end of {@link ContextSnapshot.Scope}. If reactor Context does not
* contains {@link RequestContextAccessor#KEY}, {@link ContextSnapshot} will use
* this method to remove the value from {@link ThreadLocal}.
* Please note that {@link RequestContextUtil#pop()} return {@link AutoCloseable} instance,
* but it is not used in `Try with Resources` syntax. this is because {@link ContextSnapshot.Scope}
* will handle the {@link AutoCloseable} instance returned by {@link RequestContextUtil#pop()}.
*/
@Override
@SuppressWarnings("MustBeClosedChecker")
public void restore() {
RequestContextUtil.pop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.linecorp.armeria.common.RequestContextAccessor
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright 2019 LINE Corporation
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
*
* LINE Corporation licenses this file to you 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.
*/
/*
* Copyright 2012 The Netty Project
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
*
* The Netty Project licenses this file to you 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.linecorp.armeria.common;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.internal.common.RequestContextUtil;

import io.micrometer.context.ContextRegistry;
import io.micrometer.context.ThreadLocalAccessor;

class RequestContextAccessorTest {
trustin marked this conversation as resolved.
Show resolved Hide resolved

/* Should clean up on RequestContext.
* because some test case does not clean up on RequestContext,
* and it will affect other tests if test are executed parallely.
*/

@AfterEach
@SuppressWarnings("MustBeClosedChecker")
void cleanUp() {
RequestContextUtil.pop();
minwoox marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
void should_be_loaded_by_SPI() {
final ContextRegistry ctxRegistry = ContextRegistry.getInstance();
final List<ThreadLocalAccessor<?>> threadLocalAccessors = ctxRegistry.getThreadLocalAccessors();

assertThat(threadLocalAccessors.size()).isGreaterThan(1);
assertThat(threadLocalAccessors).hasAtLeastOneElementOfType(RequestContextAccessor.class);
}

@Test
void should_return_expected_key() {
// Given
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();
final String expectedValue = RequestContextAccessor.class.getName();

// When
final Object result = reqCtxAccessor.key();

// Then
assertThat(result).isEqualTo(expectedValue);
}

@Test
void should_success_set() {
// Given
final ClientRequestContext ctx = newContext();
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();

// When
reqCtxAccessor.setValue(ctx);

// Then
final RequestContext currentCtx = RequestContext.current();
assertThat(currentCtx).isEqualTo(ctx);
}

@Test
void should_throw_NPE_when_set_null() {
// Given
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();

// When + Then
Assertions.assertThatThrownBy(() -> reqCtxAccessor.setValue(null))
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
.isInstanceOf(NullPointerException.class);
}

@Test
void should_be_null_when_setValue() {
// Given
final ClientRequestContext ctx = newContext();
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();
reqCtxAccessor.setValue(ctx);

// When
reqCtxAccessor.setValue();

// Then
final RequestContext reqCtx = RequestContext.currentOrNull();
assertThat(reqCtx).isNull();
}

@Test
void should_be_restore_original_state_when_restore() {
// Given
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();
final ClientRequestContext previousCtx = newContext();
final ClientRequestContext currentCtx = newContext();
reqCtxAccessor.setValue(currentCtx);

// When
reqCtxAccessor.restore(previousCtx);

// Then
final RequestContext reqCtx = RequestContext.currentOrNull();
assertThat(reqCtx).isNotNull();
assertThat(reqCtx).isEqualTo(previousCtx);
}

@Test
void should_be_null_when_restore() {
// Given
final RequestContextAccessor reqCtxAccessor = new RequestContextAccessor();
final ClientRequestContext currentCtx = newContext();
reqCtxAccessor.setValue(currentCtx);

// When
reqCtxAccessor.restore();

// Then
final RequestContext reqCtx = RequestContext.currentOrNull();
assertThat(reqCtx).isNull();
}

static ClientRequestContext newContext() {
return ClientRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/"))
.build();
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions reactor3/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dependencies {
api libs.reactor.core
implementation 'io.micrometer:context-propagation:1.1.1'
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2020 LINE Corporation
*
* LINE Corporation licenses this file to you 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 com.linecorp.armeria.common.reactor3;

import com.linecorp.armeria.common.RequestContext;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;

/**
* Utility class to keep {@link RequestContext} during
* <a href="https://github.com/reactor/reactor-core">Reactor</a> operations
* with <a href="https://docs.micrometer.io/context-propagation/reference/index.html">
* Context-propagation</a>.
*/
public final class RequestContextPropagationHook {
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved

private static volatile boolean enabled;

/**
* Enable <a href="https://docs.micrometer.io/context-propagation/reference/index.html">
* Context-propagation</a> to keep {@link RequestContext} during
* Reactor operations.
* Please note that enable {@link RequestContextPropagationHook} at the
* start of the application. otherwise, {@link RequestContext} may not be keep.
*/
public static synchronized void enable() {
if (enabled) {
return;
}
Hooks.enableAutomaticContextPropagation();
enabled = true;
}

/**
* It returns whether the {@link RequestContextPropagationHook} is enabled.
*/
public static boolean isEnabled() {
return enabled;
}

/**
* It disable {@link RequestContextPropagationHook}. {@link RequestContext}
* will not be keep during both {@link Mono} and {@link Flux} Operations.
*/
public static synchronized void disable() {
if (!enabled) {
return;
}

Hooks.disableAutomaticContextPropagation();
enabled = false;
}

private RequestContextPropagationHook() {}
}