ctrace-js
v0.24.1
Published
Canonical OpenTracing for Javascript
Downloads
4
Readme
ctrace-js
Canonical OpenTracing for Javascript
Currently this only supports Node.js usage. Browser-based Javascript support will be added later.
Why
OpenTracing is a young specification and for most (if not all) SDK implementations, output format and wire protocol are specific to the backend platform implementation. ctrace-js attempts to decouple the format and wire protocol from the backend tracer implementation.
What
ctrace-js specifies a canonical format for trace logs. By default the logs are output to stdout but you can configure them to go to any WritableStream.
Required Reading
To fully understand this platform API, it's helpful to be familiar with the OpenTracing project project, terminology, and ctrace-js specification more specifically.
Install
Install via npm as follows:
$ npm install ctrace-js --save
Usage
Add instrumentation to the operations you want to track. This is composed primarily of using "spans" around operations of interest and adding log statements to capture useful data relevant to those operations.
Initialize Global Tracer
First, initialize the global tracer as follows.
const tracer = require('ctrace-js')
OR, initialize the global tracer with custom options as follows.
const tracer = require('ctrace-js')
tracer.init({
multiEvent: true, // true for Multi-Event Mode; false for Single-Event Mode. defaults to false.
debug: true, // true to enabling debugging. defaults to false.
propagators: { // custom propagators mapped to format type
[tracer.FORMAT_HTTP_HEADERS]: [
{
extract: (carrier) => {
if (carrier['x-correlation-id']) {
return {
traceId: carrier['x-correlation-id'],
spanId: carrier['x-correlation-id']
}
}
}
}
]
},
stream: { // optional but defaults to stdout
write: (content) => { // Modify the anonymous function to write logs to an writable stream
console.log(content)
}
},
serviceName: "ExampleService" // can set service name for entire tracer
})
Client HTTP Requests
To trace client HTTP requests you can use the request
wrapper for request-promise or request. To trace a request using request-promise do the following.
const request = require('ctrace-js').request
OR, to trace using request do the following.
const request = require('ctrace-js').request
request.init(require('request'))
You can then send HTTP(S) requests in this or other modules as follows.
const request = require('ctrace-js').request
function send (span, uri, body) {
return request({
method: 'POST',
uri: uri,
body: body,
traceContext: {
span: span // Current opentracing span
}
})
}
Use Express Middleware for server spans
Add the Express Middleware as follows to trace HTTP REST server calls.
const express = require('express')
const tracer = require('ctrace-js')
const app = express()
app.use(tracer.express())
app.post('/users', (req, res) => {
// ...
})
Log Event
Log events as follows.
app.post('/users', (req, res) => {
const span = req.span
span.log({event: 'SaveUser', userId: 'u123'})
// ...
})
Log Errors
Log errors and return visible trace context as follows.
app.post('/users', (req, res) => {
const span = req.span
try {
// ...
} catch (err) {
span.log({
event: 'error',
'error.kind': 'Exception',
message: err.message,
stack: err.stack
})
let ctx = span.context()
res.status(500).json({
error: err.message,
traceId: ctx.traceId,
spanId: ctx.spanId
})
}
})
[Advanced Usage]
The following are examples of advanced usage. In these examples, tracing is done manually rather than using auto-instrumentation middleware.
NOTE: If you are using auto-instrumentation middleware (express, request, etc) there is no need to use manually start and finish spans.
Start Client Span
If you want to track a call to a downstream REST service, start a new client Span like this.
const span = tracer.startSpan('RegisterUser', {
// Apply Standard Tags
tags: {
'span.kind': 'client',
'component': 'UserAdapter',
'peer.hostname': 'my-registry.net',
'peer.port': '443',
'peer.service': 'UserRegistry',
'http.method': 'POST',
'http.url': 'https://my-registry.net/users?apikey=293283209'
}
})
Inject Context
The downstream REST service will want to use this span as its parent. We will inject the span context into the HTTP Headers for this purpose as follows.
const headers = {}
tracer.inject(span.context(), Tracer.FORMAT_HTTP_HEADERS, headers)
Start Server Span
The called REST service can start a server Span as follows.
const express = require('express')
const app = express()
const tracer = require('ctrace')
app.post('/users', (req, res) => {
const context = tracer.extract(tracer.FORMAT_HTTP_HEADERS, req.headers)
const span = tracer.startSpan('RegisterUser', {
childOf: context, // include parent context
// Standard Tags
tags: {
'span.kind': 'server',
'component': 'UserRegistryController',
'peer.ipv4': req.ip,
'http.method': req.method,
'http.url': req.url
}
})
...
})
Finish Server Span
If the REST service call completes successfully on the server, add tag for status and finish the span.
app.post('/users', (req, res) => {
...
span.addTags({'http.status_code': 200})
span.finish()
res.status(200).json(result)
})
If it completes with an error, , do the following to add tags for status code, error=true, recommended error_details, and finish the span.
app.post('/users', (req, res) => {
...
span.addTags({
'http.status_code': 500,
'error': true,
'error_details': error.toString()
})
span.finish()
res.status(500).json(error)
})
Finish Client Span
If the call to the downstream REST service completes successfully, finish the client Span like this.
span.addTags({'http.status_code': 200})
span.finish()
If the call completes with an error, finish the client Span like this.
span.addTags({
// Standard Tags and Recommended error_details
'http.status_code': 500,
'error': true,
'error_details': err.toString()
})
span.finish()
API
SpanContext
An object containing the context used to propagate from span to span
Type: object
Properties
traceId
string id of trace including multiple spansspanId
string id of span (start/stop event)baggage
Object<string, string> optional key/value map of tags that carry across spans in a single trace.
Propagators
Type: Array<Propagator>
GlobalTracer
Global tracer singleton. This is accessed as follows.
const tracer = require('ctrace')
startSpan
Singleton wrapper for Tracer#startSpan
Parameters
name
context
init
Used to initialize global tracer singleton
Parameters
options
object options used to initialize traceroptions.multiEvent
bool? true for multi-event mode; otherwise, single-event modeoptions.debug
bool? true for debug; otherwise, it is disabledoptions.propagators
Object<string, Propagators>? optional propagatorsoptions.serviceName
string? allows the configuration of the "service" tag for the entire Tracer if not specified here, can also be set using env variable "ctrace_service_name"
Propagator
Interface for custom context propagation. If extract or inject methods are present they will be used in the propagation chain.
extract
Extract span context from a given carrier.
Parameters
carrier
object
Returns SpanContext
inject
Inject span context into a given carrier.
Parameters
spanContext
SpanContextcarrier
object
Span
Extends opentracing.Span
Span represents a logical unit of work as part of a broader Trace. Examples of span might include remote procedure calls or a in-process function calls to sub-components. A Trace has a single, top-level "root" Span that in turn may have zero or more child Spans, which in turn may have children.
Parameters
tracer
fields
constructor
Constructor for internal use only. To start a span call Tracer#startSpan
Parameters
context
Returns the SpanContext object associated with this Span.
Returns SpanContext
tracer
Returns the Tracer object used to create this Span.
Returns Tracer
setOperationName
Sets the string name for the logical operation this span represents.
Parameters
name
string
Returns Span this
setBaggageItem
Sets a key:value pair on this Span that also propagates to future children of the associated Span.
setBaggageItem() enables powerful functionality given a full-stack opentracing integration (e.g., arbitrary application data from a web client can make it, transparently, all the way into the depths of a storage system), and with it some powerful costs: use this feature with care.
IMPORTANT NOTE #1: setBaggageItem() will only propagate baggage items to future causal descendants of the associated Span.
IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and value is copied into every local and remote child of the associated Span, and that can add up to a lot of network and cpu overhead.
Parameters
getBaggageItem
Returns the value for a baggage item given its key.
Parameters
key
string The key for the given trace attribute.
Returns string String value for the given key, or undefined if the key does not correspond to a set trace attribute.
setTag
Adds a single tag to the span. See addTags()
for details.
Parameters
Returns Span this
addTags
Adds the given key value pairs to the set of span tags.
Multiple calls to addTags() results in the tags being the superset of all calls.
The behavior of setting the same key multiple times on the same span is undefined.
The supported type of the values is implementation-dependent. Implementations are expected to safely handle all types of values but may choose to ignore unrecognized / unhandle-able values (e.g. objects with cyclic references, function objects).
Parameters
Returns Span this
log
Add a log record to this Span, optionally at a user-provided timestamp.
For example:
span.log({
size: rpc.size(), // numeric value
URI: rpc.URI(), // string value
payload: rpc.payload(), // Object value
"keys can be arbitrary strings": rpc.foo(),
});
span.log({
"error.description": someError.description(),
}, someError.timestampMillis());
Parameters
keyValues
object<string, object> An object mapping string keys to arbitrary value types. All Tracer implementations should support bool, string, and numeric value types, and some may also support Object values.timestamp
number An optional parameter specifying the timestamp in milliseconds since the Unix epoch. Fractional values are allowed so that timestamps with sub-millisecond accuracy can be represented. If not specified, the implementation is expected to use its notion of the current time of the call.
Returns Span this
finish
Sets the end timestamp and finalizes Span state.
With the exception of calls to Span.context() (which are always allowed), finish() must be the last call made to any span instance, and to do otherwise leads to undefined behavior.
Parameters
finishTime
number Optional finish time in milliseconds as a Unix timestamp. Decimal values are supported for timestamps with sub-millisecond accuracy. If not specified, the current time (as defined by the implementation) will be used.
Tracer
Tracer is the tracing entry-point. It facilitates starting a new span and context propagation (ie. inject, extract).
Parameters
options
(optional, default{}
)options.redactList
array<string, RegExp>? optional list of keys, when matched, replaces values with***
options.ignoreRoutes
array<string>? optional list of routes to ignore. These routes will not generate a trace in the logs. Routes can be of the format GET:/route, or the less specific /route, which will ignore any HTTP method call to that route.
constructor
Construct a new tracer for internal use only. Use GlobalTracer#init to set global trace options.
Parameters
options
object options used to initialize tracer (optional, default{}
)options.multiEvent
bool? true for multi-event mode; otherwise, single-event modeoptions.debug
bool? true for debug; otherwise, it is disabledoptions.propagators
object<string, Propagators>? optional propagatorsoptions.serviceName
string? allows the configuration of the "service" tag for the entire Tracer if not specified here, can also be set using env variable "ctrace_service_name"
startSpan
Starts and returns a new Span representing a logical unit of work.
For example:
// Start a new (parentless) root Span:
let parent = tracer.startSpan('DoWork')
// Start a new (child) Span:
let child = tracer.startSpan('Subroutine', {
childOf: parent,
});
Parameters
name
string the name of the operation.options
object? the fields to set on the newly created span. (optional, default{}
)options.childOf
(Span | SpanContext)? a parent SpanContext (or Span, for convenience) that the newly-started span will be the child of (per REFERENCE_CHILD_OF). If specified,fields.references
must be unspecified.options.tags
object<string, object>? set of key-value pairs which will be set as tags on the newly created Span. Ownership of the object is passed to the created span for efficiency reasons (the caller should not modify this object after calling startSpan).
Returns Span a new Span object.
inject
Injects the given SpanContext instance for cross-process propagation
within carrier
. The expected type of carrier
depends on the value of
`format.
OpenTracing defines a common set of format
values (see
FORMAT_TEXT_MAP, FORMAT_HTTP_HEADERS, and FORMAT_BINARY), and each has
an expected carrier type.
Consider this pseudocode example:
var clientSpan = ...;
...
// Inject clientSpan into a text carrier.
var headersCarrier = {};
Tracer.inject(clientSpan.context(), Tracer.FORMAT_HTTP_HEADERS, headersCarrier);
// Incorporate the textCarrier into the outbound HTTP request header
// map.
Object.assign(outboundHTTPReq.headers, headersCarrier);
// ... send the httpReq
Parameters
spanContext
SpanContext the SpanContext to inject into the carrier object. As a convenience, a Span instance may be passed in instead (in which case its .context() is used for the inject()).format
string the format of the carrier.carrier
object see the documentation for the chosenformat
for a description of the carrier object.
extract
Returns a SpanContext instance extracted from carrier
in the given
format
.
OpenTracing defines a common set of format
values (see
FORMAT_TEXT_MAP, FORMAT_HTTP_HEADERS, and FORMAT_BINARY), and each has
an expected carrier type.
Consider this pseudocode example:
// Use the inbound HTTP request's headers as a text map carrier.
var headersCarrier = inboundHTTPReq.headers;
var wireCtx = Tracer.extract(Tracer.FORMAT_HTTP_HEADERS, headersCarrier);
var serverSpan = Tracer.startSpan('...', { childOf : wireCtx });
Parameters
format
string the format of the carrier.carrier
object the type of the carrier object is determined by the format.
Returns SpanContext The extracted SpanContext, or undefined if no such SpanContext could
be found in carrier
Roadmap
- [x] Core Start, Log, and Finish Span
- [x] Inject, Extract to Text and Header formats
- [ ] Inject, Extract to Binary format
- [x] Express Middleware support
- [x] Request and Request-Promise interceptor support
- [ ] Kinesis, Lambda and Plain Lambda wrapper support
- [ ] API Gateway / Lambda in Proxy Mode support