Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Typed support for semantic conventions #132

Open
tiffon opened this issue Oct 11, 2018 · 0 comments
Open

Typed support for semantic conventions #132

tiffon opened this issue Oct 11, 2018 · 0 comments

Comments

@tiffon
Copy link
Member

tiffon commented Oct 11, 2018

Background

Currently, the semantic conventions for span tags and log fields are supported by way of constants (Go, Java, JavaScript).

Problem

  • It's easier to ignore the conventions than it is to follow them
  • Following the conventions by way of the constants makes for verbose code
  • For a given standard key, the type of the value is not enforced in any way, e.g. setTag('error', 99)

Proposal / goals

Provide a typed API which supports:

  • The standard keys
  • Value types
  • Enum values (when applicable)

Existing interfaces should not need to be changed.

Third party developers should be able to follow the same approach to promote consistency within library or application code.

Thoughts on design

One approach is to define the key-value pairs on a separate object which is then used to either log the fields or to set the span tags:

tags.makeRPC()
	.kind.isClient()
	.peer.service('service-a')
	.peer.hostname('le-hostname')
	.peer.port(8080)
	.setOnSpan(span);

This would set the following span tags:

  • span.kind: "client"
  • peer.service: "service-a"
  • peer.hostname: "le-hostname"
  • peer.port: 8080

The following standard tags would not be added to the span because they are not set in the code-snippet above:

  • peer.address
  • peer.ipv4
  • peer.ipv6

Enum values can be enforced via setter methods specific to each value. For instance, the .kind property value would have .isClient() and .isServer() methods for RPC scenarios.

Similarly, to add a log record to a span:

try {
	thingThatExplodes();
} catch (error) {
	const errorLog = logs.makeError()
		.message(error.message)
		.stack(error.stack)
		.kind(error.constructor.name);
	span.log(errorLog);
}

This would add a log record with the following fields:

  • event: "error"
  • error.kind: "..." – determined by the constructor of error
  • message: "..." – determined by error.message
  • stack: "..." – determined by error.stack

An alternate API to add a log record:

try {
	thingThatExplodes();
} catch (error) {
	logs.makeError()
		.message(error.message)
		.stack(error.stack)
		.kind(error.constructor.name)
		.logToSpan(span);
}

The approaches described above are scenario based and make use of a specific subset of the standard span tags and log fields. The same approach can be used to provide general typed support for the standard tags and fields:

tags.make()
	.component('omg-layer')
	.span.kind.isClient()
	.errored()
	.http.status_code(404)
	.setOnSpan(span);

This would set the following span tags:

  • component: "omg-layer"
  • span.kind: "client"
  • error: true
  • http.status_code: 404

One benefit of the scenario approach is that certain expectations can be enforced, as determined by the spec: Modeling special circumstances. For instance, the following omits the span.kind tag and therefore can result in a warning or error:

// this would warn or error
tags.makeRPC()
	.peer.service('service-a')
	.peer.hostname('le-hostname')
	.peer.port(8080)
	.setOnSpan(span);

An alternative style for the API:

const rpcTags = tags.makeRPC();
rpcTags.span.kind = 'client';
rpcTags.peer.service = 'the-other-service';
rpcTags.setOnSpan(span);

This style is simpler and perhaps more approachable, but is more verbose and it would be more difficult to enforce required keys at compile time (to my understanding).

Lastly

The initial thinking is that the main semantic conventions can live in the core API and extensions can live in contrib.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant