@emartech/telemetry-javascript
TypeScript icon, indicating that this package has built-in type declarations

1.5.2 • Public • Published

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"}}'
        
    • 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

Histogram chart with intervals of 100 showing numerous observations in each interval.

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'
);    

Readme

Keywords

Package Sidebar

Install

npm i @emartech/telemetry-javascript

Weekly Downloads

493

Version

1.5.2

License

MIT

Unpacked Size

442 kB

Total Files

38

Last publish

Collaborators

  • luca.fasolino.se
  • rmafteiuscai
  • lhammerl
  • bencso
  • mfel0123
  • franziskajung
  • d056437
  • ekkovats
  • laralangnau
  • emarsys-stephen-ives
  • tothbence8
  • earlpitts
  • iabraham
  • zholler
  • balintkemenyemarsys
  • ccarrollem
  • dunaicapa
  • bobby_russel
  • sovagos
  • cyholden
  • tothmarci25
  • mariannag
  • estefanlesnjakovic
  • mrmeszaros
  • bence.toth
  • jason-nelson-01
  • drahos.istvan
  • pecc
  • birokhun
  • laszlo.ori
  • dpkemarsys
  • nathan-matthews-sap
  • troywiegand
  • nikolett.tar
  • bronika
  • cenglersap
  • mlesh-sap
  • gillyes
  • danielisap
  • sridevir
  • abieler-sap
  • aidanlesh-sap
  • tonyhsap
  • karlabrandl
  • konradschewe
  • manasbommakanti
  • dudaaslaci
  • thomaskmartin
  • emarsys-security
  • norbert-levajsics-emarsys
  • ronnykrosse
  • vszegedi
  • sap-am
  • nnieman-sap
  • ariceem
  • dwolter_emarsys
  • rcsullag
  • ttoth2
  • tbucsanszki
  • dszunomar
  • dschuppa
  • andras.sarro
  • ndomke
  • sevket-ataseven
  • plsap
  • mattfeldhake_emarsys
  • atittel
  • andrasp3a
  • mruell
  • adroszler
  • erikpetroemar
  • rimo86
  • tillmannr
  • markjarvis
  • geczirobert
  • tsiraitnpm
  • bankyadam
  • bborsi
  • zbalazs
  • ziyadg
  • pintera
  • apoon
  • ianhelmrich
  • varszegik
  • rkumari03
  • cseby92
  • bozsadam
  • jfillmore
  • viktor.szell
  • bencekadaremar
  • roxanams
  • dkocsis-emarsys
  • demajo_ems
  • marko.fritzsche
  • agrucza
  • dmorvai
  • nish343
  • kolosh
  • azorahai3904
  • skrivoo
  • mark.adorjan
  • burci
  • dimitrovn
  • ivanfroehlich
  • iulianmihai
  • xueboliang
  • greszter
  • bercziand
  • criley
  • drewhodsonsap
  • jviesersap
  • sixstep
  • sap-jjf
  • sapfs
  • attilamuller01
  • scotthetrick
  • oliverweisenburger
  • maurogreco
  • nicolaeciumac
  • asciortino1
  • pendicg24
  • marton.matusek
  • adamszabolcs
  • btalos
  • bence.uto
  • daniels1404
  • saphendricksjoerg
  • mmartin2
  • fenyopeti
  • mmothersill
  • brandon-sap
  • pjohnson02
  • mhunyady
  • faridtoubal
  • mengjiao.zhao
  • ushnpm
  • dkorpos
  • xin.he
  • viau
  • zsomborh
  • muddam
  • nvkaur2
  • jbleclerc
  • jamescocker
  • arnaud.buchholz
  • jerryrichardson
  • retfalvibence
  • akapa
  • mkls
  • probalazs
  • kaaj
  • knagy
  • rehreth
  • mhegedus
  • mmartin
  • bsoos
  • emarsys-deployer
  • draven
  • judge
  • daniel.banky
  • szeist
  • rgargya
  • marton.papp.emarsys
  • dgyenes
  • s.viktor
  • m4w4q7
  • david.barkoczi
  • qw3r
  • tamas.toth
  • gergaczd
  • gerike
  • alkra
  • epgrubmair
  • morban
  • ettancos
  • epmartini
  • gabor.balla.emarsys
  • mzsombor
  • ejpersson
  • ejwalker
  • llosonczy
  • iben12
  • kartonfarkas
  • adamoa
  • mbarna
  • peva
  • bforgacs
  • kozma
  • ngabor84
  • zerosuxx
  • edosrecki
  • eadaniel
  • selator
  • kkimak
  • gaborb
  • glendvai
  • lveraszto
  • rdoczi
  • fentosi
  • boristomic
  • mbazso
  • dmihalek
  • lhalasz
  • evspasevski
  • dsztanko
  • tbugar
  • fqqdk
  • eggarcia
  • menyhertfatyol
  • zoltanrideg-emarsys
  • sarakoll
  • mmolnar-emar
  • attila.gal
  • benjamingehl
  • tdorkaa
  • lkonya
  • gpap_ema
  • vimtaai
  • lloki-emarsys
  • borzi
  • pmaksa_emarsys
  • dfarago