diff --git a/scm-core/src/main/java/sonia/scm/trace/Span.java b/scm-core/src/main/java/sonia/scm/trace/Span.java index a386c72b19..6c1ff8ed6e 100644 --- a/scm-core/src/main/java/sonia/scm/trace/Span.java +++ b/scm-core/src/main/java/sonia/scm/trace/Span.java @@ -24,7 +24,6 @@ package sonia.scm.trace; -import java.time.Clock; import java.time.Instant; import java.util.Collections; import java.util.LinkedHashMap; @@ -37,7 +36,6 @@ import java.util.Map; */ public final class Span implements AutoCloseable { - private final Clock clock; private final Tracer tracer; private final String kind; private final Map labels = new LinkedHashMap<>(); @@ -45,14 +43,9 @@ public final class Span implements AutoCloseable { private boolean failed; Span(Tracer tracer, String kind) { - this(tracer, kind, Clock.systemUTC()); - } - - Span(Tracer tracer, String kind, Clock clock) { - this.clock = clock; this.tracer = tracer; this.kind = kind; - this.opened = clock.instant(); + this.opened = Instant.now(); } /** @@ -142,7 +135,7 @@ public final class Span implements AutoCloseable { */ @Override public void close() { - tracer.export(new SpanContext(kind, Collections.unmodifiableMap(labels), opened, clock.instant(), failed)); + tracer.export(new SpanContext(kind, Collections.unmodifiableMap(labels), opened, Instant.now(), failed)); } } diff --git a/scm-core/src/main/java/sonia/scm/trace/SpanContext.java b/scm-core/src/main/java/sonia/scm/trace/SpanContext.java index 964068d71e..82d3d92db2 100644 --- a/scm-core/src/main/java/sonia/scm/trace/SpanContext.java +++ b/scm-core/src/main/java/sonia/scm/trace/SpanContext.java @@ -24,8 +24,6 @@ package sonia.scm.trace; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Value; import java.time.Duration; @@ -33,12 +31,12 @@ import java.time.Instant; import java.util.Map; /** - * The {@link SpanContext} represents a finsished span which could be processed by an {@link Exporter}. + * The {@link SpanContext} represents a finished span which could be processed by an {@link Exporter}. + * * @since 2.9.0 */ @Value -@AllArgsConstructor(access = AccessLevel.PACKAGE) -public final class SpanContext { +public class SpanContext { String kind; Map labels; diff --git a/scm-core/src/test/java/sonia/scm/trace/TracerTest.java b/scm-core/src/test/java/sonia/scm/trace/TracerTest.java index db9d36001e..12985038f8 100644 --- a/scm-core/src/test/java/sonia/scm/trace/TracerTest.java +++ b/scm-core/src/test/java/sonia/scm/trace/TracerTest.java @@ -34,7 +34,6 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; - class TracerTest { private Tracer tracer; @@ -63,8 +62,6 @@ class TracerTest { try (Span span = tracer.span("sample")) { span.label("l1", "one"); Thread.sleep(1L); - } catch (Exception ex) { - span. } SpanContext span = exporter.spans.get(0); diff --git a/scm-webapp/src/main/java/sonia/scm/trace/LoggingExporter.java b/scm-webapp/src/main/java/sonia/scm/trace/LoggingExporter.java new file mode 100644 index 0000000000..1e46a81605 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/trace/LoggingExporter.java @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.trace; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.plugin.Extension; + +import javax.inject.Inject; +import java.util.Map; +import java.util.function.Consumer; + +/** + * An {@link Exporter} which logs every collected span. + * + * @since 2.9.0 + */ +@Extension +public final class LoggingExporter implements Exporter { + + private static final Logger LOG = LoggerFactory.getLogger(LoggingExporter.class); + + private final Consumer logger; + + @Inject + LoggingExporter() { + this(LOG::info); + } + + LoggingExporter(Consumer logger) { + this.logger = logger; + } + + @Override + public void export(SpanContext span) { + logger.accept(format(span)); + } + + private String format(SpanContext span) { + StringBuilder message = new StringBuilder("received "); + if (span.isFailed()) { + message.append("failed "); + } + message.append(span.getKind()).append(" span, which took "); + message.append(span.duration().toMillis()).append("ms"); + Map labels = span.getLabels(); + if (!labels.isEmpty()) { + message.append(":"); + for (Map.Entry e : labels.entrySet()) { + message.append("\n - ").append(e.getKey()).append(": ").append(e.getValue()); + } + } + return message.toString(); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/trace/LoggingExporterTest.java b/scm-webapp/src/test/java/sonia/scm/trace/LoggingExporterTest.java new file mode 100644 index 0000000000..b1e285a5e2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/trace/LoggingExporterTest.java @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.trace; + +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +class LoggingExporterTest { + + private String message; + + private LoggingExporter exporter; + + @BeforeEach + void setUpLogger() { + exporter = new LoggingExporter((message) -> this.message = message); + } + + @Test + void shouldLogTheSpanKind() { + exporter.export(new SpanContext( + "AwesomeSpanKind", Collections.emptyMap(), Instant.now(), Instant.now(), false + )); + + assertThat(message).contains("AwesomeSpanKind"); + } + + @Test + void shouldLogFailed() { + exporter.export(new SpanContext( + "sample", Collections.emptyMap(), Instant.now(), Instant.now(), true + )); + + assertThat(message).contains("failed"); + } + + @Test + void shouldLogDuration() { + Instant opened = Instant.now(); + exporter.export(new SpanContext( + "sample", ImmutableMap.of(), opened, opened.plusMillis(42L), false + )); + + assertThat(message).contains("42ms"); + } + + @Test + void shouldLogLabels() { + exporter.export(new SpanContext( + "sample", ImmutableMap.of("l1", "v1", "l2", "v2"), Instant.now(), Instant.now(), false + )); + + assertThat(message) + .contains("l1") + .contains("v1") + .contains("l2") + .contains("v2"); + } + +}