Skip to content
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

✨Add initial span interface #31

Merged
merged 13 commits into from
Jun 19, 2019

Conversation

mayurkale22
Copy link
Member

@mayurkale22 mayurkale22 commented Jun 13, 2019

Completes #29

This is based on the specs: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/tracing-api.md#span

This PR contains following interfaces:

  • Span
  • SpanContext
  • Status
  • Link
  • Attributes
  • TraceOptions
  • TraceState
  • SpanKind

@mayurkale22 mayurkale22 added Discussion Issue or PR that needs/is extended discussion. API labels Jun 13, 2019
@mayurkale22
Copy link
Member Author

@rochdev @vmarchaud @hekike @bg451 Please review. Also, please file an issue here so that I can add you as a reviewer.

*/
addEvent(
name: string,
attributes?: { [key: string]: string | number | boolean }
Copy link
Member

Choose a reason for hiding this comment

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

Is Object not supported here intentionally?
spec: values may be string, booleans or numeric type.
maybe would be a useful addition like we did for OpenCensus's setAttribute and automatically JSON.stringify() objects.
I assume setAttribute will do the same as spec is must be either a string, a boolean value, or a numeric type.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added the object type.

*/
addLink(
spanContext: SpanContext,
attributes?: { [key: string]: string | number | boolean }
Copy link
Member

Choose a reason for hiding this comment

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

Object?

@vmarchaud
Copy link
Member

@mayurkale22 Which sponsor should we put on the issue ? I guess the first one is you but for the second one ?

* @param value the value for this attribute. If the value is a typeof object
* it has to be JSON.stringify-able otherwise it will raise an exception.
*/
setAttribute(key: string, value: string | number | boolean | object): void;
Copy link
Contributor

@danielkhan danielkhan Jun 14, 2019

Choose a reason for hiding this comment

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

If those setters would return the Span itself (this), we would provide fluid interfaces, like span.setAttibute().addEvent().

Copy link
Member

Choose a reason for hiding this comment

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

I think value should be of type any similar to OpenTracing. The idea is that vendors can accept different values in different ways, so this should not be restricted.

Copy link
Member

Choose a reason for hiding this comment

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

There should also be setAttributes and setEvents to make it easier to add in bulk.

Copy link
Member Author

Choose a reason for hiding this comment

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

If those setters would return the Span itself (this), we would provide fluid interfaces, like span.setAttibute().addEvent().

Sure, done.

Copy link
Member Author

Choose a reason for hiding this comment

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

There should also be setAttributes and setEvents to make it easier to add in bulk.

Feel free to open an issue to discuss and if goes well, will include in the next iteration of the API.

* Sets an attribute to the span.
*
* @param key the key for this attribute.
* @param value the value for this attribute. If the value is a typeof object
Copy link
Member

Choose a reason for hiding this comment

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

This would be problematic for Datadog since we currently accept values that are not JSON serializable and we have a custom serializer that knows how to handle them.

*/
addEvent(
name: string,
attributes?: { [key: string]: string | number | boolean | object }
Copy link
Member

@rochdev rochdev Jun 14, 2019

Choose a reason for hiding this comment

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

Similar to the above, attributes values should be of type any.

Copy link
Member Author

Choose a reason for hiding this comment

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

I am fine with changing it to any type, but will lose type-safety and to mitigate this I have to add TSLint rule flag (tslint:disable-next-line:no-any). WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Since there is a limited number of types in JS it would be possible to simply specify all of them, but I think any is clearer that the intent is to accept anything. I'm personally fine with a TSLint comment. If it happens to much we could simply remove the rule. Code reviews will ensure that there is no abuse.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use unknown instead of any? That allows the same semantics (the passed in value can be any type), but it requires type guards or a cast to actually do something with the type and so is safer from a type checking perspective. As a plus it doesn't require the lint comment. (Most times where you say any you can usually say unknown and get the same effect)

Copy link
Member

Choose a reason for hiding this comment

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

Would it make more sense then to include every possible JavaScript types? There are not that many and it would remove the need for casting for common types.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could we use unknown instead of any?

For now, I have changed to unknown . If needed, will change to either any or every possible JavaScript types.

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that unknown is basically equivalent (maybe exactly equivalent?) to the union type of every possible type. You can use typeof and instanceof that would do runtime checks but are also used by the TS compiler to infer a more narrow type at compile time for code in code blocks guarded by those checks. Casts would only be needed if you somehow know what type it is and force it to that, which perhaps something vendor-specific might do.

See the function f20(x: unknown) example in the in the TS 3.0 release notes for an example of how typeof and instanceof can be used for type narrowing by the compiler (and of course checking of the value's actual JS runtime type so it can be correctly serialized).

* Adds a link to the Span.
*
* @param spanContext the context of the linked span.
* @param [attributes] the attributes that will be added; these are
Copy link
Member

Choose a reason for hiding this comment

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

It's not clear what these attributes are used for.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think attributes are the collection of key:value pairs associated with the link to conserve meaningful information related to link.

Copy link
Member

Choose a reason for hiding this comment

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

What I meant is that this should be better explained in the TypeDoc. In general I feel the documentation of the file doesn't explain much of what the different methods do. Of course, documentation can be improved in later iterations.

* associated with this link.
*/
addLink(
spanContext: SpanContext,
Copy link
Member

Choose a reason for hiding this comment

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

This should accept a Span as well.

Copy link
Member

Choose a reason for hiding this comment

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

How is the type of link determined?

Copy link
Member Author

Choose a reason for hiding this comment

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

How is the type of link determined?

Good Point! AFAIK will use attributes to highlight the link type and will handle in sdk implementation. @songy23 and @rghetia to add more on this.

This should accept a Span as well.

Can you please mention the use case with span ?

Copy link
Member

Choose a reason for hiding this comment

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

Can you please mention the use case with span ?

In general, I think everywhere a SpanContext is accepted should also accept a Span. This avoids the caller having to always call span.context() if they are referencing a span. I don't have a specific use case but I've had a lot of cases where I was happy that OpenTracing did that for me. This is especially true if the context propagator returns the active span.

*
* @param name the Span name.
*/
updateName(name: string): void;
Copy link
Member

Choose a reason for hiding this comment

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

I would use setName to be consistent with setAttribute.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

The decision is to go with updateName for this feature, so terminology will be aligned across languages. I will add TODO with issue number to consider this later.

* Call to End of a Span MUST not have any effects on child spans. Those may
* still be running and can be ended later.
*/
end(): void;
Copy link
Member

Choose a reason for hiding this comment

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

This should accept a timestamp since it's the only way to support some frameworks (i.e. Apollo Tracing).

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, done.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

I would say yes as the same issue applies to every language. The same will be true also for the ability to set the start time.

Copy link
Member Author

Choose a reason for hiding this comment

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

* @returns true if this Span is active and recording information like events
* with the AddEvent operation and attributes using setAttributes.
*/
isRecordingEvents(): boolean;
Copy link
Member

Choose a reason for hiding this comment

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

From the description it sounds like the fact that it's recording events is only part of the state. Should this be called isActive instead or something generic?

Copy link
Member Author

Choose a reason for hiding this comment

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

This flag is used to indicate whether events should be recorded or not. If it's set, Events and Attributes, etc. will be recorded. Otherwise, these events will be dropped. The only way to set this flag when we create the span. If not set, the implementation will provide a default.

/** The ID of the Span. */
spanId: string;
/** Trace options to propagate. */
traceOptions?: number;
Copy link
Member

Choose a reason for hiding this comment

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

It's unclear how this is supposed to be used. What does this number represent?

Copy link
Member Author

Choose a reason for hiding this comment

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

Addressed in 52e53ab, PTAL

Copy link
Member

Choose a reason for hiding this comment

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

Would it make more sense to have different fields instead of a single bit field? While it requires a little bit more memory, it makes such an interface a lot easier to understand and use.

@yurishkuro yurishkuro removed their request for review June 14, 2019 15:41
/** Trace options to propagate. */
traceOptions?: number;
/** Tracing-system-specific info to propagate. */
traceState?: string;
Copy link
Member

Choose a reason for hiding this comment

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

It's unclear what should be the format of this string.

Copy link
Member Author

Choose a reason for hiding this comment

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

Addressed in 52e53ab, PTAL

Copy link
Member

Choose a reason for hiding this comment

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

This would be easier to interact with by using an actual object than can then be serialized to a string by some formatter/encoder. Of course this could be done with a getter and a corresponding _traceState private property to hold the object, but then every vendor will have to replicate this logic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, added TraceState and TraceOptions classes with TODO comment.

}

/** An enumeration of canonical status codes. */
export enum CanonicalCode {
Copy link
Member

Choose a reason for hiding this comment

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

Does it make sense to have a fixed enum for this? What if the reason falls outside of the range of values?

Also, should there be a code for exceptions, with the error information attached?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think will revise after open-telemetry/opentelemetry-specification#59. Currently it is copied from OpenCensus project.

* Do not return `this`. The Span generally should not be used after it
* is ended so chaining is not desired in this context.
*
* @param [endTime] the timestamp to set as Span's end time. If not provided,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you specify how the timestamp is formatted? Are we going to use epoch millis?

Copy link
Member Author

Choose a reason for hiding this comment

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

If you don't mind, I will update once we have some conclusion here #19

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. Could you add a TODO comment that points to that issue though?

* propagated along side of a distributed context.
*/
export interface SpanContext {
/** The ID of the trace that this span belongs to. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this specify the format? Hex-encoded 128-bit number? Same for spanId.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done 424c9fd

@mayurkale22
Copy link
Member Author

@hekike @rochdev @danielkhan @draffensperger I tried to address all the comments, please take a look again. I think this is just first iteration will update/add moving forward.

Copy link
Contributor

@draffensperger draffensperger left a comment

Choose a reason for hiding this comment

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

Gave a few more comments but LGTM overall.

* Do not return `this`. The Span generally should not be used after it
* is ended so chaining is not desired in this context.
*
* @param [endTime] the timestamp to set as Span's end time. If not provided,
Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. Could you add a TODO comment that points to that issue though?

* sampled or not.
* SAMPLED_VALUE = 0x1 and NOT_SAMPLED_VALUE = 0x0;
*/
traceOptions?: TraceOptions;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this just be a number instead of a TraceOptions structure? Seems like the comment implies that.

* Multiple tracing systems (with different formatting):
* tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
*/
traceState?: TraceState;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems that the comment about is describing this as a string, but the type seems to imply something more complex - is this meant to become the parsed trace state?

I'm OK with leaving this as-is though since the structure is marked as TODO anyway.

* limitations under the License.
*/

export class TraceOptions {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the file name be something like trace_options.ts to match this?

Also, should this be an interface instead?

* limitations under the License.
*/

export class TraceState {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comments here: should the file be trace_state.ts, and should this be an interface? (I realize this is TODO, but wanted to comment anyway on this since this is the types package)

@mayurkale22
Copy link
Member Author

I think I addressed all of the comments by updating the PR, adding TODOs, or opening issues. Merging this PR now. Thanks for the reviews @danielkhan @rochdev @hekike @draffensperger

@mayurkale22 mayurkale22 merged commit 802182e into open-telemetry:master Jun 19, 2019
@mayurkale22 mayurkale22 deleted the span_api branch June 19, 2019 17:37
@mayurkale22 mayurkale22 mentioned this pull request Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issue or PR that needs/is extended discussion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants