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

Merged
merged 52 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 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
640f904
Create new module micrometer-context.
chickenchickenlove Oct 26, 2024
71e414a
Add @Nullable Annotation.
chickenchickenlove Oct 26, 2024
b23f8f7
Add package-info to fix lint error.
chickenchickenlove Oct 26, 2024
646cd35
Fixes broken test.
chickenchickenlove Oct 26, 2024
6f7e589
Remove context-propagation dependency from reactor3 moudle.
chickenchickenlove Oct 28, 2024
1826d58
Update micrometer-context/src/main/java/com/linecorp/armeria/common/m…
chickenchickenlove Nov 5, 2024
1dcd621
Update micrometer-context/src/main/java/com/linecorp/armeria/common/m…
chickenchickenlove Nov 5, 2024
96c73c5
Import @UnstableApi.
chickenchickenlove Nov 5, 2024
33535c0
Exclude from publish
ikhoon Nov 8, 2024
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
5 changes: 5 additions & 0 deletions dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ caffeine = "2.9.3"
cglib = "3.3.0"
checkerframework = "2.5.6"
checkstyle = "10.3.2"
context-propagation = "1.1.1"
controlplane = "1.0.45"
curator = "5.6.0"
dagger = "2.51.1"
Expand Down Expand Up @@ -315,6 +316,10 @@ version.ref = "checkerframework"
module = "com.puppycrawl.tools:checkstyle"
version.ref = "checkstyle"

[libraries.context-propagation]
module = "io.micrometer:context-propagation"
version.ref = "context-propagation"

[libraries.controlplane-api]
module = "io.envoyproxy.controlplane:api"
version.ref = "controlplane"
Expand Down
4 changes: 4 additions & 0 deletions micrometer-context/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation libs.context.propagation
testImplementation project(':reactor3')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2024 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.micrometer.context;

import org.reactivestreams.Subscription;

import com.linecorp.armeria.common.RequestContext;
import com.linecorp.armeria.common.RequestContextStorage;
import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.annotation.UnstableApi;
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">Micrometer
* Context Propagation</a> to keep the {@link RequestContext} during
* <a href="https://github.com/reactor/reactor-core">Reactor</a> operations.
* Get the {@link RequestContextThreadLocalAccessor} to register it to the {@link ContextRegistry}.
* Then, {@link ContextRegistry} will use {@link RequestContextThreadLocalAccessor} 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.
*/
@UnstableApi
public final class RequestContextThreadLocalAccessor implements ThreadLocalAccessor<RequestContext> {

private static final Object KEY = RequestContext.class;

/**
* The value which obtained through {@link RequestContextThreadLocalAccessor},
* 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;
}

/**
* {@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.
*/
@Nullable
@Override
public RequestContext getValue() {
return RequestContext.currentOrNull();
}

/**
* {@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
@SuppressWarnings("MustBeClosedChecker")
public void setValue(RequestContext value) {
RequestContextUtil.getAndSet(value);
}

/**
* 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 RequestContextThreadLocalAccessor#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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 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.
*/

/**
* Micrometer context-propagation plugins to help keep {@link com.linecorp.armeria.common.RequestContext}
* during Reactor operations.
*/
@UnstableApi
@NonNullByDefault
package com.linecorp.armeria.common.micrometer.context;

import com.linecorp.armeria.common.annotation.NonNullByDefault;
import com.linecorp.armeria.common.annotation.UnstableApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2024 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.micrometer.context;

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

import org.junit.jupiter.api.Test;

import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.RequestContext;
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.ContextSnapshotFactory;

class RequestContextThreadLocalAccessorTest {

@Test
void should_return_expected_key() {
// Given
final RequestContextThreadLocalAccessor reqCtxAccessor = new RequestContextThreadLocalAccessor();
final Object expectedValue = RequestContext.class;

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

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

@Test
@SuppressWarnings("MustBeClosedChecker")
void should_success_set() {
// Given
final ClientRequestContext ctx = newContext();
final RequestContextThreadLocalAccessor reqCtxAccessor = new RequestContextThreadLocalAccessor();

// When
reqCtxAccessor.setValue(ctx);

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

RequestContextUtil.pop();
}

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

// When + Then
assertThatThrownBy(() -> reqCtxAccessor.setValue(null))
.isInstanceOf(NullPointerException.class);
}

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

// When
reqCtxAccessor.setValue();

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

@Test
@SuppressWarnings("MustBeClosedChecker")
void should_be_restore_original_state_when_restore() {
// Given
final RequestContextThreadLocalAccessor reqCtxAccessor = new RequestContextThreadLocalAccessor();
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);

RequestContextUtil.pop();
}

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

// When
reqCtxAccessor.restore();

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

@Test
void requestContext_should_exist_inside_scope_and_not_outside() {
// Given
final RequestContextThreadLocalAccessor reqCtxAccessor = new RequestContextThreadLocalAccessor();
ContextRegistry.getInstance()
.registerThreadLocalAccessor(reqCtxAccessor);
final ClientRequestContext currentCtx = newContext();
final ClientRequestContext expectedCtx = currentCtx;
reqCtxAccessor.setValue(currentCtx);

final ContextSnapshotFactory factory = ContextSnapshotFactory.builder()
.clearMissing(true)
.build();
final ContextSnapshot contextSnapshot = factory.captureAll();
reqCtxAccessor.setValue();

// When : contextSnapshot.setThreadLocals()
try (Scope ignored = contextSnapshot.setThreadLocals()) {

// Then : should not
final RequestContext reqCtxInScope = RequestContext.currentOrNull();
assertThat(reqCtxInScope).isSameAs(expectedCtx);
}

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

static ClientRequestContext newContext() {
return ClientRequestContext.of(HttpRequest.of(HttpMethod.GET, "/"));
}
}
Loading
Loading