-
Notifications
You must be signed in to change notification settings - Fork 30
feat: Telemetry API #863
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
feat: Telemetry API #863
Conversation
/** | ||
* Get the [LoggerProvider] used to create new [aws.smithy.kotlin.runtime.telemetry.logging.LoggerProvider] instances | ||
*/ | ||
public val loggerProvider: LoggerProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: used to create new aws.smithy.kotlin.runtime.telemetry.logging.Logger
instances
/** | ||
* Construct a logging record and emits it to an underlying logger | ||
*/ | ||
public interface LogRecordBuilder { | ||
/** | ||
* Set the logging level (severity) | ||
* @param level the level to log this record at | ||
*/ | ||
public fun setLevel(level: LogLevel) | ||
|
||
/** | ||
* Set the timestamp | ||
* @param ts the observed time for this event | ||
*/ | ||
public fun setTimestamp(ts: Instant) | ||
|
||
/** | ||
* Set an exception associated with this event | ||
* Some loggers will do additional formatting for exceptions. | ||
* @param ex the exception to associate with this log record | ||
*/ | ||
public fun setCause(ex: Throwable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Values that can be written but not read feel unergonomic to me, as do explicit setters (although I understand that Kotlin does not yet support write-only properties). Is there a reason not to use full read/write var
s here?
interface LogRecordBuilder {
var level: LogLevel
var timestamp: Instant
var cause: Throwable
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea is to be no-ops in many cases (e.g. not being sampled, log level not enabled, etc).
/** | ||
* Associate all key/value pairs from [attributes] with this log record | ||
* @param attributes the attributes to associate with this log record | ||
*/ | ||
public fun setAllAttributes(attributes: Attributes) { | ||
attributes.keys.forEach { key -> | ||
@Suppress("UNCHECKED_CAST") | ||
setAttribute(key.name, attributes[key as AttributeKey<Any>]) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: The name of this method seems misleading to me since it does not replace all the attributes (which is what "set" typically means). Maybe it "adds" or "merges" them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just setAttributes
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setAttributes
also implies to me that all existing attributes would be replaced. In my preference, either we want that and the default impl changes here or we want to merge attributes and the method name changes to reflect that.
/** | ||
* Lazy add a log message with throwable payload if trace logging is enabled | ||
*/ | ||
public fun trace(t: Throwable?, msg: () -> Any) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Given that t
is nullable here, is there a reason to offer an explicit overload without the Throwable
argument? Could we have just one overload with a default value?
fun trace(t: Throwable? = null, msg: () -> Any)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably could yes. I think there may be some changes here still anyway FWIW since I need to figure out how we want to think about trace context and logs.
/** | ||
* Get or create a named [Meter] | ||
* @param scope the name of the instrumentation scope that uniquely identifies this meter | ||
* @param attributes instrumentation scope attributes to associate with emitted telemetry | ||
*/ | ||
public fun getMeter(scope: String, attributes: Attributes = emptyAttributes()): Meter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: The word "get" by itself typically implies a read-only operation but that's not what's described in the method docs. Kotlin's stdlib calls this sort of thing getOrPut
but I've also seen getOrCreate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can change it (and tracer provider) to getOrCreate
if you prefer.
/** | ||
* Set all [attributes] on the span. | ||
* @param attributes collection of attributes to set on the span | ||
*/ | ||
public fun setAttributes(attributes: Attributes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Does this mean "replace all existing attributes with the new attributes
value"? Or merge?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge
/** | ||
* Set an attribute on the span | ||
* @param key the attribute key to use | ||
* @param value the value to associate with the key | ||
*/ | ||
public fun <T : Any> setAttribute(key: AttributeKey<T>, value: T) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Consider indexed getter/setter:
operator fun <T : Any> get(key: AttributeKey<T>): T?
operator fun <T : Any> set(key: AttributeKey<T>, value: T)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll find most of the API is going to be write only and that is on purpose but we could change it to operator functions.
/** | ||
* Set the span status | ||
* @param status the status to set | ||
*/ | ||
public fun setStatus(status: SpanStatus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment: As above, write but no read feels weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are all write only in OTeL API (the SDK implementation allows reading attributes and other values in processors and exporters where they make sampling decisions, serialize, etc). If we made them readable we'd have to store the value in the shim layer(s) even though they may not be used. We don't really need to read any of this anywhere though.
/** | ||
* Marks the end of this span's execution. This MUST be called when the unit | ||
* of work the span represents has finished. | ||
*/ | ||
public fun end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: If this MUST be called, should TraceSpan
be Closeable
and should end
instead be close
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we wanted to go that route yes it could be.
/** | ||
* Returns a unique [Tracer] scoped to be used by instrumentation code. The scope | ||
* and identity of that instrumentation code is uniquely identified by the name | ||
* and attributes. | ||
* | ||
* @param scope the name of the instrumentation scope | ||
* @param attributes (optional) specifies the instrumentation scope attributes to associate with emitted | ||
* telemetry | ||
*/ | ||
public fun getTracer(scope: String, attributes: Attributes = emptyAttributes()): Tracer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is this also "get or create"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Kudos, SonarCloud Quality Gate passed!
|
import aws.smithy.kotlin.runtime.util.get | ||
|
||
/** | ||
* Construct a logging record and emits it to an underlying logger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This builder doesn't emit to an underlying logger, it just constructs a logging record, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, will fix in next PR.
Issue #
N/A
Description of changes
Proposes a new unified interface for telemetry data. This interface aligns much closer to OpenTelemetry such that plugging in OTeL as the backing implementation should be easy (we may make tweaks along the way if we discover this not to be the case).
NOTE: This is just the interface, removal of previous tracing/logging and updating usage across the SDK will be in subsequent PRs.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.