diff --git a/flexmark/src/main/java/com/vladsch/flexmark/html/HtmlRenderer.java b/flexmark/src/main/java/com/vladsch/flexmark/html/HtmlRenderer.java index 7a41e81cf..78d75baea 100644 --- a/flexmark/src/main/java/com/vladsch/flexmark/html/HtmlRenderer.java +++ b/flexmark/src/main/java/com/vladsch/flexmark/html/HtmlRenderer.java @@ -18,6 +18,7 @@ import com.vladsch.flexmark.util.sequence.Escaping; import com.vladsch.flexmark.util.sequence.LineAppendable; import com.vladsch.flexmark.util.sequence.TagRange; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.instrumentation.annotations.WithSpan; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -206,6 +207,10 @@ public void render(@NotNull Node node, @NotNull Appendable output) { */ @WithSpan("markdown.render") public void render(@NotNull Node node, @NotNull Appendable output, int maxTrailingBlankLines) { + Span span = Span.current(); + span.setAttribute("markdown.node.type", node.getClass().getSimpleName()); + span.setAttribute("markdown.input.length", node.getChars().length()); + HtmlWriter htmlWriter = new HtmlWriter(output, htmlOptions.indentSize, htmlOptions.formatFlags, !htmlOptions.htmlBlockOpenTagEol, !htmlOptions.htmlBlockCloseTagEol); MainNodeRenderer renderer = new MainNodeRenderer(options, htmlWriter, node.getDocument()); if (renderer.htmlIdGenerator != HtmlIdGenerator.NULL && !(node instanceof Document)) { @@ -218,6 +223,10 @@ public void render(@NotNull Node node, @NotNull Appendable output, int maxTraili // resolve any unresolved tracked offsets that are outside elements which resolve their own TrackedOffsetUtils.resolveTrackedOffsets(node.getChars(), htmlWriter, TRACKED_OFFSETS.get(renderer.getDocument()), maxTrailingBlankLines, SharedDataKeys.RUNNING_TESTS.get(options)); renderer.dispose(); + + if (output instanceof CharSequence) { + span.setAttribute("markdown.output.length", ((CharSequence) output).length()); + } } /** diff --git a/flexmark/src/main/java/com/vladsch/flexmark/parser/Parser.java b/flexmark/src/main/java/com/vladsch/flexmark/parser/Parser.java index 17ccc6ea5..de01481fb 100644 --- a/flexmark/src/main/java/com/vladsch/flexmark/parser/Parser.java +++ b/flexmark/src/main/java/com/vladsch/flexmark/parser/Parser.java @@ -17,6 +17,7 @@ import com.vladsch.flexmark.util.sequence.BasedSequence; import com.vladsch.flexmark.util.sequence.ReplacedBasedSequence; import com.vladsch.flexmark.util.sequence.mappers.SpecialLeadInHandler; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.instrumentation.annotations.WithSpan; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -356,6 +357,8 @@ public static Builder builder(DataHolder options) { */ @WithSpan("markdown.parse") public @NotNull Document parse(@NotNull BasedSequence input) { + Span.current().setAttribute("markdown.input.length", input.length()); + // NOTE: parser can only handle contiguous sequences with no out of base characters if (input instanceof ReplacedBasedSequence) { throw new IllegalArgumentException("" + @@ -383,6 +386,8 @@ public static Builder builder(DataHolder options) { */ @WithSpan("markdown.parse") public @NotNull Document parse(@NotNull String input) { + Span.current().setAttribute("markdown.input.length", input.length()); + DocumentParser documentParser = new DocumentParser(options , blockParserFactories , paragraphPreProcessorFactories diff --git a/flexmark/src/main/java/com/vladsch/flexmark/parser/internal/PostProcessorManager.java b/flexmark/src/main/java/com/vladsch/flexmark/parser/internal/PostProcessorManager.java index 8021db187..765fe3fdd 100644 --- a/flexmark/src/main/java/com/vladsch/flexmark/parser/internal/PostProcessorManager.java +++ b/flexmark/src/main/java/com/vladsch/flexmark/parser/internal/PostProcessorManager.java @@ -12,6 +12,10 @@ import com.vladsch.flexmark.util.dependency.DependencyResolver; import com.vladsch.flexmark.util.dependency.DependentItem; import com.vladsch.flexmark.util.dependency.DependentItemMap; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.annotations.WithSpan; import java.util.*; @@ -72,7 +76,16 @@ public Document postProcess(Document document) { boolean hadGlobal = false; for (PostProcessorFactory dependent : stage.dependents) { if (dependent.affectsGlobalScope()) { - document = dependent.apply(document).processDocument(document); + PostProcessor postProcessor = dependent.apply(document); + Span span = startPostProcessorSpan(postProcessor); + try (Scope ignored = span.makeCurrent()) { + document = postProcessor.processDocument(document); + } catch (Throwable t) { + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName() + ": " + t.getMessage()); + throw t; + } finally { + span.end(); + } hadGlobal = true; // assume it no longer reflects reality; classifyingNodeTracker = null; @@ -94,28 +107,36 @@ public Document postProcess(Document document) { } ReversibleIterable nodes = classifyingNodeTracker.getCategoryItems(Node.class, dependentNodeTypes.keySet()); - for (Node node : nodes) { - if (node.getParent() == null) continue; // was already removed - // now we need to get the bitset for the excluded ancestors of the node, then intersect it with the actual ancestors of this factory - int index; - BitSet nodeAncestors; - BitSet nodeExclusions; - Set> excluded = dependentNodeTypes.get(node.getClass()); - if (excluded != null) { - index = classifyingNodeTracker.getItems().indexOf(node); - if (index != -1) { - nodeAncestors = classifyingNodeTracker.getNodeAncestryMap().get(index); - if (nodeAncestors != null) { - nodeExclusions = classifyingNodeTracker.getExclusionSet().indexBitSet(excluded); - nodeExclusions.and(nodeAncestors); - if (!nodeExclusions.isEmpty()) { - // has excluded ancestor - continue; + Span span = startPostProcessorSpan(postProcessor); + try (Scope ignored = span.makeCurrent()) { + for (Node node : nodes) { + if (node.getParent() == null) continue; // was already removed + // now we need to get the bitset for the excluded ancestors of the node, then intersect it with the actual ancestors of this factory + int index; + BitSet nodeAncestors; + BitSet nodeExclusions; + Set> excluded = dependentNodeTypes.get(node.getClass()); + if (excluded != null) { + index = classifyingNodeTracker.getItems().indexOf(node); + if (index != -1) { + nodeAncestors = classifyingNodeTracker.getNodeAncestryMap().get(index); + if (nodeAncestors != null) { + nodeExclusions = classifyingNodeTracker.getExclusionSet().indexBitSet(excluded); + nodeExclusions.and(nodeAncestors); + if (!nodeExclusions.isEmpty()) { + // has excluded ancestor + continue; + } } } } + postProcessor.process(classifyingNodeTracker, node); } - postProcessor.process(classifyingNodeTracker, node); + } catch (Throwable t) { + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName() + ": " + t.getMessage()); + throw t; + } finally { + span.end(); } } } @@ -125,6 +146,14 @@ public Document postProcess(Document document) { return document; } + private static Span startPostProcessorSpan(PostProcessor postProcessor) { + // no-op span unless a Java agent (or the application) has wired GlobalOpenTelemetry + return GlobalOpenTelemetry.getTracer("com.vladsch.flexmark") + .spanBuilder("markdown.parse.post_processor") + .setAttribute("markdown.post_processor.class", postProcessor.getClass().getName()) + .startSpan(); + } + static DependentItemMap prioritizePostProcessors(DependentItemMap dependentMap) { // put globals last List, DependentItem>> prioritized = dependentMap.entries();