diff --git a/opentracing-api/src/main/java/io/opentracing/Span.java b/opentracing-api/src/main/java/io/opentracing/Span.java index 5961fcea..8dfc2b3d 100644 --- a/opentracing-api/src/main/java/io/opentracing/Span.java +++ b/opentracing-api/src/main/java/io/opentracing/Span.java @@ -13,6 +13,8 @@ */ package io.opentracing; +import java.util.Map; + /** * Represents an in-flight span in the opentracing system. * @@ -65,25 +67,72 @@ public interface Span extends AutoCloseable { Span setTag(String key, Number value); /** - * Add a new log event to the Span, accepting an event name string and an optional structured payload argument. + * Log key:value pairs to the Span with the current walltime timestamp. + * + *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. + * Caveat emptor. + * + *

A contrived example (using Guava, which is not required): + *

{@code
+     span.log(
+         ImmutableMap.Builder()
+         .put("event", "soft error")
+         .put("type", "cache timeout")
+         .put("waited.millis", 1500)
+         .build());
+     }
+ * + * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; + * some may also support arbitrary Objects. + * @return the Span, for chaining + * @see Span#log(String) + */ + Span log(Map fields); + + /** + * Like log(Map<String, Object>), but with an explicit timestamp. * - * If specified, the payload argument may be of any type and arbitrary size, though implementations are not - * required to retain all payload arguments (or even all parts of all payload arguments). + *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. + * Caveat emptor. * - * The timestamp of this log event is the current time. - **/ - Span log(String eventName, /* @Nullable */ Object payload); + * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the + * Span's start timestamp. + * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; + * some may also support arbitrary Objects. + * @return the Span, for chaining + * @see Span#log(long, String) + */ + Span log(long timestampMicroseconds, Map fields); /** - * Add a new log event to the Span, accepting an event name string and an optional structured payload argument. + * Record an event at the current walltime timestamp. * - * If specified, the payload argument may be of any type and arbitrary size, though implementations are not - * required to retain all payload arguments (or even all parts of all payload arguments). + * Shorthand for * - * The timestamp is specified manually here to represent a past log event. - * The timestamp in microseconds in UTC time. - **/ - Span log(long timestampMicroseconds, String eventName, /* @Nullable */ Object payload); + *

