From be202eab9c469e7f968085978fc8375cf17f8d44 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 2 Oct 2024 15:00:41 -0700 Subject: [PATCH] Prototype: User facing logs API (including emit event) --- .../io/opentelemetry/api/OpenTelemetry.java | 14 ++ .../opentelemetry/api/logs/DefaultLogger.java | 43 +++++ .../opentelemetry/api/logs/EventBuilder.java | 152 ++++++++++++++++++ .../io/opentelemetry/api/logs/Logger.java | 11 ++ .../incubator/logs/ExtendedDefaultLogger.java | 44 +++++ .../sdk/logs/SdkEventBuilder.java | 87 ++++++++++ .../io/opentelemetry/sdk/logs/SdkLogger.java | 18 +++ 7 files changed, 369 insertions(+) create mode 100644 api/all/src/main/java/io/opentelemetry/api/logs/EventBuilder.java create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkEventBuilder.java diff --git a/api/all/src/main/java/io/opentelemetry/api/OpenTelemetry.java b/api/all/src/main/java/io/opentelemetry/api/OpenTelemetry.java index 673c6e429eb..6c7e3a94bf8 100644 --- a/api/all/src/main/java/io/opentelemetry/api/OpenTelemetry.java +++ b/api/all/src/main/java/io/opentelemetry/api/OpenTelemetry.java @@ -5,6 +5,8 @@ package io.opentelemetry.api; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterBuilder; @@ -114,6 +116,18 @@ default MeterBuilder meterBuilder(String instrumentationScopeName) { return getMeterProvider().meterBuilder(instrumentationScopeName); } + default LoggerProvider getLoggerProvider() { + return LoggerProvider.noop(); + } + + default Logger getLogger(String instrumentationScopeName) { + return getLoggerProvider().get(instrumentationScopeName); + } + + default LoggerBuilder getLoggerBuilder(String instrumentationScopeName) { + return getLoggerProvider().loggerBuilder(instrumentationScopeName); + } + /** * Returns the {@link LoggerProvider} for bridging logs into OpenTelemetry. * diff --git a/api/all/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java b/api/all/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java index 56284b43cb2..7e518060fb5 100644 --- a/api/all/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java +++ b/api/all/src/main/java/io/opentelemetry/api/logs/DefaultLogger.java @@ -6,6 +6,7 @@ package io.opentelemetry.api.logs; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.context.Context; import java.time.Instant; @@ -15,6 +16,7 @@ class DefaultLogger implements Logger { private static final Logger INSTANCE = new DefaultLogger(); private static final LogRecordBuilder NOOP_LOG_RECORD_BUILDER = new NoopLogRecordBuilder(); + private static final EventBuilder NOOP_EVENT_BUILDER = new NoopEventBuilder(); private DefaultLogger() {} @@ -27,6 +29,11 @@ public LogRecordBuilder logRecordBuilder() { return NOOP_LOG_RECORD_BUILDER; } + @Override + public EventBuilder eventBuilder(String eventName) { + return NOOP_EVENT_BUILDER; + } + private static final class NoopLogRecordBuilder implements LogRecordBuilder { private NoopLogRecordBuilder() {} @@ -84,4 +91,40 @@ public LogRecordBuilder setAttribute(AttributeKey key, T value) { @Override public void emit() {} } + + private static class NoopEventBuilder implements EventBuilder { + + @Override + public EventBuilder put(String key, Value value) { + return this; + } + + @Override + public EventBuilder setTimestamp(long timestamp, TimeUnit unit) { + return this; + } + + @Override + public EventBuilder setTimestamp(Instant instant) { + return this; + } + + @Override + public EventBuilder setContext(Context context) { + return this; + } + + @Override + public EventBuilder setSeverity(Severity severity) { + return this; + } + + @Override + public EventBuilder setAttributes(Attributes attributes) { + return this; + } + + @Override + public void emit() {} + } } diff --git a/api/all/src/main/java/io/opentelemetry/api/logs/EventBuilder.java b/api/all/src/main/java/io/opentelemetry/api/logs/EventBuilder.java new file mode 100644 index 00000000000..e7aecf75b92 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/logs/EventBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.logs; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.context.Context; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static java.util.stream.Collectors.toList; + +/** The EventBuilder is used to {@link #emit()} events. */ +public interface EventBuilder { + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, String value) { + return put(key, Value.of(value)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, long value) { + return put(key, Value.of(value)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, double value) { + return put(key, Value.of(value)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, boolean value) { + return put(key, Value.of(value)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, String... value) { + List> values = new ArrayList<>(value.length); + for (String val : value) { + values.add(Value.of(val)); + } + return put(key, Value.of(values)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, long... value) { + List> values = new ArrayList<>(value.length); + for (long val : value) { + values.add(Value.of(val)); + } + return put(key, Value.of(values)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, double... value) { + List> values = new ArrayList<>(value.length); + for (double val : value) { + values.add(Value.of(val)); + } + return put(key, Value.of(values)); + } + + /** Put the given {@code key} and {@code value} in the payload. */ + default EventBuilder put(String key, boolean... value) { + List> values = new ArrayList<>(value.length); + for (boolean val : value) { + values.add(Value.of(val)); + } + return put(key, Value.of(values)); + } + + /** + * Put the given key and value in the payload. + * + *

NOTE: The key value pair is NOT added to the event attributes. Setting event attributes is + * less common than adding entries to the event payload. Use {@link #setAttributes(Attributes)} if + * intending the data to be set in attributes instead of the payload. + */ + @SuppressWarnings("unchecked") + default EventBuilder put(AttributeKey key, T value) { + switch (key.getType()) { + case STRING: + return put(key.getKey(), (String) value); + case BOOLEAN: + return put(key.getKey(), (boolean) value); + case LONG: + return put(key.getKey(), (long) value); + case DOUBLE: + return put(key.getKey(), (double) value); + case STRING_ARRAY: + return put( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case BOOLEAN_ARRAY: + return put( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case LONG_ARRAY: + return put( + key.getKey(), Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case DOUBLE_ARRAY: + return put( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + } + return this; + } + + /** Put the given {@code key} and {@code value} in the payload. */ + EventBuilder put(String key, Value value); + + /** + * Set the epoch {@code timestamp}, 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}, 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); + + /** Set the context. */ + EventBuilder setContext(Context context); + + /** Set the severity. */ + EventBuilder setSeverity(Severity severity); + + /** + * Set the attributes. + * + *

Event {@link Attributes} provide additional details about the + * Event which are not part of the well-defined {@link Value} payload. Setting event attributes is + * less common than adding entries to the event payload. Most users will want to call one of the + * {@code #put(String, ?)} methods instead. + */ + EventBuilder setAttributes(Attributes attributes); + + /** Emit an event. */ + void emit(); +} diff --git a/api/all/src/main/java/io/opentelemetry/api/logs/Logger.java b/api/all/src/main/java/io/opentelemetry/api/logs/Logger.java index 50b8035bb5d..14ac25ab480 100644 --- a/api/all/src/main/java/io/opentelemetry/api/logs/Logger.java +++ b/api/all/src/main/java/io/opentelemetry/api/logs/Logger.java @@ -33,4 +33,15 @@ public interface Logger { * LogRecordBuilder#emit()}. */ LogRecordBuilder logRecordBuilder(); + + /** + * Return a {@link LogRecordBuilder} to emit an event. + * + * @param eventName the event name, which identifies the class or type of event. Event with the + * same name are structurally similar to one another. Event names are subject to the same + * naming rules as attribute names. Notably, they are namespaced to avoid collisions. See event.name semantic + * conventions for more details. + */ + EventBuilder eventBuilder(String eventName); } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java index 3e8dce08e74..d7923425bc7 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java @@ -6,7 +6,9 @@ package io.opentelemetry.api.incubator.logs; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.logs.EventBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.logs.Severity; @@ -18,6 +20,7 @@ class ExtendedDefaultLogger implements ExtendedLogger { private static final Logger INSTANCE = new ExtendedDefaultLogger(); private static final LogRecordBuilder NOOP_LOG_RECORD_BUILDER = new NoopLogRecordBuilder(); + private static final EventBuilder NOOP_EVENT_BUILDER = new NoopEventBuilder(); private ExtendedDefaultLogger() {} @@ -30,6 +33,11 @@ public LogRecordBuilder logRecordBuilder() { return NOOP_LOG_RECORD_BUILDER; } + @Override + public EventBuilder eventBuilder(String eventName) { + return NOOP_EVENT_BUILDER; + } + private static final class NoopLogRecordBuilder implements ExtendedLogRecordBuilder { private NoopLogRecordBuilder() {} @@ -87,4 +95,40 @@ public LogRecordBuilder setAttribute(AttributeKey key, T value) { @Override public void emit() {} } + + private static class NoopEventBuilder implements EventBuilder { + + @Override + public EventBuilder put(String key, Value value) { + return this; + } + + @Override + public EventBuilder setTimestamp(long timestamp, TimeUnit unit) { + return this; + } + + @Override + public EventBuilder setTimestamp(Instant instant) { + return this; + } + + @Override + public EventBuilder setContext(Context context) { + return this; + } + + @Override + public EventBuilder setSeverity(Severity severity) { + return this; + } + + @Override + public EventBuilder setAttributes(Attributes attributes) { + return this; + } + + @Override + public void emit() {} + } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkEventBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkEventBuilder.java new file mode 100644 index 00000000000..f112ffda585 --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkEventBuilder.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.logs.EventBuilder; +import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.Clock; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +class SdkEventBuilder implements EventBuilder { + + private static final AttributeKey EVENT_NAME = AttributeKey.stringKey("event.name"); + + private final Map> payload = new HashMap<>(); + private final Clock clock; + private final LogRecordBuilder logRecordBuilder; + private final String eventName; + private boolean hasTimestamp = false; + + SdkEventBuilder(Clock clock, LogRecordBuilder logRecordBuilder, String eventName) { + this.clock = clock; + this.logRecordBuilder = logRecordBuilder; + this.eventName = eventName; + } + + @Override + public EventBuilder put(String key, Value value) { + payload.put(key, value); + return this; + } + + @Override + public EventBuilder setTimestamp(long timestamp, TimeUnit unit) { + this.logRecordBuilder.setTimestamp(timestamp, unit); + this.hasTimestamp = true; + return this; + } + + @Override + public EventBuilder setTimestamp(Instant instant) { + this.logRecordBuilder.setTimestamp(instant); + this.hasTimestamp = true; + return this; + } + + @Override + public EventBuilder setContext(Context context) { + logRecordBuilder.setContext(context); + return this; + } + + @Override + public EventBuilder setSeverity(Severity severity) { + logRecordBuilder.setSeverity(severity); + return this; + } + + @Override + public EventBuilder setAttributes(Attributes attributes) { + logRecordBuilder.setAllAttributes(attributes); + return this; + } + + @Override + public void emit() { + if (!payload.isEmpty()) { + logRecordBuilder.setBody(Value.of(payload)); + } + if (!hasTimestamp) { + logRecordBuilder.setTimestamp(clock.now(), TimeUnit.NANOSECONDS); + } + logRecordBuilder.setAttribute(EVENT_NAME, eventName); + logRecordBuilder.emit(); + } +} 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 72fb9f0b356..c2c69bd1485 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 @@ -6,15 +6,20 @@ package io.opentelemetry.sdk.logs; import io.opentelemetry.api.incubator.logs.ExtendedLogger; +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.api.logs.Severity; +import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.internal.LoggerConfig; /** SDK implementation of {@link Logger}. */ final class SdkLogger implements ExtendedLogger { + private static final Severity DEFAULT_EVENT_SEVERITY = Severity.INFO; + private static final Logger NOOP_LOGGER = LoggerProvider.noop().get("noop"); private final LoggerSharedState loggerSharedState; @@ -38,6 +43,19 @@ public LogRecordBuilder logRecordBuilder() { return NOOP_LOGGER.logRecordBuilder(); } + @Override + public EventBuilder eventBuilder(String eventName) { + if (loggerEnabled) { + return new SdkEventBuilder( + loggerSharedState.getClock(), + logRecordBuilder() + .setSeverity(DEFAULT_EVENT_SEVERITY) + .setContext(Context.current()), + eventName); + } + return NOOP_LOGGER.eventBuilder(eventName); + } + // VisibleForTesting InstrumentationScopeInfo getInstrumentationScopeInfo() { return instrumentationScopeInfo;