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

Feature: Implement @jambonz/tracing library #311

Open
wants to merge 79 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9616c55
custom propagator to set span context traceId to a hex version of cal…
ajukes Apr 11, 2023
177cd4b
custom propagator to set span context traceId to a hex version of cal…
ajukes Apr 11, 2023
678a7f0
added SipPropagator as global propagator
ajukes Apr 12, 2023
698a317
Merge branch 'main' into feature/override-trace-id
ajukes Apr 12, 2023
6d6b7fe
Merge remote-tracking branch 'origin/feature/override-trace-id' into …
ajukes Apr 12, 2023
59b74c7
added SipPropagator as global propagator
ajukes Apr 12, 2023
131e2a4
check if span has already ended before ending
ajukes Apr 12, 2023
9f631c1
stringify customerData on span attributes
ajukes Apr 12, 2023
6d74511
Merge branch 'main' into feature/override-trace-id
ajukes Apr 12, 2023
28df38f
first step to @jambonz/tracing lib
ajukes Apr 12, 2023
82d3a75
Merge branch 'main' into feature/override-trace-id
ajukes Apr 12, 2023
5ae7a9a
jambonzTracer getter
ajukes Apr 12, 2023
762d3a3
startChildSpan now returns a ChildSpan helper class
ajukes Apr 13, 2023
422a038
removed old tracer.js from lint scripts
ajukes Apr 13, 2023
a38e2fd
fixed span issue
ajukes Apr 13, 2023
22be575
-fixed bind function issue
ajukes Apr 13, 2023
44b84a5
new RootSpan contsructor
ajukes Apr 13, 2023
18e2cd0
added tracing lib to package.json
ajukes Apr 13, 2023
8105877
default otel service name
ajukes Apr 13, 2023
21e704d
bumped tracing lib
ajukes Apr 13, 2023
51241f2
bumped tracing lib
ajukes Apr 13, 2023
cad84a1
undefined tracer
ajukes Apr 13, 2023
2a5b659
bumped tracing lib
ajukes Apr 13, 2023
052a53c
package-lock.json
ajukes Apr 13, 2023
4868ae2
package-lock.json
ajukes Apr 13, 2023
6ebe370
fixed null tracer on create call
ajukes Apr 13, 2023
0b29e67
better logging of ws commands
davehorton Apr 13, 2023
2715876
update verb specifications
davehorton Apr 13, 2023
7082f07
traceId now takes call-id instead of call-sid.
ajukes Apr 14, 2023
f03cbac
traceId now takes external call-id instead of call-sid.
ajukes Apr 14, 2023
1dff4da
attempt to pickup traceid from X-Trace-ID header
ajukes Apr 14, 2023
c7415ef
use callSid as traceId on create call and add to X-Trace-ID
ajukes Apr 14, 2023
814b96f
added readme envs
ajukes Apr 14, 2023
7c8c9c8
X-Trace-ID header place-outdial.js
ajukes Apr 14, 2023
feddc28
getSIPTracingPropagationHeaders
ajukes Apr 14, 2023
63fe5e1
getSIPTracingPropagationHeaders on child span
ajukes Apr 14, 2023
7befcc8
FS -> SBC incorrect spanId
ajukes Apr 14, 2023
e1d878c
minor fix
ajukes Apr 14, 2023
0356b99
fix: wss requestor incase mysql cache is used (#319)
xquanluu Apr 18, 2023
a22bc8e
fix issue where multiple gathers running simultaneously (#321)
davehorton Apr 19, 2023
6dc019e
fix: amd support for language other than en-US (#322)
davehorton Apr 19, 2023
9948592
feat: update verb specification version (#330)
xquanluu Apr 28, 2023
5043edf
addresses #340 and #331 (#341)
davehorton May 8, 2023
7a184a8
Fix/tracing cleanup (#342)
davehorton May 8, 2023
c118cac
custom propagator to set span context traceId to a hex version of cal…
ajukes Apr 11, 2023
8c81edf
added SipPropagator as global propagator
ajukes Apr 12, 2023
569edae
custom propagator to set span context traceId to a hex version of cal…
ajukes Apr 11, 2023
bbdec6d
added SipPropagator as global propagator
ajukes Apr 12, 2023
4c2f45c
check if span has already ended before ending
ajukes Apr 12, 2023
ee7eec6
stringify customerData on span attributes
ajukes Apr 12, 2023
f599c7c
first step to @jambonz/tracing lib
ajukes Apr 12, 2023
bd54cb5
jambonzTracer getter
ajukes Apr 12, 2023
bb96a9b
startChildSpan now returns a ChildSpan helper class
ajukes Apr 13, 2023
d26a494
removed old tracer.js from lint scripts
ajukes Apr 13, 2023
001d953
fixed span issue
ajukes Apr 13, 2023
e0c82e7
-fixed bind function issue
ajukes Apr 13, 2023
149d20d
new RootSpan contsructor
ajukes Apr 13, 2023
398728a
added tracing lib to package.json
ajukes Apr 13, 2023
ab452f4
default otel service name
ajukes Apr 13, 2023
5cfd374
bumped tracing lib
ajukes Apr 13, 2023
2d9dcfd
bumped tracing lib
ajukes Apr 13, 2023
7eef994
undefined tracer
ajukes Apr 13, 2023
e1fd39d
bumped tracing lib
ajukes Apr 13, 2023
09c26b1
package-lock.json
ajukes Apr 13, 2023
16030e1
package-lock.json
ajukes Apr 13, 2023
b7f71e0
fixed null tracer on create call
ajukes Apr 13, 2023
b4077d6
traceId now takes call-id instead of call-sid.
ajukes Apr 14, 2023
b9b40b7
traceId now takes external call-id instead of call-sid.
ajukes Apr 14, 2023
57d146e
attempt to pickup traceid from X-Trace-ID header
ajukes Apr 14, 2023
d850f0e
use callSid as traceId on create call and add to X-Trace-ID
ajukes Apr 14, 2023
75e7de1
added readme envs
ajukes Apr 14, 2023
32f3bff
X-Trace-ID header place-outdial.js
ajukes Apr 14, 2023
dbf8263
getSIPTracingPropagationHeaders
ajukes Apr 14, 2023
93062ea
getSIPTracingPropagationHeaders on child span
ajukes Apr 14, 2023
127b81e
FS -> SBC incorrect spanId
ajukes Apr 14, 2023
9b2f5f6
minor fix
ajukes Apr 14, 2023
13b2d2c
bumped libs to match current state
ajukes May 9, 2023
f16cb39
Merge remote-tracking branch 'origin/feature/override-trace-id' into …
ajukes May 9, 2023
50c84f5
bumped libs to match current state
ajukes May 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,39 @@ This application implements the core feature server of the jambones platform.

Configuration is provided via environment variables:

| variable | meaning | required?|
|----------|----------|---------|
|AWS_ACCESS_KEY_ID| aws access key id, used for TTS/STT as well SNS notifications|no|
|AWS_REGION| aws region| no|
|AWS_SECRET_ACCESS_KEY| aws secret access key, used per above|no|
|AWS_SNS_TOPIC_ARM| aws sns topic arn that scale-in lifecycle notifications will be published to|no|
|DRACHTIO_HOST| ip address of drachtio server (typically '127.0.0.1')|yes|
|DRACHTIO_PORT| listening port of drachtio server for control connections (typically 9022)|yes|
|DRACHTIO_SECRET| shared secret|yes|
|ENABLE_METRICS| if 1, metrics will be generated|no|
|ENCRYPTION_SECRET| secret for credential encryption(JWT_SECRET is deprecated) |yes|
|GOOGLE_APPLICATION_CREDENTIALS| path to gcp service key file|yes|
|HTTP_PORT| tcp port to listen on for API requests from jambonz-api-server|yes|
|JAMBONES_GATHER_EARLY_HINTS_MATCH| if true and hints are provided, gather will opportunistically review interim transcripts if possible to reduce ASR latency |no|
|JAMBONES_FREESWITCH| IP:port:secret for Freeswitch server (e.g. '127.0.0.1:8021:JambonzR0ck$'|yes|
|JAMBONES_LOGLEVEL| log level for application, 'info' or 'debug'|no|
|JAMBONES_MYSQL_HOST| mysql host|yes|
|JAMBONES_MYSQL_USER| mysql username|yes|
|JAMBONES_MYSQL_PASSWORD| mysql password|yes|
|JAMBONES_MYSQL_DATABASE| mysql data|yes|
|JAMBONES_MYSQL_CONNECTION_LIMIT| mysql connection limit |no|
|JAMBONES_NETWORK_CIDR| CIDR of private network that feature server is running in (e.g. '172.31.0.0/16')|yes|
|JAMBONES_REDIS_HOST| redis host|yes|
|JAMBONES_REDIS_PORT|redis port|yes|
|JAMBONES_SBCS| list of IP addresses (on the internal network) of SBCs, comma-separated|yes|
|STATS_HOST| ip address of metrics host (usually '127.0.0.1' since telegraf is installed locally|no|
|STATS_PORT| listening port for metrics host|no|
|STATS_PROTOCOL| 'tcp' or 'udp'|no|
|STATS_TELEGRAF| if 1, metrics will be generated in telegraf format|no|
| variable | meaning | required? |
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------|-----------|
| AWS_ACCESS_KEY_ID | aws access key id, used for TTS/STT as well SNS notifications | no |
| AWS_REGION | aws region | no |
| AWS_SECRET_ACCESS_KEY | aws secret access key, used per above | no |
| AWS_SNS_TOPIC_ARM | aws sns topic arn that scale-in lifecycle notifications will be published to | no |
| DRACHTIO_HOST | ip address of drachtio server (typically '127.0.0.1') | yes |
| DRACHTIO_PORT | listening port of drachtio server for control connections (typically 9022) | yes |
| DRACHTIO_SECRET | shared secret | yes |
| ENABLE_METRICS | if 1, metrics will be generated | no |
| ENCRYPTION_SECRET | secret for credential encryption(JWT_SECRET is deprecated) | yes |
| GOOGLE_APPLICATION_CREDENTIALS | path to gcp service key file | yes |
| HTTP_PORT | tcp port to listen on for API requests from jambonz-api-server | yes |
| JAMBONES_GATHER_EARLY_HINTS_MATCH | if true and hints are provided, gather will opportunistically review interim transcripts if possible to reduce ASR latency | no |
| JAMBONES_FREESWITCH | IP:port:secret for Freeswitch server (e.g. '127.0.0.1:8021:JambonzR0ck$' | yes |
| JAMBONES_LOGLEVEL | log level for application, 'info' or 'debug' | no |
| JAMBONES_MYSQL_HOST | mysql host | yes |
| JAMBONES_MYSQL_USER | mysql username | yes |
| JAMBONES_MYSQL_PASSWORD | mysql password | yes |
| JAMBONES_MYSQL_DATABASE | mysql data | yes |
| JAMBONES_MYSQL_CONNECTION_LIMIT | mysql connection limit | no |
| JAMBONES_NETWORK_CIDR | CIDR of private network that feature server is running in (e.g. '172.31.0.0/16') | yes |
| JAMBONES_REDIS_HOST | redis host | yes |
| JAMBONES_REDIS_PORT | redis port | yes |
| JAMBONES_SBCS | list of IP addresses (on the internal network) of SBCs, comma-separated | yes |
| STATS_HOST | ip address of metrics host (usually '127.0.0.1' since telegraf is installed locally | no |
| STATS_PORT | listening port for metrics host | no |
| STATS_PROTOCOL | 'tcp' or 'udp' | no |
| STATS_TELEGRAF | if 1, metrics will be generated in telegraf format | no |
| JAMBONES_OTEL_ENABLED | set to 1 to enable otel tracing | no |
| JAMBONES_OTEL_SERVICE_NAME | app name, defaults to 'jambones-feature-server' | no |
| OTEL_EXPORTER_JAEGER_ENDPOINT | jaeger endpoint url 'http://127.0.0.1:14268/api/traces' | no |


### running under pm2
Typically, this application runs under [pm2](https://pm2.io) using an [ecosystem.config.js](https://pm2.keymetrics.io/docs/usage/application-declaration/) file similar to this:
Expand Down Expand Up @@ -89,4 +93,4 @@ module.exports = {

#### Running the test suite

Please [see this](./docs/contributing.md#run-the-regression-test-suite).
Please [see this](./docs/contributing.md#run-the-regression-test-suite).
29 changes: 23 additions & 6 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
const {JambonzTracer, api} = require('@jambonz/tracing');
const {version} = require('./package.json');

const {
DRACHTIO_PORT,
DRACHTIO_HOST,
DRACHTIO_SECRET,
JAMBONES_OTEL_ENABLED,
JAMBONES_OTEL_SERVICE_NAME,
OTEL_EXPORTER_COLLECTOR_URL,
OTEL_EXPORTER_JAEGER_AGENT_HOST,
OTEL_EXPORTER_JAEGER_ENDPOINT,
OTEL_EXPORTER_ZIPKIN_URL,
JAMBONES_LOGLEVEL,
JAMBONES_CLUSTER_ID,
JAMBONZ_CLEANUP_INTERVAL_MINS,
Expand All @@ -16,8 +24,18 @@ checkEnvs();

const Srf = require('drachtio-srf');
const srf = new Srf();
const tracer = require('./tracer')(JAMBONES_OTEL_SERVICE_NAME);
const api = require('@opentelemetry/api');

const {tracer} = new JambonzTracer({
version,
name: JAMBONES_OTEL_SERVICE_NAME || 'jambonz-feature-server',
enabled: JAMBONES_OTEL_ENABLED,
jaegerHost: OTEL_EXPORTER_JAEGER_AGENT_HOST,
jaegerEndpoint: OTEL_EXPORTER_JAEGER_ENDPOINT,
zipkinUrl: OTEL_EXPORTER_ZIPKIN_URL,
collectorUrl: OTEL_EXPORTER_COLLECTOR_URL,
logLevel: JAMBONES_LOGLEVEL
});

srf.locals = {...srf.locals, otel: {tracer, api}};

const opts = {level: JAMBONES_LOGLEVEL};
Expand All @@ -41,14 +59,13 @@ const InboundCallSession = require('./lib/session/inbound-call-session');
const SipRecCallSession = require('./lib/session/siprec-call-session');

if (DRACHTIO_HOST) {
srf.connect({host: DRACHTIO_HOST, port: DRACHTIO_PORT, secret: DRACHTIO_SECRET });
srf.connect({host: DRACHTIO_HOST, port: DRACHTIO_PORT, secret: DRACHTIO_SECRET});
srf.on('connect', (err, hp) => {
const arr = /^(.*)\/(.*)$/.exec(hp.split(',').pop());
srf.locals.localSipAddress = `${arr[2]}`;
logger.info(`connected to drachtio listening on ${hp}, local sip address is ${srf.locals.localSipAddress}`);
});
}
else {
} else {
logger.info(`listening for drachtio requests on port ${DRACHTIO_PORT}`);
srf.listen({port: DRACHTIO_PORT, secret: DRACHTIO_SECRET});
}
Expand Down Expand Up @@ -103,7 +120,7 @@ setInterval(() => {
}, 20000);

const disconnect = () => {
return new Promise ((resolve) => {
return new Promise((resolve) => {
httpServer?.on('close', resolve);
httpServer?.close();
srf.disconnect();
Expand Down
22 changes: 16 additions & 6 deletions data/example-voicemail-greetings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
"at the tone",
"leave a message",
"leave me a message",
"not available right now",
"not available to take your call",
"not available",
"can't take your call",
"I will get back to you",
"will get back to you",
"I'll get back to you",
"we will get back to you",
"we are unable",
"we are not available"
"we are unable"
],
"es-ES": [
"le pasamos la llamada",
Expand Down Expand Up @@ -48,5 +45,18 @@
"ens posarem en contacto",
"ara no estem disponibles",
"no hi som"
],
"de-DE": [
"nicht erreichbar",
"nnruf wurde weitergeleitet",
"beim piepsen",
"am ton",
"eine nachricht hinterlassen",
"hinterlasse mir eine Nachricht",
"nicht verfügbar",
"kann ihren anruf nicht entgegennehmen",
"wird sich bei Ihnen melden",
"ich melde mich bei dir",
"wir können nicht"
]
}
49 changes: 30 additions & 19 deletions lib/http-routes/api/create-call.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const SipError = require('drachtio-srf').SipError;
const sysError = require('./error');
const HttpRequestor = require('../../utils/http-requestor');
const WsRequestor = require('../../utils/ws-requestor');
const RootSpan = require('../../utils/call-tracer');
const dbUtils = require('../../utils/db-utils');
const {RootSpan} = require('@jambonz/tracing');

router.post('/', async(req, res) => {
const {logger} = req.app.locals;
Expand Down Expand Up @@ -37,6 +37,8 @@ router.post('/', async(req, res) => {
const accountInfo = await lookupAccountDetails(req.body.account_sid);
const callSid = uuidv4();

const traceId = callSid.replace('-', '');

opts.headers = {
...opts.headers,
'X-Jambonz-Routing': target.type,
Expand Down Expand Up @@ -135,29 +137,35 @@ router.post('/', async(req, res) => {
*/
if ('WS' === app.call_hook?.method || /^wss?:/.test(app.call_hook.url)) {
logger.debug({call_hook: app.call_hook}, 'creating websocket for call hook');
app.requestor = new WsRequestor(logger, account.account_sid, app.call_hook, account.webhook_secret) ;
if (app.call_hook.url === app.call_status_hook.url || !app.call_status_hook?.url) {
app.requestor = new WsRequestor(logger, account.account_sid, app.call_hook, account.webhook_secret);
if (app.call_hook.url === app.call_status_hook.url || !app.call_status_hook?.url) {
logger.debug('reusing websocket for call status hook');
app.notifier = app.requestor;
}
}
else {
} else {
logger.debug({call_hook: app.call_hook}, 'creating http client for call hook');
app.requestor = new HttpRequestor(logger, account.account_sid, app.call_hook, account.webhook_secret);
}
if (!app.notifier && app.call_status_hook) {
app.notifier = new HttpRequestor(logger, account.account_sid, app.call_status_hook, account.webhook_secret);
logger.debug({call_hook: app.call_hook}, 'creating http client for call status hook');
}
else if (!app.notifier) {
} else if (!app.notifier) {
logger.debug('creating null call status hook');
app.notifier = {request: () => {}, close: () => {}};
app.notifier = {
request: () => {
}, close: () => {
}
};
}

/* now launch the outdial */
try {
const dlg = await srf.createUAC(uri, {...opts, followRedirects: true, keepUriOnRedirect: true}, {
const dlg = await srf.createUAC(uri, {...opts, followRedirects: true, keepUriOnRedirect: true}, {
cbRequest: (err, inviteReq) => {

const {tracer} = inviteReq.srf.locals.otel;
const rootSpan = new RootSpan('fs:rest-call', inviteReq.get('Call-ID'), null, {}, tracer, logger);

/* in case of 302 redirect, this gets called twice, ignore the second
except to update the req so that it can later be canceled if need be
*/
Expand All @@ -168,6 +176,7 @@ router.post('/', async(req, res) => {
}

if (err) {
rootSpan.end();
logger.error(err, 'createCall Error creating call');
res.status(500).send('Call Failure');
return;
Expand All @@ -178,15 +187,15 @@ router.post('/', async(req, res) => {
callSid,
application_sid: app.application_sid
};
rootSpan.setAttributes(inviteReq.locals);
/* ok our outbound INVITE is in flight */

const callId = inviteReq.get('Call-ID');
const tasks = [restDial];
const rootSpan = new RootSpan('rest-call', inviteReq);
sipLogger = logger.child({
callSid,
callId: inviteReq.get('Call-ID'),
callId,
accountSid,
traceId: rootSpan.traceId
traceId
});
app.requestor.logger = app.notifier.logger = sipLogger;
const callInfo = new CallInfo({
Expand All @@ -197,7 +206,7 @@ router.post('/', async(req, res) => {
callSid,
accountSid: req.body.account_sid,
applicationSid: app.application_sid,
traceId: rootSpan.traceId
traceId
});
cs = new RestCallSession({
logger: sipLogger,
Expand All @@ -212,7 +221,11 @@ router.post('/', async(req, res) => {
});
cs.exec(req);

res.status(201).json({sid: cs.callSid, callId: inviteReq.get('Call-ID')});
res.status(201).json({
sid: cs.callSid,
callId: inviteReq.get('Call-ID'),
traceId
});

sipLogger.info({sid: cs.callSid, callId: inviteReq.get('Call-ID')},
`outbound REST call attempt to ${JSON.stringify(target)} has been sent`);
Expand All @@ -232,8 +245,7 @@ router.post('/', async(req, res) => {
});
restDial.emit('callStatus', 200);
restDial.emit('connect', dlg);
}
catch (err) {
} catch (err) {
let callStatus = CallStatus.Failed;
if (err instanceof SipError) {
if ([486, 603].includes(err.status)) callStatus = CallStatus.Busy;
Expand All @@ -245,8 +257,7 @@ router.post('/', async(req, res) => {
sipStatus: err.status,
sipReason: err.reason
});
}
else {
} else {
if (cs) cs.emit('callStatusChange', {
callStatus,
sipStatus: 500,
Expand Down
Loading