{@code
+     span.log(Collections.singletonMap("event", event));
+     }
+ * + * @param event the event value; often a stable identifier for a moment in the Span lifecycle + * @return the Span, for chaining + */ + Span log(String event); + + /** + * Record an event at a specific timestamp. + * + * Shorthand for + * + *
{@code
+     span.log(timestampMicroseconds, Collections.singletonMap("event", event));
+     }
+ * + * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the + * Span's start timestamp. + * @param event the event value; often a stable identifier for a moment in the Span lifecycle + * @return the Span, for chaining + */ + Span log(long timestampMicroseconds, String event); /** * Sets a baggage item in the Span (and its SpanContext) as a key/value pair. @@ -111,4 +160,19 @@ public interface Span extends AutoCloseable { * @return this Span instance, for chaining */ Span setOperationName(String operationName); + + /** + * @deprecated use {@link #log(Map)} like this + * {@code span.log(Map.of("event", "timeout"))} + * or + * {@code span.log(timestampMicroseconds, Map.of("event", "exception", "payload", stackTrace))} + **/ + Span log(String eventName, /* @Nullable */ Object payload); + /** + * @deprecated use {@link #log(Map)} like this + * {@code span.log(timestampMicroseconds, Map.of("event", "timeout"))} + * or + * {@code span.log(timestampMicroseconds, Map.of("event", "exception", "payload", stackTrace))} + **/ + Span log(long timestampMicroseconds, String eventName, /* @Nullable */ Object payload); } diff --git a/opentracing-api/src/test/java/io/opentracing/PlaygroundTest.java b/opentracing-api/src/test/java/io/opentracing/PlaygroundTest.java deleted file mode 100644 index 6f2df202..00000000 --- a/opentracing-api/src/test/java/io/opentracing/PlaygroundTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2016 The OpenTracing Authors - * - * 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 io.opentracing; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public final class PlaygroundTest { - - @Test - public void playground() { - - // Eventhough src/main is Java 7, we can use Java 8 types in tests - CompletableFuture fooCompleted = CompletableFuture.completedFuture("foo"); - assertThat(fooCompleted).isCompleted(); - - assertThat(Arrays.asList("foo", "bar")) - .filteredOn("bar"::equals) // We can use method references - .filteredOn(e -> !e.equals("foo")) // We can also use lambdas - .containsOnly("bar"); - - } -} diff --git a/opentracing-impl-java8/src/main/java/io/opentracing/AbstractSpan.java b/opentracing-impl-java8/src/main/java/io/opentracing/AbstractSpan.java index 2262d6fd..b6a5e344 100644 --- a/opentracing-impl-java8/src/main/java/io/opentracing/AbstractSpan.java +++ b/opentracing-impl-java8/src/main/java/io/opentracing/AbstractSpan.java @@ -15,11 +15,7 @@ import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; abstract class AbstractSpan implements Span, SpanContext { @@ -127,18 +123,46 @@ public final Map getBaggage() { } @Override - public final Span log(String message, /* @Nullable */ Object payload) { + public final Span log(String event) { + return log(nowMicros(), event); + } + + @Override + public final Span log(long timestampMicros, String event) { + return log(timestampMicros, Collections.singletonMap("event", event)); + } + + @Override + public final Span log(Map fields) { + return log(nowMicros(), fields); + } + + @Override + public final Span log(long timestampMicros, Map fields) { + Instant timestamp = Instant.ofEpochSecond(timestampMicros / 1000000, (timestampMicros % 1000000) * 1000); + logs.add(new LogData(timestamp, fields)); + return this; + } + + @Override + public final Span log(String event, /* @Nullable */ Object payload) { Instant now = Instant.now(); return log( TimeUnit.SECONDS.toMicros(now.getEpochSecond()) + TimeUnit.NANOSECONDS.toMicros(now.getNano()), - message, + event, payload); } @Override - public final Span log(long instantMicroseconds, String message, /* @Nullable */ Object payload) { - logs.add(new LogData(start, message, payload)); + public final Span log(long timestampMicros, String event, /* @Nullable */ Object payload) { + Instant timestamp = Instant.ofEpochSecond(timestampMicros / 1000000, (timestampMicros % 1000000) * 1000); + Map fields = new HashMap<>(); + fields.put("event", event); + if (payload != null) { + fields.put("payload", payload); + } + logs.add(new LogData(timestamp, fields)); return this; } @@ -148,13 +172,16 @@ public final List getLogs() { final class LogData { private final Instant time; - private final String message; - private final Object payload; + private final Map fields; - LogData(Instant time, String message, Object payload) { + LogData(Instant time, Map fields) { this.time = time; - this.message = message; - this.payload = payload; + this.fields = fields; } } + + static long nowMicros() { + Instant now = Instant.now(); + return (now.getEpochSecond() * 1000000) + (now.getNano() / 1000); + } } diff --git a/opentracing-impl/src/main/java/io/opentracing/NoopSpan.java b/opentracing-impl/src/main/java/io/opentracing/NoopSpan.java index 2e09264f..03c6aa99 100644 --- a/opentracing-impl/src/main/java/io/opentracing/NoopSpan.java +++ b/opentracing-impl/src/main/java/io/opentracing/NoopSpan.java @@ -39,34 +39,34 @@ public void finish() {} public void finish(long finishMicros) {} @Override - public void close() { - finish(); - } + public void close() { finish(); } @Override - public Span setTag(String key, String value) { - return this; - } + public Span setTag(String key, String value) { return this; } @Override - public Span setTag(String key, boolean value) { - return this; - } + public Span setTag(String key, boolean value) { return this; } @Override - public Span setTag(String key, Number value) { - return this; - } + public Span setTag(String key, Number value) { return this; } @Override - public Span log(String eventName, Object payload) { - return this; - } + public Span log(Map fields) { return this; } @Override - public Span log(long timestampMicroseconds, String eventName, Object payload) { - return this; - } + public Span log(long timestampMicroseconds, Map fields) { return this; } + + @Override + public Span log(String event) { return this; } + + @Override + public Span log(long timestampMicroseconds, String event) { return this; } + + @Override + public Span log(String eventName, Object payload) { return this; } + + @Override + public Span log(long timestampMicroseconds, String eventName, Object payload) { return this; } @Override public Span setBaggageItem(String key, String value) { return this; } @@ -75,8 +75,6 @@ public Span log(long timestampMicroseconds, String eventName, Object payload) { public String getBaggageItem(String key) { return null; } @Override - public Span setOperationName(String operationName) { - return this; - } + public Span setOperationName(String operationName) { return this; } } diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java index 15944487..ca062cb4 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -13,10 +13,7 @@ */ package io.opentracing.mock; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import io.opentracing.Span; @@ -92,7 +89,7 @@ public synchronized MockContext context() { @Override public void finish() { - this.finish(System.nanoTime() / 1000); + this.finish(nowMicros()); } @Override @@ -124,15 +121,39 @@ public synchronized Span setTag(String key, Number value) { return this; } + @Override + public final Span log(Map fields) { + return log(nowMicros(), fields); + } + @Override + public final Span log(long timestampMicros, Map fields) { + this.logEntries.add(new LogEntry(timestampMicros, fields)); + return this; + } + + @Override + public Span log(String event) { + return this.log(nowMicros(), event); + } + + @Override + public Span log(long timestampMicroseconds, String event) { + return this.log(timestampMicroseconds, Collections.singletonMap("event", event)); + } + @Override public Span log(String eventName, Object payload) { - return this.log(System.nanoTime() / 1000, eventName, payload); + return this.log(nowMicros(), eventName, payload); } @Override public synchronized Span log(long timestampMicroseconds, String eventName, Object payload) { - this.logEntries.add(new LogEntry(timestampMicroseconds, eventName, payload)); - return this; + Map fields = new HashMap<>(); + fields.put("event", eventName); + if (payload != null) { + fields.put("payload", payload); + } + return this.log(timestampMicroseconds, fields); } @Override @@ -192,25 +213,19 @@ public Iterable> baggageItems() { public static final class LogEntry { private final long timestampMicros; - private final String eventName; - private final Object payload; + private final Map fields; - public LogEntry(long timestampMicros, String eventName, Object payload) { + public LogEntry(long timestampMicros, Map fields) { this.timestampMicros = timestampMicros; - this.eventName = eventName; - this.payload = payload; + this.fields = fields; } public long timestampMicros() { return timestampMicros; } - public String eventName() { - return eventName; - } - - public Object payload() { - return payload; + public Map fields() { + return fields; } } @@ -237,4 +252,8 @@ public Object payload() { static long nextId() { return nextId.addAndGet(1); } + + static long nowMicros() { + return System.currentTimeMillis() * 1000; + } } diff --git a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java index 5843a87f..178c0caf 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -16,6 +16,7 @@ import io.opentracing.Span; import org.junit.Test; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,7 +32,14 @@ public void testRootSpan() { Span span = tracer.buildSpan("tester").withStartTimestamp(1000).start(); span.setTag("string", "foo"); span.setTag("int", 7); + // Old style logging: span.log(1001, "event name", tracer); + // New style logging: + Map fields = new HashMap<>(); + fields.put("f1", 4); + fields.put("f2", "two"); + span.log(1002, fields); + span.log(1003, "event name"); span.finish(2000); } List finishedSpans = tracer.finishedSpans(); @@ -50,11 +58,24 @@ public void testRootSpan() { assertEquals(7, tags.get("int")); assertEquals("foo", tags.get("string")); List logs = finishedSpan.logEntries(); - assertEquals(1, logs.size()); - MockSpan.LogEntry log = logs.get(0); - assertEquals(1001, log.timestampMicros()); - assertEquals("event name", log.eventName()); - assertEquals(tracer, log.payload()); + assertEquals(3, logs.size()); + { + MockSpan.LogEntry log = logs.get(0); + assertEquals(1001, log.timestampMicros()); + assertEquals("event name", log.fields().get("event")); + assertEquals(tracer, log.fields().get("payload")); + } + { + MockSpan.LogEntry log = logs.get(1); + assertEquals(1002, log.timestampMicros()); + assertEquals(4, log.fields().get("f1")); + assertEquals("two", log.fields().get("f2")); + } + { + MockSpan.LogEntry log = logs.get(2); + assertEquals(1003, log.timestampMicros()); + assertEquals("event name", log.fields().get("event")); + } } @Test