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

Add history-tracking to ObservationValidator #5370

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import io.micrometer.observation.Observation;
import io.micrometer.observation.Observation.Context;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* A {@link RuntimeException} that can be thrown when an invalid {@link Observation}
* detected.
Expand All @@ -29,13 +33,83 @@ public class InvalidObservationException extends RuntimeException {

private final Context context;

InvalidObservationException(String message, Context context) {
private final List<HistoryElement> history;

InvalidObservationException(String message, Context context, List<HistoryElement> history) {
super(message);
this.context = context;
this.history = history;
}

public Context getContext() {
return context;
}

public List<HistoryElement> getHistory() {
return history;
}

@Override
public String toString() {
return super.toString() + "\n"
+ history.stream().map(HistoryElement::toString).collect(Collectors.joining("\n"));
}

public static class HistoryElement {

private final EventName eventName;

private final StackTraceElement[] stackTrace;

HistoryElement(EventName eventName) {
this.eventName = eventName;
StackTraceElement[] currentStackTrace = Thread.getAllStackTraces().get(Thread.currentThread());
this.stackTrace = findRelevantStackTraceElements(currentStackTrace);
}

private StackTraceElement[] findRelevantStackTraceElements(StackTraceElement[] stackTrace) {
int index = findFirstRelevantStackTraceElementIndex(stackTrace);
if (index == -1) {
return new StackTraceElement[0];
}
else {
return Arrays.copyOfRange(stackTrace, index, stackTrace.length);
}
}

private int findFirstRelevantStackTraceElementIndex(StackTraceElement[] stackTrace) {
int index = -1;
for (int i = 0; i < stackTrace.length; i++) {
String className = stackTrace[i].getClassName();
if (className.equals(Observation.class.getName())
|| className.equals("io.micrometer.observation.SimpleObservation")) {
// the first relevant StackTraceElement is after the last Observation
index = i + 1;
}
}

return (index >= stackTrace.length) ? -1 : index;
}

public EventName getEventName() {
return eventName;
}

public StackTraceElement[] getStackTrace() {
return stackTrace;
}

@Override
public String toString() {
return eventName + ": " + stackTrace[0];
}

}

public enum EventName {

START, STOP, ERROR, EVENT, SCOPE_OPEN, SCOPE_CLOSE, SCOPE_RESET

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
import io.micrometer.observation.Observation.Context;
import io.micrometer.observation.Observation.Event;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.tck.InvalidObservationException.EventName;
import io.micrometer.observation.tck.InvalidObservationException.HistoryElement;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

Expand Down Expand Up @@ -52,6 +57,7 @@ class ObservationValidator implements ObservationHandler<Context> {

@Override
public void onStart(Context context) {
addHistoryElement(context, EventName.START);
Status status = context.get(Status.class);
if (status != null) {
consumer.accept(new ValidationResult("Invalid start: Observation has already been started", context));
Expand All @@ -63,31 +69,37 @@ public void onStart(Context context) {

@Override
public void onError(Context context) {
addHistoryElement(context, EventName.ERROR);
checkIfObservationWasStartedButNotStopped("Invalid error signal", context);
}

@Override
public void onEvent(Event event, Context context) {
addHistoryElement(context, EventName.EVENT);
checkIfObservationWasStartedButNotStopped("Invalid event signal", context);
}

@Override
public void onScopeOpened(Context context) {
addHistoryElement(context, EventName.SCOPE_OPEN);
checkIfObservationWasStartedButNotStopped("Invalid scope opening", context);
}

@Override
public void onScopeClosed(Context context) {
addHistoryElement(context, EventName.SCOPE_CLOSE);
checkIfObservationWasStartedButNotStopped("Invalid scope closing", context);
}

@Override
public void onScopeReset(Context context) {
addHistoryElement(context, EventName.SCOPE_RESET);
checkIfObservationWasStartedButNotStopped("Invalid scope resetting", context);
}

@Override
public void onStop(Context context) {
addHistoryElement(context, EventName.STOP);
Status status = checkIfObservationWasStartedButNotStopped("Invalid stop", context);
if (status != null) {
status.markStopped();
Expand All @@ -99,6 +111,14 @@ public boolean supportsContext(Context context) {
return supportsContextPredicate.test(context);
}

private void addHistoryElement(Context context, EventName eventName) {
if (!context.containsKey(History.class)) {
context.put(History.class, new History());
}
History history = context.get(History.class);
history.addHistoryElement(eventName);
}

@Nullable
private Status checkIfObservationWasStartedButNotStopped(String prefix, Context context) {
Status status = context.get(Status.class);
Expand All @@ -113,7 +133,9 @@ else if (status.isStopped()) {
}

private static void throwInvalidObservationException(ValidationResult validationResult) {
throw new InvalidObservationException(validationResult.getMessage(), validationResult.getContext());
History history = validationResult.getContext().getOrDefault(History.class, new History());
throw new InvalidObservationException(validationResult.getMessage(), validationResult.getContext(),
history.getHistoryElements());
}

static class ValidationResult {
Expand Down Expand Up @@ -156,4 +178,18 @@ void markStopped() {

}

static class History {

private final List<HistoryElement> historyElements = new ArrayList<>();

private void addHistoryElement(EventName eventName) {
historyElements.add(new HistoryElement(eventName));
}

List<HistoryElement> getHistoryElements() {
return Collections.unmodifiableList(historyElements);
}

}

}