diff --git a/bosk-core/src/main/java/io/vena/bosk/Bosk.java b/bosk-core/src/main/java/io/vena/bosk/Bosk.java index 4d80d2ac..2b2e6b8a 100644 --- a/bosk-core/src/main/java/io/vena/bosk/Bosk.java +++ b/bosk-core/src/main/java/io/vena/bosk/Bosk.java @@ -1,5 +1,7 @@ package io.vena.bosk; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.vena.bosk.BoskDiagnosticContext.DiagnosticScope; import io.vena.bosk.ReferenceUtils.CatalogRef; import io.vena.bosk.ReferenceUtils.ListingRef; @@ -399,7 +401,7 @@ private void triggerQueueingOfHooks(Reference target, @Nullable R prior ) { try (@SuppressWarnings("unused") ReadContext executionContext = new ReadContext(rootForHook)) { LOGGER.debug("Hook: RUN {}({})", reg.name, changedRef); - reg.hook.onChanged(changedRef); + callHook(reg, changedRef, reg.name()); } catch (Exception e) { LOGGER.error("Bosk hook \"" + reg.name() + "\" terminated with an exception, which usually indicates a bug. State updates may have been lost", e); @@ -415,6 +417,11 @@ private void triggerQueueingOfHooks(Reference target, @Nullable R prior }); } + @WithSpan + private static void callHook(Bosk.HookRegistration reg, @SpanAttribute("hook.reference") Reference changedRef, @SpanAttribute("hook.name") String hookName) { + reg.hook.onChanged(changedRef); + } + /** * Runs queued hooks in a "breadth-first" fashion: all hooks "H" triggered by * any single hook "G" will run before any consequent hooks triggered by "H". diff --git a/bosk-core/src/main/java/io/vena/bosk/drivers/OtelSpanContextDriver.java b/bosk-core/src/main/java/io/vena/bosk/drivers/OtelSpanContextDriver.java index f3f6c03f..79597a5d 100644 --- a/bosk-core/src/main/java/io/vena/bosk/drivers/OtelSpanContextDriver.java +++ b/bosk-core/src/main/java/io/vena/bosk/drivers/OtelSpanContextDriver.java @@ -1,6 +1,9 @@ package io.vena.bosk.drivers; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.vena.bosk.BoskDiagnosticContext; import io.vena.bosk.BoskDriver; import io.vena.bosk.DriverFactory; @@ -18,60 +21,100 @@ public class OtelSpanContextDriver implements BoskDriver { private final BoskDiagnosticContext context; private final BoskDriver downstream; + private final String caller; - OtelSpanContextDriver(BoskDiagnosticContext context, BoskDriver downstream) { + OtelSpanContextDriver(BoskDiagnosticContext context, BoskDriver downstream, String caller) { this.context = context; this.downstream = downstream; + this.caller = caller; } public static DriverFactory factory() { - return (b,d) -> new OtelSpanContextDriver<>(b.rootReference().diagnosticContext(), d); + StackTraceElement caller = new RuntimeException().getStackTrace()[1]; + return (b,d) -> new OtelSpanContextDriver<>( + b.rootReference().diagnosticContext(), + d, + caller.toString() + ); } @Override - public R initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException { + @WithSpan("initialRoot") + public R initialRoot( + @SpanAttribute("bosk.rootType") Type rootType + ) throws InvalidTypeException, IOException, InterruptedException { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { return downstream.initialRoot(rootType); } } @Override - public void submitReplacement(Reference target, T newValue) { + @WithSpan("submitReplacement") + public void submitReplacement( + @SpanAttribute("bosk.target") Reference target, + T newValue + ) { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.submitReplacement(target, newValue); } } @Override - public void submitConditionalReplacement(Reference target, T newValue, Reference precondition, Identifier requiredValue) { + @WithSpan("submitConditionalReplacement") + public void submitConditionalReplacement( + @SpanAttribute("bosk.target") Reference target, + T newValue, + @SpanAttribute("bosk.precondition") Reference precondition, + @SpanAttribute("bosk.requiredValue") Identifier requiredValue + ) { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.submitConditionalReplacement(target, newValue, precondition, requiredValue); } } @Override - public void submitInitialization(Reference target, T newValue) { + @WithSpan("submitInitialization") + public void submitInitialization( + @SpanAttribute("bosk.target") Reference target, + T newValue + ) { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.submitInitialization(target, newValue); } } @Override - public void submitDeletion(Reference target) { + @WithSpan("submitDeletion") + public void submitDeletion( + @SpanAttribute("bosk.target") Reference target + ) { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.submitDeletion(target); } } @Override - public void submitConditionalDeletion(Reference target, Reference precondition, Identifier requiredValue) { + @WithSpan("submitConditionalDeletion") + public void submitConditionalDeletion( + @SpanAttribute("bosk.target") Reference target, + @SpanAttribute("bosk.precondition") Reference precondition, + @SpanAttribute("bosk.requiredValue") Identifier requiredValue + ) { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.submitConditionalDeletion(target, precondition, requiredValue); } } @Override + @WithSpan("InterruptedException") public void flush() throws IOException, InterruptedException { + Span.current().setAttribute("bosk.createdAt", caller); try (var __ = context.withCurrentOtelContext()) { downstream.flush(); } diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MainDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MainDriver.java index 5dbd29e7..0f6cdaf5 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MainDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MainDriver.java @@ -375,6 +375,7 @@ private Listener(FutureTask initialRootAction) { } @Override + @WithSpan("onConnectionSucceeded") public void onConnectionSucceeded() throws UnrecognizedFormatException, UninitializedCollectionException, @@ -582,7 +583,6 @@ private MDCScope beginDriverOperation(String description, Object... args) { return ex; } - @WithSpan private void doRetryableDriverOperation(RetryableOperation operation, String description, Object... args) throws X,Y { RetryableOperation operationInSession = () -> { int immediateRetriesLeft = 2; diff --git a/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverHanoiTest.java b/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverHanoiTest.java index 9736fd56..ae7d5349 100644 --- a/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverHanoiTest.java +++ b/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverHanoiTest.java @@ -27,7 +27,8 @@ public MongoDriverHanoiTest(ParameterSet parameters) { mongoService.clientSettings(), settings, new BsonPlugin() - ) + ), + OtelSpanContextDriver.factory() ); mongoService.client() .getDatabase(settings.database()) diff --git a/bosk-testing/src/main/java/io/vena/bosk/drivers/AbstractDriverTest.java b/bosk-testing/src/main/java/io/vena/bosk/drivers/AbstractDriverTest.java index 2385efcb..3493d34d 100644 --- a/bosk-testing/src/main/java/io/vena/bosk/drivers/AbstractDriverTest.java +++ b/bosk-testing/src/main/java/io/vena/bosk/drivers/AbstractDriverTest.java @@ -53,6 +53,7 @@ protected void setupBosksAndReferences(DriverFactory driverFactory) // This is the bosk we're testing bosk = new Bosk("Test bosk", TestEntity.class, AbstractDriverTest::initialRoot, DriverStack.of( MirroringDriver.targeting(canonicalBosk), + OtelSpanContextDriver.factory(), DriverStateVerifier.wrap(driverFactory, TestEntity.class, AbstractDriverTest::initialRoot) )); driver = bosk.driver(); diff --git a/bosk-testing/src/main/java/io/vena/bosk/drivers/DriverConformanceTest.java b/bosk-testing/src/main/java/io/vena/bosk/drivers/DriverConformanceTest.java index f4eed1f5..299afc45 100644 --- a/bosk-testing/src/main/java/io/vena/bosk/drivers/DriverConformanceTest.java +++ b/bosk-testing/src/main/java/io/vena/bosk/drivers/DriverConformanceTest.java @@ -1,5 +1,7 @@ package io.vena.bosk.drivers; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.vena.bosk.Bosk; import io.vena.bosk.Catalog; import io.vena.bosk.CatalogReference; @@ -62,7 +64,8 @@ void initialState(Path enclosingCatalogPath) { } @ParametersByName - void replaceIdentical(Path enclosingCatalogPath, Identifier childID) throws InvalidTypeException { + @WithSpan("replaceIdentical") + void replaceIdentical(@SpanAttribute("enclosingPath") Path enclosingCatalogPath, @SpanAttribute("childID") Identifier childID) throws InvalidTypeException { CatalogReference ref = initializeBoskWithCatalog(enclosingCatalogPath); driver.submitReplacement(ref.then(childID), newEntity(childID, ref)); assertCorrectBoskContents(); @@ -455,7 +458,8 @@ private Reference initializeBoskWithBlankValues(Path enclosingCatalo return ref; } - private CatalogReference initializeBoskWithCatalog(Path enclosingCatalogPath) { + @WithSpan + private CatalogReference initializeBoskWithCatalog(@SpanAttribute("enclosingCatalogPath") Path enclosingCatalogPath) { LOGGER.debug("initializeBoskWithCatalog({})", enclosingCatalogPath); setupBosksAndReferences(driverFactory); try {