diff --git a/api/events/src/main/java/io/opentelemetry/api/events/DefaultEventEmitter.java b/api/events/src/main/java/io/opentelemetry/api/events/DefaultEventEmitter.java index 2be4164d010..8d9a81f8e7f 100644 --- a/api/events/src/main/java/io/opentelemetry/api/events/DefaultEventEmitter.java +++ b/api/events/src/main/java/io/opentelemetry/api/events/DefaultEventEmitter.java @@ -6,6 +6,8 @@ package io.opentelemetry.api.events; import io.opentelemetry.api.common.Attributes; +import java.time.Instant; +import java.util.concurrent.TimeUnit; class DefaultEventEmitter implements EventEmitter { @@ -19,4 +21,27 @@ static EventEmitter getInstance() { @Override public void emit(String eventName, Attributes attributes) {} + + @Override + public EventBuilder builder(String eventName, Attributes attributes) { + return NoOpEventBuilder.INSTANCE; + } + + private static class NoOpEventBuilder implements EventBuilder { + + public static final EventBuilder INSTANCE = new NoOpEventBuilder(); + + @Override + public EventBuilder setTimestamp(long timestamp, TimeUnit unit) { + return this; + } + + @Override + public EventBuilder setTimestamp(Instant instant) { + return this; + } + + @Override + public void emit() {} + } } diff --git a/api/events/src/main/java/io/opentelemetry/api/events/EventBuilder.java b/api/events/src/main/java/io/opentelemetry/api/events/EventBuilder.java new file mode 100644 index 00000000000..a9acabbca6c --- /dev/null +++ b/api/events/src/main/java/io/opentelemetry/api/events/EventBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.events; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +/** The EventBuilder is used to {@link #emit()} events. */ +public interface EventBuilder { + + /** + * Set the epoch {@code timestamp} for the event, using the timestamp and unit. + * + *

The {@code timestamp} is the time at which the event occurred. If unset, it will be set to + * the current time when {@link #emit()} is called. + */ + EventBuilder setTimestamp(long timestamp, TimeUnit unit); + + /** + * Set the epoch {@code timestamp} for the event, using the instant. + * + *

The {@code timestamp} is the time at which the event occurred. If unset, it will be set to + * the current time when {@link #emit()} is called. + */ + EventBuilder setTimestamp(Instant instant); + + /** Emit an event. */ + void emit(); +} diff --git a/api/events/src/main/java/io/opentelemetry/api/events/EventEmitter.java b/api/events/src/main/java/io/opentelemetry/api/events/EventEmitter.java index 69df8407f43..a8dc47c83d7 100644 --- a/api/events/src/main/java/io/opentelemetry/api/events/EventEmitter.java +++ b/api/events/src/main/java/io/opentelemetry/api/events/EventEmitter.java @@ -40,4 +40,13 @@ public interface EventEmitter { * @param attributes attributes associated with the event */ void emit(String eventName, Attributes attributes); + + /** + * Return a {@link EventBuilder} to emit an event. + * + * @param eventName the event name, which acts as a classifier for events. Within a particular + * event domain, event name defines a particular class or type of event. + * @param attributes attributes associated with the event + */ + EventBuilder builder(String eventName, Attributes attributes); } diff --git a/api/events/src/test/java/io/opentelemetry/api/events/DefaultEventEmitterTest.java b/api/events/src/test/java/io/opentelemetry/api/events/DefaultEventEmitterTest.java index fb6bcabfc91..460cb1583ac 100644 --- a/api/events/src/test/java/io/opentelemetry/api/events/DefaultEventEmitterTest.java +++ b/api/events/src/test/java/io/opentelemetry/api/events/DefaultEventEmitterTest.java @@ -8,6 +8,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import io.opentelemetry.api.common.Attributes; +import java.time.Instant; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class DefaultEventEmitterTest { @@ -22,4 +24,18 @@ void emit() { .emit("event-name", Attributes.builder().put("key1", "value1").build())) .doesNotThrowAnyException(); } + + @Test + void builder() { + Attributes attributes = Attributes.builder().put("key1", "value1").build(); + EventEmitter emitter = DefaultEventEmitter.getInstance(); + assertThatCode( + () -> + emitter + .builder("myEvent", attributes) + .setTimestamp(123456L, TimeUnit.NANOSECONDS) + .setTimestamp(Instant.now()) + .emit()) + .doesNotThrowAnyException(); + } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilder.java new file mode 100644 index 00000000000..a79b09babc3 --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs.internal; + +import io.opentelemetry.api.events.EventBuilder; +import io.opentelemetry.api.logs.LogRecordBuilder; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +class SdkEventBuilder implements EventBuilder { + private final LogRecordBuilder logRecordBuilder; + private final String eventDomain; + private final String eventName; + + SdkEventBuilder(LogRecordBuilder logRecordBuilder, String eventDomain, String eventName) { + this.logRecordBuilder = logRecordBuilder; + this.eventDomain = eventDomain; + this.eventName = eventName; + } + + @Override + public EventBuilder setTimestamp(long timestamp, TimeUnit unit) { + this.logRecordBuilder.setTimestamp(timestamp, unit); + return this; + } + + @Override + public EventBuilder setTimestamp(Instant instant) { + this.logRecordBuilder.setTimestamp(instant); + return this; + } + + @Override + public void emit() { + SdkEventEmitterProvider.addEventNameAndDomain(logRecordBuilder, eventDomain, eventName); + logRecordBuilder.emit(); + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProvider.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProvider.java index 18d1e434824..1cc6768667e 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProvider.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProvider.java @@ -7,9 +7,11 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.events.EventBuilder; import io.opentelemetry.api.events.EventEmitter; import io.opentelemetry.api.events.EventEmitterBuilder; import io.opentelemetry.api.events.EventEmitterProvider; +import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.api.logs.LoggerProvider; @@ -24,7 +26,10 @@ */ public final class SdkEventEmitterProvider implements EventEmitterProvider { - private static final String DEFAULT_EVENT_DOMAIN = "unknown"; + static final AttributeKey EVENT_DOMAIN = AttributeKey.stringKey("event.domain"); + static final AttributeKey EVENT_NAME = AttributeKey.stringKey("event.name"); + + static final String DEFAULT_EVENT_DOMAIN = "unknown"; private final LoggerProvider delegateLoggerProvider; private final Clock clock; @@ -98,9 +103,6 @@ public EventEmitter build() { private static class SdkEventEmitter implements EventEmitter { - private static final AttributeKey EVENT_DOMAIN = AttributeKey.stringKey("event.domain"); - private static final AttributeKey EVENT_NAME = AttributeKey.stringKey("event.name"); - private final Clock clock; private final Logger delegateLogger; private final String eventDomain; @@ -111,15 +113,31 @@ private SdkEventEmitter(Clock clock, Logger delegateLogger, String eventDomain) this.eventDomain = eventDomain; } + @Override + public EventBuilder builder(String eventName, Attributes attributes) { + return new SdkEventBuilder( + delegateLogger + .logRecordBuilder() + .setTimestamp(clock.now(), TimeUnit.NANOSECONDS) + .setAllAttributes(attributes), + eventDomain, + eventName); + } + @Override public void emit(String eventName, Attributes attributes) { - delegateLogger - .logRecordBuilder() - .setTimestamp(clock.now(), TimeUnit.NANOSECONDS) - .setAllAttributes(attributes) - .setAttribute(EVENT_DOMAIN, eventDomain) - .setAttribute(EVENT_NAME, eventName) - .emit(); + LogRecordBuilder logRecordBuilder = + delegateLogger + .logRecordBuilder() + .setTimestamp(clock.now(), TimeUnit.NANOSECONDS) + .setAllAttributes(attributes); + addEventNameAndDomain(logRecordBuilder, eventDomain, eventName); + logRecordBuilder.emit(); } } + + static void addEventNameAndDomain( + LogRecordBuilder logRecordBuilder, String eventDomain, String eventName) { + logRecordBuilder.setAttribute(EVENT_DOMAIN, eventDomain).setAttribute(EVENT_NAME, eventName); + } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java new file mode 100644 index 00000000000..2115463a771 --- /dev/null +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.logs.LogRecordBuilder; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class SdkEventBuilderTest { + + @Test + void emit() { + String eventDomain = "mydomain"; + String eventName = "banana"; + + LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); + when(logRecordBuilder.setTimestamp(anyLong(), any())).thenReturn(logRecordBuilder); + when(logRecordBuilder.setAttribute(any(), any())).thenReturn(logRecordBuilder); + + Instant instant = Instant.now(); + new SdkEventBuilder(logRecordBuilder, eventDomain, eventName) + .setTimestamp(123456L, TimeUnit.NANOSECONDS) + .setTimestamp(instant) + .emit(); + verify(logRecordBuilder).setAttribute(stringKey("event.domain"), eventDomain); + verify(logRecordBuilder).setAttribute(stringKey("event.name"), eventName); + verify(logRecordBuilder).setTimestamp(123456L, TimeUnit.NANOSECONDS); + verify(logRecordBuilder).setTimestamp(instant); + verify(logRecordBuilder).emit(); + } +} diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProviderTest.java index 964c67e64c1..7f22b1376a8 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventEmitterProviderTest.java @@ -5,16 +5,19 @@ package io.opentelemetry.sdk.logs.internal; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.events.EventEmitter; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.ReadWriteLogRecord; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; @@ -91,4 +94,27 @@ void emit_NoDomain() { .put("event.name", "event-name") .build()); } + + @Test + void builder() { + long yesterday = System.nanoTime() - TimeUnit.DAYS.toNanos(1); + Attributes attributes = Attributes.of(stringKey("foo"), "bar"); + + EventEmitter emitter = eventEmitterProvider.eventEmitterBuilder("test-scope").build(); + + emitter.builder("testing", attributes).setTimestamp(yesterday, TimeUnit.NANOSECONDS).emit(); + verifySeen(yesterday, attributes); + } + + private void verifySeen(long timestamp, Attributes attributes) { + assertThat(seenLog.get().toLogRecordData()) + .hasResource(RESOURCE) + .hasInstrumentationScope(InstrumentationScopeInfo.create("test-scope")) + .hasTimestamp(timestamp) + .hasAttributes( + attributes.toBuilder() + .put("event.domain", "unknown") + .put("event.name", "testing") + .build()); + } }