@emartech/telemetry-javascript
v1.5.2
Published
Telemetry package for Typescript/Javascript code use
Downloads
1,419
Maintainers
Readme
telemetry-javascript
Telemetry Client Library Wrapper
Library for emitting telemetry to an OpenTelmetry Collector via StatsD.
Quickstart
Setup
Environment Variable (optional)
TELEMETRY_CONFIG = {
'application':'personalization service',
'domain':'personalization',
'module':'compilation',
'metrics': {
'client':'statsd',
'hostname':'127.0.0.1',
'port':8125,
'enabled':true
}
}
application
(default: ''): application using the library.domain
(default: ''): default namespace domain of the application.module
(default: ''): default namespace module of the applcation.metrics
client
(default: 'statsd'):statsd
metrics client to use.hostname
(default: '127.0.0.1'): host to which the metrics client will send metrics data. If running locally in Docker this will be the name of the collector service.- Example
services: app: environment: - 'TELEMETRY_CONFIG={"metrics":{"hostname":"collector"}}'
- Example
port
(default: 8125): port to which the metrics client will send metrics data.enabled
(default: true):true
|false
controls whether metris will be emitted or not.
Installation
Install the Emarsys telemetry package
npm install @emartech/telemetry-javascript
Write your first metric
import * as metrics from '@emartech/telemetry-javascript';
// With default namespace set
metrics.setDefaultNamespace('personalization', 'compilation');
metrics.incrementCounter('request_count');
Features
Instrument your code with OpenTelemetry-compatible metrics with an established path to Google Cloud Monitoring
Use out-of-the-box defaults to hook into the Emarsys OpenTelemetry pipeline automatically
Insulate your application code from changes to OpenTelemetry or telemetry data platforms
Automatically capture the duration of functions
metrics.setDefaultNamespace('personalization', 'compilation');
// Callable
const personalizationString = metrics.time(
'total_duration',
[500, 1000, 1500, 2000, 2500, 3000, 3500, 4000]],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
computePersonalizationString,
'Hello <name>',
'<name>',
'World!',
);
function computePersonalizationString(
personlizationString: string,
token: string,
tokenValue: string): string {
return personalizationString.replace(token, tokenValue);
}
/*
Result: 'Hello World!'
Metric Name: personalization_compilation_total_duration
Value: Time to execute function in milliseconds
*/
// Decorator
const myClass = new MyClass();
const result = myClass.computePersonalizationString('Hello <name>', '<name>', 'World!');
class MyClass {
@metrics.timeDecorator('total_duration')
function computePersonalizationString(
personlizationString: string,
token: string,
tokenValue: string): string {
return personalizationString.replace(token, tokenValue);
}
}
/*
Result: 'Hello World!'
Metric Name: personalization_compilation_total_duration
Value: Time to execute function in milliseconds
*/
- Automatically capture the duration and reliability of functions
metrics.setDefaultNamespace('personalization', 'compilation');
// Callable
// Success
const personalizationString = 'Hello <name>';
const token ='<name>';
const tokenValue = 'World!'
const result = metrics.timeAndCount(
'total',
() => {
computePersonalizationString(personalizationString, token, tokenValue);
},
[500, 1000, 1500, 2000, 2500, 3000, 3500, 4000]],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
);
// OR
const result = metrics.metricWrapper(
'total',
[500, 1000, 1500, 2000, 2500, 3000, 3500, 4000]],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
computePersonalizationString,
'Hello <name>',
'<name>',
'World!',
);
function computePersonalizationString(
personlizationString: string,
token: string,
tokenValue: string): string {
return personalizationString.replace(token, tokenValue);
}
/*
Result: 'Hello World!'
Metric Name: personalization_compilation_total_duration
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:true'
Metric Name: personalization_compilation_total_count
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:true'
*/
metrics.setDefaultNamespace('personalization', 'compilation');
// Failure
const personalizationString = 'Hello <name>';
const token ='<name>';
const tokenValue = 'World!'
const result = metrics.timeAndCount(
'total',
() => {
computePersonalizationString(personalizationString, token, tokenValue);
},
[500, 1000, 1500, 2000, 2500, 3000, 3500, 4000]],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
);
// OR
const personalizationString = metrics.metricWrapper(
'total',
[500, 1000, 1500, 2000, 2500, 3000, 3500, 4000]],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
computePersonalizationString,
'Hello <name>',
'<name>',
'World!',
);
function computePersonalizationString(
personlizationString: string,
token: string,
tokenValue: string): string {
throw new Error();
}
/*
Result: Error
Metric Name: personalization_compilation_total_duration
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:false'
Metric Name: personalization_compilation_total_count
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:false'
*/
// Decorator
const myClass = new MyClass();
const result = myClass.computePersonalizationString('Hello <name>', '<name>', 'World!');
class MyClass {
@metrics.timeAndCountDecorator('total')
function computePersonalizationString(
personlizationString: string,
token: string,
tokenValue: string): string {
return personalizationString.replace(token, tokenValue);
}
}
/*
Result: 'Hello World!'
Metric Name: personalization_compilation_total_duration
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:true'
Metric Name: personalization_compilation_total_count
Value: Time to execute function in milliseconds
Auto-Attribute: 'success:true'
*/
- Namespace metrics into units based on module, component, or other criteria
const compilationNamespace = metrics.getNamespace(domain='personalization', module='compilation');
compilationNamespace.recordTimer('total_duration', 1000);
// Metric Name: personalization_compilation_total_duration
const executionNamespace = metrics.getNamespace(domain='personalization', module='execution');
executionNamespace.recordTimer('total_duration', 1000);
// Metric Name: personalization_execution_total_duration
- Flexibly tag metrics at any scope to add metadata to your metrics without extra configuration
// Global attribute
metrics.setAttribute('hostname', 'server4');
// Namespace attribute
const compilationNamespace = metrics.getNamespace('personalization', 'compilation');
compilationNamespace.setAttribute('worker_type', 'reticulator');
// Dynamic attribute
const compilationNamespace = metrics.getNamespace('personalization', 'compilation');
compilationNamespace.incrementCounter(
'completedJobs_count',
{ 'attribute1': 'value1', 'attribute2': 'value2' }
);
- Add metrics to shared code and see them reported independently in consuming applications
Recommended Usage
Application Startup
import * as metrics from '@emartech/telemetry-javascript';
// We will use a single namespace to cover all of the metrics generated by our application, so we set it up as the default.
metrics.setDefaultNamespace('personalization', 'compilation');
metrics.incrementCounter('total_duration');
// We will explicitly create our metrics in order to provide a description and unit for our metrics
metrics.createCounter('request_Count', 'Number of requests the service received', 'requests');
metrics.createGauge('queue_depth', 'Number of requests currently in the queue', 'requests');
Metric Generation
// Application code - throughout the application, metrics will be generated
import * as metrics from '@emartech/telemetry-javascript';
def process_incoming_request(request):
metrics.increment_counter('total_requests')
generated_error = handle_request()
if generated_error:
metrics.increment_counter('error_requests')
Large, Multi-module Applications
import * as metrics from '@emartech/telemetry-javascript';
// Namespace objects can be created and stored at the module, component, or other level to create separate units for metrics
const compilationNamespace = metrics.getNamespace('personalization', 'compilation');
compilationNamespace.incrementCounter('request_count');
const executionNamespace = metrics.getNamespace('personalization', 'execution');
executionNamespace.incrementCounter('request_count');
Advanced Usage
import * as metrics from '@emartech/telemetry-javascript';
// For each default entity or setting, the deftaults can overriden by using lower-level APIs.
metrics.setDefaultClient(new StatsDClient('endpoint', 0));
metrics.setAttribute('hostname', 'server4');
const compilationNamespace = metrics.getNamespace('personalization', 'compilation');
compilationNamespace.setAttribute('namespace_attribute', 'foo');
const totalDurationMetric = compilationNamespace.createTimer('total_duration', 'Total duration of blah', 'ms');
totalDurationMetric.setAttribute('metric_attribute', 'bar');
totalDurationMetric.record(
1000,
[200, 400, 600, 800, 1000, 1200, 1400],
{ 'attribute1': 'value1', 'attribute2': 'value2' }
);
Including Metrics in Shared Code
import * as metrics from '@emartech/telemetry-javascript';
// When instrumenting a shared library, the shared library should create it's own namespace and write it's metrics there.
// The shared library will rely upon the consuming application to attach any attributes it want's applied to the data to the default metric client.
compilationSharedLibraryNamespace = metrics.getNamespace('personalization', 'compilation_shared_library');
compilationSharedLibraryNamespace.createCounter('compilation_invocations', 'Number of compilation invocations', 'invocations');
compilationSharedLibraryNamespace.incrementCounter('compilation_invocations');
Metric Types
Four types of metrics are supported in Emarsys telemetry libraries:
- Counters
- Timers
- Gauges
- Histograms
Counter
A counter metric can be incremented or decremented over time and captures the total (sum) of all provided values.
Examples
- Number of messages processed
- Number of errors generated
/*
incrementCounter(
metricName: string,
value = 1,
attributes: Record<string, string> = {},
): void;
*/
metrics.incrementCounter(
'request_count',
100,
{ 'attribute1': 'value1', 'attribute2': 'value2' }
);
Gauge
A gauge metric stores the current value of something at the time that it was observed. Gauge metrics should be used for data which does not make sense to add together, but instead new measurements should replace old.
The last value of a gauge which is written during a time interval (generally every 60s) overwrites any previous values.
Examples
- Number of items in a queue
- Current memory usage of a process
/*
recordGauge(
metricName: string,
value: number,
attributes: Record<string, string> = {},
): void;
*/
metrics.recordGauge(
'queue_depth',
100000,
{ 'attribute1': 'value1', 'attribute2': 'value2' }
);
Histogram
A histogram metric stores a statistical summary of multiple values which are recorded within a time interval. Each observation that is recorded is counted in one of a series of “buckets” which are configured for the histogram. This storage method allows for the calculation of statistics after collection such as the minimum, maximum, median, and percentiles (P99, etc.) at a later date.
Visualization of a Histogram
Examples
- Size of incoming requests
- Number of personalization tokens in each email
/*
recordHistogram(
metricName: string,
value: number,
buckets: number[] = [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000],
attributes: Record<string, string> = {},
): void;
*/
metrics.recordHistogram(
'total_size',
2500,
[1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000],
{ 'attribute1': 'value1', 'attribute2': 'value2' },
);
Timer
A timer metric can capture how long multiple observations of the same code or process have taken and results in a distribution of these time values. The distribution can be processed to determine the median, P99 or other values of interest.
A timer is a special case of a Histogram metric, which can store the statistical distribution of any type of data.
Examples
- Time taken to process a message
- Time taken to run a database query
/*
recordTimer(
metricName: string,
value: number,
buckets: number[] = [0, 10, 25, 50, 75, 100, 250, 500, 1000, 5000, 10000, 30000, 60000, 240000],
attributes: Record<string, string> = {},
): void;
*/
metrics.recordTimer(
'total_duration',
850,
[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000],
{ 'attribute1': 'value1', 'attribute2': 'value2' }
);
Utility Methods
The Emarsys telemetry library provides utility methods
toLowerCamelCase
A hyphenated string is turned into a camel-cased string for use in GCP attribute fields
/*
toLowerCamelCase(
name: string | undefined
): string
*/
metrics.Util.toLowerCamelCase(
'telemetry-attribute-example'
);