Add event API (#4781)
* Add event API * Log when emitting event without domain, add javadoc example to Logger
This commit is contained in:
parent
29fe7caffe
commit
01a07b51a1
|
@ -6,20 +6,37 @@
|
||||||
package io.opentelemetry.api.logs;
|
package io.opentelemetry.api.logs;
|
||||||
|
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
|
import io.opentelemetry.api.internal.ValidationUtil;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
class DefaultLogger implements Logger {
|
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() {
|
private DefaultLogger(boolean hasDomain) {
|
||||||
return INSTANCE;
|
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
|
@Override
|
||||||
|
@ -27,7 +44,7 @@ class DefaultLogger implements Logger {
|
||||||
return NOOP_LOG_RECORD_BUILDER;
|
return NOOP_LOG_RECORD_BUILDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class NoopLogRecordBuilder implements LogRecordBuilder {
|
private static final class NoopLogRecordBuilder implements EventBuilder {
|
||||||
|
|
||||||
private NoopLogRecordBuilder() {}
|
private NoopLogRecordBuilder() {}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,10 @@ package io.opentelemetry.api.logs;
|
||||||
class DefaultLoggerProvider implements LoggerProvider {
|
class DefaultLoggerProvider implements LoggerProvider {
|
||||||
|
|
||||||
private static final LoggerProvider INSTANCE = new DefaultLoggerProvider();
|
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() {}
|
private DefaultLoggerProvider() {}
|
||||||
|
|
||||||
|
@ -18,12 +21,22 @@ class DefaultLoggerProvider implements LoggerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoggerBuilder loggerBuilder(String instrumentationScopeName) {
|
public LoggerBuilder loggerBuilder(String instrumentationScopeName) {
|
||||||
return NOOP_BUILDER;
|
return NOOP_BUILDER_NO_DOMAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NoopLoggerBuilder implements LoggerBuilder {
|
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
|
@Override
|
||||||
public LoggerBuilder setSchemaUrl(String schemaUrl) {
|
public LoggerBuilder setSchemaUrl(String schemaUrl) {
|
||||||
|
@ -37,7 +50,7 @@ class DefaultLoggerProvider implements LoggerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Logger build() {
|
public Logger build() {
|
||||||
return DefaultLogger.getInstance();
|
return DefaultLogger.getInstance(hasDomain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}.
|
||||||
|
*
|
||||||
|
* <p>An event is a log record with attributes for {@code event.domain} and {@code event.name}.
|
||||||
|
*
|
||||||
|
* <p>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 {}
|
|
@ -10,12 +10,47 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
/**
|
/**
|
||||||
* A {@link Logger} is the entry point into a log pipeline.
|
* A {@link Logger} is the entry point into a log pipeline.
|
||||||
*
|
*
|
||||||
* <p>Obtain a {@link #logRecordBuilder()}, add properties using the setters, and emit it via {@link
|
* <p>Obtain a {@link EventBuilder} or {@link #logRecordBuilder()}, add properties using the
|
||||||
* LogRecordBuilder#emit()}.
|
* setters, and emit it via {@link LogRecordBuilder#emit()}.
|
||||||
|
*
|
||||||
|
* <p>Example usage emitting events:
|
||||||
|
*
|
||||||
|
* <pre>{@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
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
public interface Logger {
|
public interface Logger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link EventBuilder} to emit an event.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> this API MUST only be called on {@link Logger}s which have been assigned an
|
||||||
|
* {@link LoggerBuilder#setEventDomain(String) event domain}.
|
||||||
|
*
|
||||||
|
* <p>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.
|
* Return a {@link LogRecordBuilder} to emit a log record.
|
||||||
*
|
*
|
||||||
|
|
|
@ -8,6 +8,20 @@ package io.opentelemetry.api.logs;
|
||||||
/** Builder class for creating {@link Logger} instances. */
|
/** Builder class for creating {@link Logger} instances. */
|
||||||
public interface LoggerBuilder {
|
public interface LoggerBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the event domain of the resulting {@link Logger}.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> Event domain is required to use {@link Logger#eventBuilder(String)}.
|
||||||
|
*
|
||||||
|
* <p>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}.
|
* Assign an OpenTelemetry schema URL to the resulting {@link Logger}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,12 +14,13 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
* <p>The OpenTelemetry logging API exists to satisfy two use cases:
|
* <p>The OpenTelemetry logging API exists to satisfy two use cases:
|
||||||
*
|
*
|
||||||
* <ol>
|
* <ol>
|
||||||
|
* <li>Enable emitting structured <a
|
||||||
|
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/semantic_conventions/events.md">events</a>
|
||||||
|
* via {@link Logger#eventBuilder(String)}. Requires assigning an {@link
|
||||||
|
* LoggerBuilder#setEventDomain(String) event domain} to the {@link Logger}.
|
||||||
* <li>Enable the creation of log appenders, which bridge logs from other log frameworks (e.g.
|
* <li>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()}.
|
* SLF4J, Log4j, JUL, Logback, etc) into OpenTelemetry via {@link Logger#logRecordBuilder()}.
|
||||||
* It is <b>NOT</b> a replacement log framework.
|
* It is <b>NOT</b> a replacement log framework.
|
||||||
* <li>Enable emitting structured <a
|
|
||||||
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/semantic_conventions/events.md">events</a>.
|
|
||||||
* TODO: add link when event API is added.
|
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* @see Logger
|
* @see Logger
|
||||||
|
|
|
@ -26,6 +26,26 @@ class DefaultLoggerProviderTest {
|
||||||
.setSchemaUrl("http://schema.com")
|
.setSchemaUrl("http://schema.com")
|
||||||
.build())
|
.build())
|
||||||
.doesNotThrowAnyException();
|
.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,24 +5,31 @@
|
||||||
|
|
||||||
package io.opentelemetry.api.logs;
|
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 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.AttributeKey;
|
||||||
import io.opentelemetry.api.common.Attributes;
|
import io.opentelemetry.api.common.Attributes;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.slf4j.event.LoggingEvent;
|
||||||
|
|
||||||
class DefaultLoggerTest {
|
class DefaultLoggerTest {
|
||||||
|
|
||||||
private static final Logger logger = DefaultLogger.getInstance();
|
@RegisterExtension
|
||||||
|
LogCapturer apiUsageLogs = LogCapturer.create().captureForLogger(API_USAGE_LOGGER_NAME);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildAndEmit() {
|
void buildAndEmit() {
|
||||||
|
// Logger with no event.domain
|
||||||
assertThatCode(
|
assertThatCode(
|
||||||
() ->
|
() ->
|
||||||
logger
|
DefaultLogger.getInstance(true)
|
||||||
.logRecordBuilder()
|
.logRecordBuilder()
|
||||||
.setEpoch(100, TimeUnit.SECONDS)
|
.setEpoch(100, TimeUnit.SECONDS)
|
||||||
.setEpoch(Instant.now())
|
.setEpoch(Instant.now())
|
||||||
|
@ -34,5 +41,57 @@ class DefaultLoggerTest {
|
||||||
.setAllAttributes(Attributes.builder().put("key2", "value2").build())
|
.setAllAttributes(Attributes.builder().put("key2", "value2").build())
|
||||||
.emit())
|
.emit())
|
||||||
.doesNotThrowAnyException();
|
.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."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package io.opentelemetry.sdk.logs;
|
package io.opentelemetry.sdk.logs;
|
||||||
|
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
|
import io.opentelemetry.api.logs.EventBuilder;
|
||||||
import io.opentelemetry.api.logs.LogRecordBuilder;
|
import io.opentelemetry.api.logs.LogRecordBuilder;
|
||||||
import io.opentelemetry.api.logs.Severity;
|
import io.opentelemetry.api.logs.Severity;
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
@ -18,8 +19,8 @@ import java.time.Instant;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** SDK implementation of {@link LogRecordBuilder}. */
|
/** SDK implementation of {@link EventBuilder} and {@link LogRecordBuilder}. */
|
||||||
final class SdkLogRecordBuilder implements LogRecordBuilder {
|
final class SdkLogRecordBuilder implements EventBuilder {
|
||||||
|
|
||||||
private final LoggerSharedState loggerSharedState;
|
private final LoggerSharedState loggerSharedState;
|
||||||
private final LogLimits logLimits;
|
private final LogLimits logLimits;
|
||||||
|
@ -40,43 +41,43 @@ final class SdkLogRecordBuilder implements LogRecordBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setEpoch(long timestamp, TimeUnit unit) {
|
public SdkLogRecordBuilder setEpoch(long timestamp, TimeUnit unit) {
|
||||||
this.epochNanos = unit.toNanos(timestamp);
|
this.epochNanos = unit.toNanos(timestamp);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setEpoch(Instant instant) {
|
public SdkLogRecordBuilder setEpoch(Instant instant) {
|
||||||
this.epochNanos = TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano();
|
this.epochNanos = TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setContext(Context context) {
|
public SdkLogRecordBuilder setContext(Context context) {
|
||||||
this.spanContext = Span.fromContext(context).getSpanContext();
|
this.spanContext = Span.fromContext(context).getSpanContext();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setSeverity(Severity severity) {
|
public SdkLogRecordBuilder setSeverity(Severity severity) {
|
||||||
this.severity = severity;
|
this.severity = severity;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setSeverityText(String severityText) {
|
public SdkLogRecordBuilder setSeverityText(String severityText) {
|
||||||
this.severityText = severityText;
|
this.severityText = severityText;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogRecordBuilder setBody(String body) {
|
public SdkLogRecordBuilder setBody(String body) {
|
||||||
this.body = Body.string(body);
|
this.body = Body.string(body);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> LogRecordBuilder setAttribute(AttributeKey<T> key, T value) {
|
public <T> SdkLogRecordBuilder setAttribute(AttributeKey<T> key, T value) {
|
||||||
if (key == null || key.getKey().isEmpty() || value == null) {
|
if (key == null || key.getKey().isEmpty() || value == null) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,64 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.logs;
|
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.LogRecordBuilder;
|
||||||
import io.opentelemetry.api.logs.Logger;
|
import io.opentelemetry.api.logs.Logger;
|
||||||
|
import io.opentelemetry.api.logs.LoggerProvider;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
|
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** SDK implementation of {@link Logger}. */
|
/** SDK implementation of {@link Logger}. */
|
||||||
final class SdkLogger implements 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 LoggerSharedState loggerSharedState;
|
||||||
private final InstrumentationScopeInfo instrumentationScopeInfo;
|
private final InstrumentationScopeInfo instrumentationScopeInfo;
|
||||||
|
@Nullable private final String eventDomain;
|
||||||
|
|
||||||
SdkLogger(
|
SdkLogger(
|
||||||
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
|
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
|
||||||
|
this(loggerSharedState, instrumentationScopeInfo, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SdkLogger(
|
||||||
|
LoggerSharedState loggerSharedState,
|
||||||
|
InstrumentationScopeInfo instrumentationScopeInfo,
|
||||||
|
@Nullable String eventDomain) {
|
||||||
this.loggerSharedState = loggerSharedState;
|
this.loggerSharedState = loggerSharedState;
|
||||||
this.instrumentationScopeInfo = instrumentationScopeInfo;
|
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
|
@Override
|
||||||
|
|
|
@ -9,17 +9,25 @@ import io.opentelemetry.api.logs.LoggerBuilder;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
|
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
|
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
|
||||||
import io.opentelemetry.sdk.internal.ComponentRegistry;
|
import io.opentelemetry.sdk.internal.ComponentRegistry;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
final class SdkLoggerBuilder implements LoggerBuilder {
|
final class SdkLoggerBuilder implements LoggerBuilder {
|
||||||
|
|
||||||
private final ComponentRegistry<SdkLogger> registry;
|
private final ComponentRegistry<SdkLogger> registry;
|
||||||
private final InstrumentationScopeInfoBuilder scopeBuilder;
|
private final InstrumentationScopeInfoBuilder scopeBuilder;
|
||||||
|
@Nullable private String eventDomain;
|
||||||
|
|
||||||
SdkLoggerBuilder(ComponentRegistry<SdkLogger> registry, String instrumentationScopeName) {
|
SdkLoggerBuilder(ComponentRegistry<SdkLogger> registry, String instrumentationScopeName) {
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.scopeBuilder = InstrumentationScopeInfo.builder(instrumentationScopeName);
|
this.scopeBuilder = InstrumentationScopeInfo.builder(instrumentationScopeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoggerBuilder setEventDomain(String eventDomain) {
|
||||||
|
this.eventDomain = eventDomain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SdkLoggerBuilder setSchemaUrl(String schemaUrl) {
|
public SdkLoggerBuilder setSchemaUrl(String schemaUrl) {
|
||||||
scopeBuilder.setSchemaUrl(schemaUrl);
|
scopeBuilder.setSchemaUrl(schemaUrl);
|
||||||
|
@ -34,6 +42,7 @@ final class SdkLoggerBuilder implements LoggerBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SdkLogger build() {
|
public SdkLogger build() {
|
||||||
return registry.get(scopeBuilder.build());
|
SdkLogger logger = registry.get(scopeBuilder.build());
|
||||||
|
return eventDomain == null ? logger : logger.withEventDomain(eventDomain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.doubleArrayKey;
|
||||||
import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
|
import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
|
||||||
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
|
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.LogAssertions.assertThat;
|
||||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
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.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import io.github.netmikey.logunit.api.LogCapturer;
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
import io.opentelemetry.api.common.Attributes;
|
import io.opentelemetry.api.common.Attributes;
|
||||||
import io.opentelemetry.api.common.AttributesBuilder;
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
|
@ -30,9 +32,14 @@ import java.util.Arrays;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.slf4j.event.LoggingEvent;
|
||||||
|
|
||||||
class SdkLoggerTest {
|
class SdkLoggerTest {
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
LogCapturer apiUsageLogs = LogCapturer.create().captureForLogger(API_USAGE_LOGGER_NAME);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void logRecordBuilder() {
|
void logRecordBuilder() {
|
||||||
LoggerSharedState state = mock(LoggerSharedState.class);
|
LoggerSharedState state = mock(LoggerSharedState.class);
|
||||||
|
@ -134,4 +141,40 @@ class SdkLoggerTest {
|
||||||
|
|
||||||
verify(logRecordProcessor, never()).onEmit(any());
|
verify(logRecordProcessor, never()).onEmit(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void eventBuilder() {
|
||||||
|
AtomicReference<ReadWriteLogRecord> 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."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue