Skip to content

Commit

Permalink
feat(web): populate the MDC when the ApplicationService gathers infor…
Browse files Browse the repository at this point in the history
…mation from front50 and clouddriver (#1830)

so that, for example, when gate receives an http request with an X-SPINNAKER-REQUEST-ID header, that same value appears in outgoing request headers to front50 and clouddriver.  This makes troubleshooting a whole lot easier.
  • Loading branch information
kirangodishala committed Sep 19, 2024
1 parent 81b25aa commit 79dc08e
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class ApplicationService {
return result
}

static class Front50ApplicationListRetriever implements Callable<List<Map>> {
static class Front50ApplicationListRetriever extends MdcWrappedCallable<List<Map>> {
private final Front50Service front50
private final AtomicReference<List<Map>> allApplicationsCache
private final Object principal
Expand All @@ -363,7 +363,7 @@ class ApplicationService {
}

@Override
List<Map> call() throws Exception {
List<Map> callWithMdc() throws Exception {
try {
AuthenticatedRequest.propagate({
try {
Expand All @@ -383,7 +383,7 @@ class ApplicationService {
}
}

static class Front50ApplicationRetriever implements Callable<Map> {
static class Front50ApplicationRetriever extends MdcWrappedCallable<Map> {
private final String name
private final Front50Service front50
private final AtomicReference<List<Map>> allApplicationsCache
Expand All @@ -399,7 +399,7 @@ class ApplicationService {
}

@Override
Map call() throws Exception {
Map callWithMdc() throws Exception {
try {
AuthenticatedRequest.propagate({
try {
Expand All @@ -422,7 +422,7 @@ class ApplicationService {
}
}

static class ClouddriverApplicationListRetriever implements Callable<List<Map>> {
static class ClouddriverApplicationListRetriever extends MdcWrappedCallable<List<Map>> {
private final ClouddriverService clouddriver
private final Object principal
private final AtomicReference<List<Map>> allApplicationsCache
Expand All @@ -438,7 +438,7 @@ class ApplicationService {
}

@Override
List<Map> call() throws Exception {
List<Map> callWithMdc() throws Exception {
try {
AuthenticatedRequest.propagate({
try {
Expand All @@ -458,7 +458,7 @@ class ApplicationService {
}
}

static class ClouddriverApplicationRetriever implements Callable<Map> {
static class ClouddriverApplicationRetriever extends MdcWrappedCallable<Map> {
private final String name
private final ClouddriverService clouddriver
private final Object principal
Expand All @@ -471,7 +471,7 @@ class ApplicationService {
}

@Override
Map call() throws Exception {
Map callWithMdc() throws Exception {
try {
AuthenticatedRequest.propagate({
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 Salesforce, 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
*
* 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.netflix.spinnaker.gate.services;

import java.util.Map;
import java.util.concurrent.Callable;
import org.slf4j.MDC;

/** Make a copy of the MDC at construction time available to a task. */
public abstract class MdcWrappedCallable<T> implements Callable<T> {
private final Map<String, String> contextMap;

public MdcWrappedCallable() {
this.contextMap = MDC.getCopyOfContextMap();
}

/** Compute a result, with the MDC as it was at construction time of this object */
public abstract T callWithMdc() throws Exception;

@Override
public T call() throws Exception {
if (contextMap == null) {
MDC.clear();
} else {
MDC.setContextMap(contextMap);
}
return callWithMdc();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2023 Salesforce, 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
*
* 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.netflix.spinnaker.gate.services;

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

import ch.qos.logback.classic.Level;
import com.netflix.spinnaker.kork.test.log.MemoryAppender;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

class MdcWrappedCallableTest {
private static final Logger log = LoggerFactory.getLogger(MdcWrappedCallableTest.class);

@Test
void verifyMdcWrappedCallableIncludesTheMdc() throws Exception {
// Capture the log messages that our test callable generates
MemoryAppender memoryAppender = new MemoryAppender(MdcWrappedCallableTest.class);

// Provide a way to execute callables in some other thread
ExecutorService executorService = Executors.newCachedThreadPool();

// Put something in the MDC here, to see if it makes it into the thread that
// executes the operation.
String mdcKey = "myKey";
String mdcValue = "myValue";
MDC.put(mdcKey, mdcValue);

// The contents of the MDC at construction time of the MdcWrappedCallable
// are what's available when it executes, so construct it after the MDC is set.
Callable testCallable = new TestCallable();

// Execute the callable in another thread
executorService.submit(testCallable).get();

// Verify that messages logged in the MdcWrappedCallable include the info from the MDC
List<String> logMessages = memoryAppender.search(mdcKey + "=" + mdcValue, Level.INFO);
assertThat(logMessages).hasSize(1);

// And now clear the MDC and make sure the resulting operation gets the empty MDC.
MDC.clear();
Callable emptyMdcCallable = new TestCallable();
executorService.submit(emptyMdcCallable).get();

List<String> emptyMdcMessages = memoryAppender.search("contextMap: null", Level.INFO);
assertThat(emptyMdcMessages).hasSize(1);
}

static class TestCallable extends MdcWrappedCallable<Void> {
@Override
public Void callWithMdc() {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
log.info("contextMap: {}", contextMap);
return null;
}
}
}

0 comments on commit 79dc08e

Please sign in to comment.