From 01a07b51a1b0068b3cca29d77767c26bccdd5d7c Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:39:33 -0500 Subject: [PATCH] Add event API (#4781) * Add event API * Log when emitting event without domain, add javadoc example to Logger --- .../opentelemetry/api/logs/DefaultLogger.java | 29 +++++++-- .../api/logs/DefaultLoggerProvider.java | 21 +++++-- .../opentelemetry/api/logs/EventBuilder.java | 16 +++++ .../io/opentelemetry/api/logs/Logger.java | 39 +++++++++++- .../opentelemetry/api/logs/LoggerBuilder.java | 14 +++++ .../api/logs/LoggerProvider.java | 7 ++- .../api/logs/DefaultLoggerProviderTest.java | 22 ++++++- .../api/logs/DefaultLoggerTest.java | 63 ++++++++++++++++++- .../sdk/logs/SdkLogRecordBuilder.java | 19 +++--- .../io/opentelemetry/sdk/logs/SdkLogger.java | 44 +++++++++++++ .../sdk/logs/SdkLoggerBuilder.java | 11 +++- .../opentelemetry/sdk/logs/SdkLoggerTest.java | 43 +++++++++++++ 12 files changed, 300 insertions(+), 28 deletions(-) create mode 100644 api/logs/src/main/java/io/opentelemetry/api/logs/EventBuilder.java diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java b/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java index 2fbd6b72d7..7f86ff9cd9 100644 --- a/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java @@ -6,20 +6,37 @@ package io.opentelemetry.api.logs; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.internal.ValidationUtil; import io.opentelemetry.context.Context; import java.time.Instant; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; class DefaultLogger implements Logger { - private static final Logger INSTANCE = new DefaultLogger(); + private static final Logger INSTANCE_WITH_DOMAIN = new DefaultLogger(/* hasDomain= */ true); + private static final Logger INSTANCE_NO_DOMAIN = new DefaultLogger(/* hasDomain= */ false); - private static final LogRecordBuilder NOOP_LOG_RECORD_BUILDER = new NoopLogRecordBuilder(); + private static final EventBuilder NOOP_LOG_RECORD_BUILDER = new NoopLogRecordBuilder(); - private DefaultLogger() {} + private final boolean hasDomain; - static Logger getInstance() { - return INSTANCE; + private DefaultLogger(boolean hasDomain) { + this.hasDomain = hasDomain; + } + + static Logger getInstance(boolean hasDomain) { + return hasDomain ? INSTANCE_WITH_DOMAIN : INSTANCE_NO_DOMAIN; + } + + @Override + public EventBuilder eventBuilder(String eventName) { + if (!hasDomain) { + ValidationUtil.log( + "Cannot emit event from Logger without event domain. Please use LoggerBuilder#setEventDomain(String) when obtaining Logger.", + Level.WARNING); + } + return NOOP_LOG_RECORD_BUILDER; } @Override @@ -27,7 +44,7 @@ class DefaultLogger implements Logger { return NOOP_LOG_RECORD_BUILDER; } - private static final class NoopLogRecordBuilder implements LogRecordBuilder { + private static final class NoopLogRecordBuilder implements EventBuilder { private NoopLogRecordBuilder() {} diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLoggerProvider.java b/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLoggerProvider.java index 25a18f529b..035fb4bc02 100644 --- a/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLoggerProvider.java +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/DefaultLoggerProvider.java @@ -8,7 +8,10 @@ package io.opentelemetry.api.logs; class DefaultLoggerProvider implements LoggerProvider { private static final LoggerProvider INSTANCE = new DefaultLoggerProvider(); - private static final LoggerBuilder NOOP_BUILDER = new NoopLoggerBuilder(); + private static final LoggerBuilder NOOP_BUILDER_WITH_DOMAIN = + new NoopLoggerBuilder(/* hasDomain= */ true); + private static final LoggerBuilder NOOP_BUILDER_NO_DOMAIN = + new NoopLoggerBuilder(/* hasDomain= */ false); private DefaultLoggerProvider() {} @@ -18,12 +21,22 @@ class DefaultLoggerProvider implements LoggerProvider { @Override public LoggerBuilder loggerBuilder(String instrumentationScopeName) { - return NOOP_BUILDER; + return NOOP_BUILDER_NO_DOMAIN; } private static class NoopLoggerBuilder implements LoggerBuilder { - private NoopLoggerBuilder() {} + private final boolean hasDomain; + + private NoopLoggerBuilder(boolean hasDomain) { + this.hasDomain = hasDomain; + } + + @Override + @SuppressWarnings("BuilderReturnThis") + public LoggerBuilder setEventDomain(String eventDomain) { + return eventDomain == null ? NOOP_BUILDER_NO_DOMAIN : NOOP_BUILDER_WITH_DOMAIN; + } @Override public LoggerBuilder setSchemaUrl(String schemaUrl) { @@ -37,7 +50,7 @@ class DefaultLoggerProvider implements LoggerProvider { @Override public Logger build() { - return DefaultLogger.getInstance(); + return DefaultLogger.getInstance(hasDomain); } } } diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/EventBuilder.java b/api/logs/src/main/java/io/opentelemetry/api/logs/EventBuilder.java new file mode 100644 index 0000000000..e7dbda842f --- /dev/null +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/EventBuilder.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.logs; + +/** + * Used to construct an emit events from a {@link Logger}. + * + *

An event is a log record with attributes for {@code event.domain} and {@code event.name}. + * + *

Obtain a {@link Logger#eventBuilder(String)}, add properties using the setters, and emit the + * log record by calling {@link #emit()}. + */ +public interface EventBuilder extends LogRecordBuilder {} diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/Logger.java b/api/logs/src/main/java/io/opentelemetry/api/logs/Logger.java index 900a2a5172..0ccc8227f9 100644 --- a/api/logs/src/main/java/io/opentelemetry/api/logs/Logger.java +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/Logger.java @@ -10,12 +10,47 @@ import javax.annotation.concurrent.ThreadSafe; /** * A {@link Logger} is the entry point into a log pipeline. * - *

Obtain a {@link #logRecordBuilder()}, add properties using the setters, and emit it via {@link - * LogRecordBuilder#emit()}. + *

Obtain a {@link EventBuilder} or {@link #logRecordBuilder()}, add properties using the + * setters, and emit it via {@link LogRecordBuilder#emit()}. + * + *

Example usage emitting events: + * + *

{@code
+ * class MyClass {
+ *   private final Logger eventLogger = openTelemetryLoggerProvider.loggerBuilder("instrumentation-library-name")
+ *     .setInstrumentationVersion("1.0.0")
+ *     .setEventDomain("acme.observability")
+ *     .build();
+ *
+ *   void doWork() {
+ *     eventLogger.eventBuilder("my-event")
+ *       .setAllAttributes(Attributes.builder()
+ *         .put("key1", "value1")
+ *         .put("key2", "value2")
+ *         .build())
+ *       .emit();
+ *     // do work
+ *   }
+ * }
+ * }
*/ @ThreadSafe public interface Logger { + /** + * Return a {@link EventBuilder} to emit an event. + * + *

NOTE: this API MUST only be called on {@link Logger}s which have been assigned an + * {@link LoggerBuilder#setEventDomain(String) event domain}. + * + *

Build the event using the {@link EventBuilder} setters, and emit via {@link + * EventBuilder#emit()}. + * + * @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. + */ + EventBuilder eventBuilder(String eventName); + /** * Return a {@link LogRecordBuilder} to emit a log record. * diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerBuilder.java b/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerBuilder.java index cf3d781823..5a81d52bf4 100644 --- a/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerBuilder.java +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerBuilder.java @@ -8,6 +8,20 @@ package io.opentelemetry.api.logs; /** Builder class for creating {@link Logger} instances. */ public interface LoggerBuilder { + /** + * Set the event domain of the resulting {@link Logger}. + * + *

NOTE: Event domain is required to use {@link Logger#eventBuilder(String)}. + * + *

The event domain will be included in the {@code event.domain} attribute of every event + * produced by the resulting {@link Logger}. + * + * @param eventDomain The event domain, which acts as a namespace for event names. Within a + * particular event domain, event name defines a particular class or type of event. + * @return this + */ + LoggerBuilder setEventDomain(String eventDomain); + /** * Assign an OpenTelemetry schema URL to the resulting {@link Logger}. * diff --git a/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerProvider.java b/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerProvider.java index a748a59009..fc55f81052 100644 --- a/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerProvider.java +++ b/api/logs/src/main/java/io/opentelemetry/api/logs/LoggerProvider.java @@ -14,12 +14,13 @@ import javax.annotation.concurrent.ThreadSafe; *

The OpenTelemetry logging API exists to satisfy two use cases: * *

    + *
  1. Enable emitting structured events + * via {@link Logger#eventBuilder(String)}. Requires assigning an {@link + * LoggerBuilder#setEventDomain(String) event domain} to the {@link Logger}. *
  2. Enable the creation of log appenders, which bridge logs from other log frameworks (e.g. * SLF4J, Log4j, JUL, Logback, etc) into OpenTelemetry via {@link Logger#logRecordBuilder()}. * It is NOT a replacement log framework. - *
  3. Enable emitting structured events. - * TODO: add link when event API is added. *
* * @see Logger diff --git a/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerProviderTest.java b/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerProviderTest.java index c4ca0fa415..3f47843917 100644 --- a/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerProviderTest.java +++ b/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerProviderTest.java @@ -26,6 +26,26 @@ class DefaultLoggerProviderTest { .setSchemaUrl("http://schema.com") .build()) .doesNotThrowAnyException(); - ; + + assertThatCode(() -> provider.loggerBuilder("scope-name").build().logRecordBuilder()) + .doesNotThrowAnyException(); + assertThatCode(() -> provider.loggerBuilder("scope-name").build().eventBuilder("event-name")) + .doesNotThrowAnyException(); + assertThatCode( + () -> + provider + .loggerBuilder("scope-name") + .setEventDomain("event-domain") + .build() + .logRecordBuilder()) + .doesNotThrowAnyException(); + assertThatCode( + () -> + provider + .loggerBuilder("scope-name") + .setEventDomain("event-domain") + .build() + .eventBuilder("event-name")) + .doesNotThrowAnyException(); } } diff --git a/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java b/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java index bf4522f51e..e27c5304e0 100644 --- a/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java +++ b/api/logs/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java @@ -5,24 +5,31 @@ package io.opentelemetry.api.logs; +import static io.opentelemetry.api.internal.ValidationUtil.API_USAGE_LOGGER_NAME; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.context.Context; import java.time.Instant; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.event.LoggingEvent; class DefaultLoggerTest { - private static final Logger logger = DefaultLogger.getInstance(); + @RegisterExtension + LogCapturer apiUsageLogs = LogCapturer.create().captureForLogger(API_USAGE_LOGGER_NAME); @Test void buildAndEmit() { + // Logger with no event.domain assertThatCode( () -> - logger + DefaultLogger.getInstance(true) .logRecordBuilder() .setEpoch(100, TimeUnit.SECONDS) .setEpoch(Instant.now()) @@ -34,5 +41,57 @@ class DefaultLoggerTest { .setAllAttributes(Attributes.builder().put("key2", "value2").build()) .emit()) .doesNotThrowAnyException(); + assertThatCode( + () -> + DefaultLogger.getInstance(true) + .eventBuilder("event-name") + .setEpoch(100, TimeUnit.SECONDS) + .setEpoch(Instant.now()) + .setContext(Context.root()) + .setSeverity(Severity.DEBUG) + .setSeverityText("debug") + .setBody("body") + .setAttribute(AttributeKey.stringKey("key1"), "value1") + .setAllAttributes(Attributes.builder().put("key2", "value2").build()) + .emit()) + .doesNotThrowAnyException(); + assertThat(apiUsageLogs.getEvents()).isEmpty(); + + // Logger with event.domain + assertThatCode( + () -> + DefaultLogger.getInstance(false) + .logRecordBuilder() + .setEpoch(100, TimeUnit.SECONDS) + .setEpoch(Instant.now()) + .setContext(Context.root()) + .setSeverity(Severity.DEBUG) + .setSeverityText("debug") + .setBody("body") + .setAttribute(AttributeKey.stringKey("key1"), "value1") + .setAllAttributes(Attributes.builder().put("key2", "value2").build()) + .emit()) + .doesNotThrowAnyException(); + assertThatCode( + () -> + DefaultLogger.getInstance(false) + .eventBuilder("event-name") + .setEpoch(100, TimeUnit.SECONDS) + .setEpoch(Instant.now()) + .setContext(Context.root()) + .setSeverity(Severity.DEBUG) + .setSeverityText("debug") + .setBody("body") + .setAttribute(AttributeKey.stringKey("key1"), "value1") + .setAllAttributes(Attributes.builder().put("key2", "value2").build()) + .emit()) + .doesNotThrowAnyException(); + assertThat(apiUsageLogs.getEvents()) + .hasSize(1) + .extracting(LoggingEvent::getMessage) + .allMatch( + log -> + log.equals( + "Cannot emit event from Logger without event domain. Please use LoggerBuilder#setEventDomain(String) when obtaining Logger.")); } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java index 04b829276f..0c333cc6fc 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.logs; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.logs.EventBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; @@ -18,8 +19,8 @@ import java.time.Instant; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; -/** SDK implementation of {@link LogRecordBuilder}. */ -final class SdkLogRecordBuilder implements LogRecordBuilder { +/** SDK implementation of {@link EventBuilder} and {@link LogRecordBuilder}. */ +final class SdkLogRecordBuilder implements EventBuilder { private final LoggerSharedState loggerSharedState; private final LogLimits logLimits; @@ -40,43 +41,43 @@ final class SdkLogRecordBuilder implements LogRecordBuilder { } @Override - public LogRecordBuilder setEpoch(long timestamp, TimeUnit unit) { + public SdkLogRecordBuilder setEpoch(long timestamp, TimeUnit unit) { this.epochNanos = unit.toNanos(timestamp); return this; } @Override - public LogRecordBuilder setEpoch(Instant instant) { + public SdkLogRecordBuilder setEpoch(Instant instant) { this.epochNanos = TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(); return this; } @Override - public LogRecordBuilder setContext(Context context) { + public SdkLogRecordBuilder setContext(Context context) { this.spanContext = Span.fromContext(context).getSpanContext(); return this; } @Override - public LogRecordBuilder setSeverity(Severity severity) { + public SdkLogRecordBuilder setSeverity(Severity severity) { this.severity = severity; return this; } @Override - public LogRecordBuilder setSeverityText(String severityText) { + public SdkLogRecordBuilder setSeverityText(String severityText) { this.severityText = severityText; return this; } @Override - public LogRecordBuilder setBody(String body) { + public SdkLogRecordBuilder setBody(String body) { this.body = Body.string(body); return this; } @Override - public LogRecordBuilder setAttribute(AttributeKey key, T value) { + public SdkLogRecordBuilder setAttribute(AttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java index efcb7882ad..ef30a9b2fd 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java @@ -5,20 +5,64 @@ package io.opentelemetry.sdk.logs; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.internal.ValidationUtil; +import io.opentelemetry.api.logs.EventBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import java.util.logging.Level; +import javax.annotation.Nullable; /** SDK implementation of {@link Logger}. */ final class SdkLogger implements Logger { + // Obtain a noop logger with the domain set so that we can obtain noop EventBuilder without + // generating additional warning logs + private static final Logger NOOP_LOGGER_WITH_DOMAIN = + LoggerProvider.noop().loggerBuilder("unused").setEventDomain("unused").build(); + private final LoggerSharedState loggerSharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; + @Nullable private final String eventDomain; SdkLogger( LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) { + this(loggerSharedState, instrumentationScopeInfo, null); + } + + SdkLogger( + LoggerSharedState loggerSharedState, + InstrumentationScopeInfo instrumentationScopeInfo, + @Nullable String eventDomain) { this.loggerSharedState = loggerSharedState; this.instrumentationScopeInfo = instrumentationScopeInfo; + this.eventDomain = eventDomain; + } + + /** + * Return a logger identical to {@code this} ensuring the {@link #eventDomain} is equal to {@code + * eventDomain}. If {@link #eventDomain} is not equal, creates a new instance. + */ + SdkLogger withEventDomain(String eventDomain) { + if (!eventDomain.equals(this.eventDomain)) { + return new SdkLogger(loggerSharedState, instrumentationScopeInfo, eventDomain); + } + return this; + } + + @Override + public EventBuilder eventBuilder(String eventName) { + if (eventDomain == null) { + ValidationUtil.log( + "Cannot emit event from Logger without event domain. Please use LoggerBuilder#setEventDomain(String) when obtaining Logger.", + Level.WARNING); + return NOOP_LOGGER_WITH_DOMAIN.eventBuilder(eventName); + } + return new SdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo) + .setAttribute(AttributeKey.stringKey("event.domain"), eventDomain) + .setAttribute(AttributeKey.stringKey("event.name"), eventName); } @Override diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerBuilder.java index 99427a2cc2..8d3eeaeb7c 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerBuilder.java @@ -9,17 +9,25 @@ import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder; import io.opentelemetry.sdk.internal.ComponentRegistry; +import javax.annotation.Nullable; final class SdkLoggerBuilder implements LoggerBuilder { private final ComponentRegistry registry; private final InstrumentationScopeInfoBuilder scopeBuilder; + @Nullable private String eventDomain; SdkLoggerBuilder(ComponentRegistry registry, String instrumentationScopeName) { this.registry = registry; this.scopeBuilder = InstrumentationScopeInfo.builder(instrumentationScopeName); } + @Override + public LoggerBuilder setEventDomain(String eventDomain) { + this.eventDomain = eventDomain; + return this; + } + @Override public SdkLoggerBuilder setSchemaUrl(String schemaUrl) { scopeBuilder.setSchemaUrl(schemaUrl); @@ -34,6 +42,7 @@ final class SdkLoggerBuilder implements LoggerBuilder { @Override public SdkLogger build() { - return registry.get(scopeBuilder.build()); + SdkLogger logger = registry.get(scopeBuilder.build()); + return eventDomain == null ? logger : logger.withEventDomain(eventDomain); } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java index e9c781f475..d6af99c66e 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java @@ -9,6 +9,7 @@ import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; import static io.opentelemetry.api.common.AttributeKey.longArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.internal.ValidationUtil.API_USAGE_LOGGER_NAME; import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -17,6 +18,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -30,9 +32,14 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.event.LoggingEvent; class SdkLoggerTest { + @RegisterExtension + LogCapturer apiUsageLogs = LogCapturer.create().captureForLogger(API_USAGE_LOGGER_NAME); + @Test void logRecordBuilder() { LoggerSharedState state = mock(LoggerSharedState.class); @@ -134,4 +141,40 @@ class SdkLoggerTest { verify(logRecordProcessor, never()).onEmit(any()); } + + @Test + void eventBuilder() { + AtomicReference seenLog = new AtomicReference<>(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder().addLogRecordProcessor(seenLog::set).build(); + + // Emit event from logger with name and add event domain + loggerProvider + .loggerBuilder("test") + .setEventDomain("event-domain") + .build() + .eventBuilder("event-name") + .emit(); + + assertThat(seenLog.get().toLogRecordData()) + .hasAttributes( + Attributes.builder() + .put("event.domain", "event-domain") + .put("event.name", "event-name") + .build()); + + assertThat(apiUsageLogs.getEvents()).isEmpty(); + seenLog.set(null); + + // Emit event from logger with name and no event domain + loggerProvider.get("test").eventBuilder("event-name"); + + assertThat(apiUsageLogs.getEvents()) + .hasSize(1) + .extracting(LoggingEvent::getMessage) + .allMatch( + log -> + log.equals( + "Cannot emit event from Logger without event domain. Please use LoggerBuilder#setEventDomain(String) when obtaining Logger.")); + } }