Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow events to be emitted with timestamp #5928

Merged
merged 9 commits into from
Nov 9, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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() {}
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,7 +26,10 @@
*/
public final class SdkEventEmitterProvider implements EventEmitterProvider {

private static final String DEFAULT_EVENT_DOMAIN = "unknown";
static final AttributeKey<String> EVENT_DOMAIN = AttributeKey.stringKey("event.domain");
static final AttributeKey<String> EVENT_NAME = AttributeKey.stringKey("event.name");

static final String DEFAULT_EVENT_DOMAIN = "unknown";

private final LoggerProvider delegateLoggerProvider;
private final Clock clock;
Expand Down Expand Up @@ -98,9 +103,6 @@ public EventEmitter build() {

private static class SdkEventEmitter implements EventEmitter {

private static final AttributeKey<String> EVENT_DOMAIN = AttributeKey.stringKey("event.domain");
private static final AttributeKey<String> EVENT_NAME = AttributeKey.stringKey("event.name");

private final Clock clock;
private final Logger delegateLogger;
private final String eventDomain;
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
}
}