Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ dependencies {

testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")

testImplementation("org.slf4j:slf4j-api")
testImplementation("org.apache.logging.log4j:log4j-api:2.25.2")

testImplementation("com.google.guava:guava")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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.incubator.common.ExtendedAttributeKey;
import io.opentelemetry.api.logs.Logger;
Expand All @@ -20,6 +21,7 @@ class ExtendedDefaultLogger implements ExtendedLogger {
private static final Logger INSTANCE = new ExtendedDefaultLogger();
private static final ExtendedLogRecordBuilder NOOP_LOG_RECORD_BUILDER =
new NoopExtendedLogRecordBuilder();
private static final LogEventBuilder NOOP_LOG_EVENT_BUILDER = new NoopLogEventBuilder();

private ExtendedDefaultLogger() {}

Expand All @@ -32,6 +34,20 @@ public boolean isEnabled(Severity severity, Context context) {
return false;
}

@Override
public LogEventBuilder logBuilder(Severity severity, String eventName) {
return NOOP_LOG_EVENT_BUILDER;
}

@Override
public void log(
Severity severity,
String eventName,
Attributes attributes,
@Nullable Value<?> body,
@Nullable Throwable exception,
Context context) {}

@Override
public ExtendedLogRecordBuilder logRecordBuilder() {
return NOOP_LOG_RECORD_BUILDER;
Expand Down Expand Up @@ -109,4 +125,35 @@ public ExtendedLogRecordBuilder setBody(Value<?> body) {
@Override
public void emit() {}
}

private static final class NoopLogEventBuilder implements LogEventBuilder {

@Override
public LogEventBuilder setContext(Context context) {
return this;
}

@Override
public LogEventBuilder setBody(Value<?> body) {
return this;
}

@Override
public LogEventBuilder setBody(String body) {
return this;
}

@Override
public <T> LogEventBuilder setAttribute(AttributeKey<T> key, @Nullable T value) {
return this;
}

@Override
public LogEventBuilder setException(Throwable throwable) {
return this;
}

@Override
public void emit() {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

package io.opentelemetry.api.incubator.logs;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import javax.annotation.Nullable;

/** Extended {@link Logger} with experimental APIs. */
public interface ExtendedLogger extends Logger {
Expand Down Expand Up @@ -41,6 +44,130 @@ default boolean isEnabled() {
return isEnabled(Severity.UNDEFINED_SEVERITY_NUMBER);
}

// Fluent log API

/**
* A fluent log event API, with convenience methods for incrementally adding fields and
* attributes.
*
* <p>Callers must call {@link LogEventBuilder#emit()}.
*/
LogEventBuilder logBuilder(Severity severity, String eventName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

callback methods are popular to short circuit if the severity is not reached, e.g. https://logging.apache.org/log4j/2.x/javadoc/log4j-api/org/apache/logging/log4j/Logger.html#info(java.lang.String,org.apache.logging.log4j.util.Supplier...)

we could add

void log(Severity severity, Consumer<LogEventBuilder> consumer);


// Low allocation log APIs

/**
* A low-allocation alternative to {@link #logBuilder(Severity, String)} which prevents the need
* for allocating a builder instance.
*
* @param severity the log severity number
* @param eventName the name that identifies the class / type of event which uniquely identifies
* the event structure (attributes and body)
* @param attributes the log attributes, or {@link Attributes#empty()}
* @param body the log body, or {@code null}
* @param exception the exception, or {@code null}
* @param context the context, or {@link Context#current()}
*/
void log(
Severity severity,
String eventName,
Attributes attributes,
@Nullable Value<?> body,
@Nullable Throwable exception,
Context context);

default void log(
Severity severity,
String eventName,
Attributes attributes,
Value<?> body,
Throwable exception) {
log(severity, eventName, attributes, body, exception, Context.current());
}

default void log(Severity severity, String eventName, Attributes attributes, Value<?> body) {
log(severity, eventName, attributes, body, null, Context.current());
}

default void log(
Severity severity,
String eventName,
Attributes attributes,
String body,
Throwable exception) {
log(severity, eventName, attributes, Value.of(body), exception, Context.current());
}

default void log(Severity severity, String eventName, Attributes attributes, String body) {
log(severity, eventName, attributes, Value.of(body));
}

default void log(
Severity severity, String eventName, Attributes attributes, Throwable exception) {
log(severity, eventName, attributes, null, exception, Context.current());
}

default void log(Severity severity, String eventName, Attributes attributes) {
log(severity, eventName, attributes, null, null, Context.current());
}

default void log(Severity severity, String eventName, Throwable exception) {
log(severity, eventName, Attributes.empty(), null, exception, Context.current());
}

default void log(Severity severity, String eventName) {
log(severity, eventName, Attributes.empty(), null, null, Context.current());
}

// info overloads of log(..)

/**
* Overload of {@link #log(Severity, String, Attributes, Value, Throwable, Context)} assuming
* {@link Severity#INFO}.
*/
default void info(
String eventName,
Attributes attributes,
@Nullable Value<?> body,
@Nullable Throwable exception,
Context context) {
log(Severity.INFO, eventName, attributes, body, exception, context);
}

default void info(String eventName, Attributes attributes, Value<?> body, Throwable exception) {
info(eventName, attributes, body, exception, Context.current());
}

default void info(String eventName, Attributes attributes, Value<?> body) {
info(eventName, attributes, body, null, Context.current());
}

default void info(String eventName, Attributes attributes, String body, Throwable exception) {
info(eventName, attributes, Value.of(body), exception, Context.current());
}

default void info(String eventName, Attributes attributes, String body) {
info(eventName, attributes, Value.of(body));
}

default void info(String eventName, Attributes attributes, Throwable exception) {
info(eventName, attributes, null, exception, Context.current());
}

default void info(String eventName, Attributes attributes) {
info(eventName, attributes, null, null, Context.current());
}

default void info(String eventName, Throwable exception) {
info(eventName, Attributes.empty(), null, exception, Context.current());
}

default void info(String eventName) {
info(eventName, Attributes.empty(), null, null, Context.current());
}

// TODO: add severity overloads for trace, debug, warn, error, fatal

@Override
ExtendedLogRecordBuilder logRecordBuilder();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;

import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.logs.LogRecordBuilder;
import io.opentelemetry.context.Context;
import javax.annotation.Nullable;

/**
* Experimental fluent builder for <a
* href="https://opentelemetry.io/docs/specs/semconv/general/events/">log events</a>.
*/
public interface LogEventBuilder {

/** Set the context. */
LogEventBuilder setContext(Context context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LogEventBuilder setContext(Context context);
LogEventBuilder context(Context context);


/** Set the body {@link Value}. */
LogEventBuilder setBody(Value<?> body);

/**
* Set the body string.
*
* <p>Shorthand for calling {@link #setBody(Value)} with {@link Value#of(String)}.
*/
LogEventBuilder setBody(String body);

/**
* Sets attributes. If the {@link LogRecordBuilder} previously contained a mapping for any of the
* keys, the old values are replaced by the specified values.
*/
@SuppressWarnings("unchecked")
default LogEventBuilder setAllAttributes(Attributes attributes) {
if (attributes == null || attributes.isEmpty()) {
return this;
}
attributes.forEach(
(attributeKey, value) -> setAttribute((AttributeKey<Object>) attributeKey, value));
return this;
}

/**
* Sets an attribute on the {@code LogRecord}. If the {@code LogRecord} previously contained a
* mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: Providing a null value is a no-op and will not remove previously set values.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
* @return this.
*/
<T> LogEventBuilder setAttribute(AttributeKey<T> key, @Nullable T value);

/**
* Sets a String attribute on the {@code LogRecord}. If the {@code LogRecord} previously contained
* a mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: Providing a null value is a no-op and will not remove previously set values.
*
* <p>Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and
* pre-allocate your keys, if possible.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
*/
default LogEventBuilder setAttribute(String key, @Nullable String value) {
return setAttribute(stringKey(key), value);
}

/**
* Sets a Long attribute on the {@code LogRecord}. If the {@code LogRecord} previously contained a
* mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and
* pre-allocate your keys, if possible.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
*/
default LogEventBuilder setAttribute(String key, long value) {
return setAttribute(longKey(key), value);
}

/**
* Sets a Double attribute on the {@code LogRecord}. If the {@code LogRecord} previously contained
* a mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and
* pre-allocate your keys, if possible.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
*/
default LogEventBuilder setAttribute(String key, double value) {
return setAttribute(doubleKey(key), value);
}

/**
* Sets a Boolean attribute on the {@code LogRecord}. If the {@code LogRecord} previously
* contained a mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and
* pre-allocate your keys, if possible.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
*/
default LogEventBuilder setAttribute(String key, boolean value) {
return setAttribute(booleanKey(key), value);
}

/**
* Sets an Integer attribute on the {@code LogRecord}. If the {@code LogRecord} previously
* contained a mapping for the key, the old value is replaced by the specified value.
*
* <p>Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and
* pre-allocate your keys, if possible.
*
* @param key the key for this attribute.
* @param value the value for this attribute.
*/
default LogEventBuilder setAttribute(String key, int value) {
return setAttribute(key, (long) value);
}

/** Set standard {@code exception.*} attributes based on the {@code throwable}. */
LogEventBuilder setException(Throwable throwable);

/** Emit the log event. */
void emit();
}
Loading
Loading