Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 2d85539

Browse files
committed
First working version
1 parent 85d590e commit 2d85539

File tree

9 files changed

+215
-41
lines changed

9 files changed

+215
-41
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,18 @@
1616

1717
# Apigee Distributed Tracing
1818

19+
## TO DO
20+
- Who writes the `tracecontext` to the Pub/Sub message?
21+
- What is the format?
22+
- Does NR have a standard for `tracestate`?
1923
## Installation
20-
24+
## Configuration
25+
- [Flow execution sequence](https://docs.apigee.com/api-platform/fundamentals/what-are-flows#designingflowexecutionsequence)
26+
- Start/End timestamps: [Apigee Flow Variables](https://cloud.google.com/apigee/docs/api-platform/reference/variables-reference)
27+
### Proxy policies
28+
- Proxy policies must be paired, one in a Request Flow and one in a Response Flow
29+
- Request Policies use `<ResourceURL>jsc://Trace-Context-Request.js</ResourceURL>`
30+
-
2131
## Development
2232

2333
### Helpful links
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="Target-Trace-Context-Request">
3+
<DisplayName>Target-Trace-Context-Request</DisplayName>
4+
<Properties>
5+
<!-- The odds of a Trace being sampled, 0-100. 0 = tracing off, 100 = trace everything. -->
6+
<Property name="SampleOddsPercentage">100</Property>
7+
<!-- The name of the Request/Response pair. Must be unique within a Proxy. -->
8+
<Property name="RequestResponsePairName">Target-Trace-Context-Flow</Property>
9+
<!-- How to determine the start time for this Trace. Any Flow Variable (https://cloud.google.com/apigee/docs/api-platform/reference/variables-reference) ending in '.timestamp' is valid. Default is `Date.now()'-->
10+
<Property name="TraceStartTimestamp"></Property>
11+
</Properties>
12+
<IncludeURL>jsc://Utils.js</IncludeURL>
13+
<ResourceURL>jsc://Trace-Context-Request.js</ResourceURL>
14+
</Javascript>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Javascript async="false" continueOnError="true" enabled="true" timeLimit="5000" name="Target-Trace-Context-Response">
3+
<DisplayName>Target-Trace-Context-Response</DisplayName>
4+
<Properties>
5+
<!-- The name of the Request/Response pair. Must be unique within a Proxy. -->
6+
<Property name="RequestResponsePairName">Target-Trace-Context-Flow</Property>
7+
<!-- Custom attributes to include with the NR Span object. A comma separated list of key:value pairs -->
8+
<Property name="AdditionalSpanAttributes"></Property>
9+
<!-- How to determine the end time for this Trace. Any Flow Variable (https://cloud.google.com/apigee/docs/api-platform/reference/variables-reference) ending in '.timestamp' is valid. Default is `Date.now()'-->
10+
<Property name="TraceEndTimestamp"></Property>
11+
</Properties>
12+
<IncludeURL>jsc://Utils.js</IncludeURL>
13+
<ResourceURL>jsc://Trace-Context-Response.js</ResourceURL>
14+
</Javascript>

src/main/apigee/apiproxies/W3C-Trace-Context/apiproxy/policies/Trace-Context-Request.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
<Properties>
55
<!-- The odds of a Trace being sampled, 0-100. 0 = tracing off, 100 = trace everything. -->
66
<Property name="SampleOddsPercentage">100</Property>
7-
<!-- The name of the Request/Response pair. Must be unique withing a Proxy. -->
8-
<Property name="RequestResponsePairName">Initial</Property>
7+
<!-- The name of the Request/Response pair. Must be unique within a Proxy. -->
8+
<Property name="RequestResponsePairName">Proxy-Trace-Context-Flow</Property>
99
<!-- How to determine the start time for this Trace. Any Flow Variable (https://cloud.google.com/apigee/docs/api-platform/reference/variables-reference) ending in '.timestamp' is valid. Default is `Date.now()'-->
1010
<Property name="TraceStartTimestamp"></Property>
1111
</Properties>
12+
<IncludeURL>jsc://Utils.js</IncludeURL>
1213
<ResourceURL>jsc://Trace-Context-Request.js</ResourceURL>
1314
</Javascript>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="Trace-Context-Response">
2+
<Javascript async="false" continueOnError="true" enabled="true" timeLimit="5000" name="Trace-Context-Response">
33
<DisplayName>Trace-Context-Response</DisplayName>
44
<Properties>
55
<!-- The name of the Request/Response pair. Must be unique within a Proxy. -->
6-
<Property name="RequestResponsePairName">Initial</Property>
6+
<Property name="RequestResponsePairName">Proxy-Trace-Context-Flow</Property>
77
<!-- Custom attributes to include with the NR Span object. A comma separated list of key:value pairs -->
88
<Property name="AdditionalSpanAttributes"></Property>
99
<!-- How to determine the end time for this Trace. Any Flow Variable (https://cloud.google.com/apigee/docs/api-platform/reference/variables-reference) ending in '.timestamp' is valid. Default is `Date.now()'-->
1010
<Property name="TraceEndTimestamp"></Property>
1111
</Properties>
12+
<IncludeURL>jsc://Utils.js</IncludeURL>
1213
<ResourceURL>jsc://Trace-Context-Response.js</ResourceURL>
1314
</Javascript>

src/main/apigee/apiproxies/W3C-Trace-Context/apiproxy/resources/jsc/Trace-Context-Request.js

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
"use strict"
1+
'use strict'
2+
3+
var RequestResponsePairName = properties.RequestResponsePairName;
4+
var logPrefix = RequestResponsePairName + '.TraceContextRequest: ';
5+
logMsg(logPrefix + 'enter');
6+
var ctxPrefix = 'traceContext.' + RequestResponsePairName;
7+
8+
var startTime = Date.now();
9+
var configuredStartTime = context.getVariable(properties.TraceStartTimestamp);
10+
if (configuredStartTime != null) {
11+
startTime = configuredStartTime;
12+
} else {
13+
//logMsg(RequestResponsePairName + ' ' +'WARN: null value for TraceStartTimestamp: ' + properties.TraceStartTimestamp );
14+
}
15+
216
function randomHexString(len) {
317
var r = '';
418
var maxlen = 8,
@@ -7,14 +21,14 @@ function randomHexString(len) {
721
n = Math.floor(Math.random() * (max - min + 1)) + min,
822
r = n.toString(16);
923
while (r.length < len) {
10-
r = r + randHex(len - maxlen);
24+
r = r + randomHexString(len - maxlen);
1125
}
1226
return r;
1327
}
1428

1529
function generateValidID(len) {
1630
var id = randomHexString(len);
17-
// IDs must not be all zeros
31+
// IDs must not be all zeros
1832
while (! /[1-9a-f]/.test(id)) {
1933
id = randomHexString(len);
2034
}
@@ -31,20 +45,23 @@ function generateSampled() {
3145
return '00';
3246
}
3347

34-
var traceparent = context.getVariable("request.header.traceparent");
35-
var tracestate = context.getVariable("request.header.tracestate");
48+
//var traceparent = context.proxyRequest.headers['traceparent'];
49+
//var tracestate = context.proxyRequest.headers['tracestate'];
50+
var traceparent = context.getVariable('request.header.traceparent');
51+
var tracestate = context.getVariable('request.header.tracestate');
52+
logMsg(logPrefix + 'traceparent: ' + traceparent + ' type: ' + typeof (traceparent))
3653
//
3754
// Note:
38-
// In W3C speak "Parent ID" is only the "Parent's ID" as received in an incoming traceparent header. Once we create a "Span" we become the "Parent ID"- what
39-
// most would term "the Span ID". `requestParentID` is therefore the incoming Span's ID. This code sticks with W3C terminology for consistency.
55+
// In W3C speak 'Parent ID' is only the 'Parent's ID' as received in an incoming traceparent header. Once we create a 'Span' we become the 'Parent ID'- what
56+
// most would term 'the Span ID'. `requestParentID` is therefore the incoming Span's ID. This code sticks with W3C terminology for consistency.
4057
var versionFormat, requrestVersionFormat, traceID, parentID, traceFlags, requestParentID
4158

4259
function newTraceparent() {
4360
versionFormat = '00';
44-
traceID = generateValidID(16);
45-
parentID = generateValidID(8); // aka SpanID
61+
traceID = generateValidID(32);
62+
parentID = generateValidID(16); // aka SpanID
4663
traceFlags = generateSampled();
47-
requestParentID = ""; // New Relic's Trace API expects a null parent.id attribute if this is the root span
64+
requestParentID = ''; // New Relic's Trace API expects a null parent.id attribute if this is the root span
4865
tracestate = '';
4966
}
5067

@@ -69,13 +86,13 @@ else {
6986
// The vendor will only parse the trace-flags values supported by this version of this specification and ignore all other values.
7087
// If parsing fails, the vendor creates a new traceparent header and deletes the tracestate. Vendors will set all unparsed / unknown trace-flags to 0 on outgoing requests.
7188
if (!/^[\da-f]{2}-[\da-f]{32}-[\da-f]{16}-[\da-f]{2}$/.test(traceparent)) {
72-
print('Error: invalid traceparent: ' + traceparent);
89+
logMsg(logPrefix + 'Error: invalid traceparent: ' + traceparent);
7390
newTraceparent();
7491
} else {
7592
// If the vendor supports the version number, it validates trace-id and parent-id. If either trace-id, parent-id or trace-flags are invalid, the vendor creates a new traceparent header and deletes tracestate.
7693
var values = traceparent.split('-');
7794
requestVersionFormat = values[0];
78-
traceId = values[1];
95+
traceID = values[1];
7996
parentID = values[2];
8097
traceFlags = values[2];
8198
if (/[1-9a-f]/.test(traceID)) {
@@ -89,22 +106,22 @@ else {
89106
parentID = generateValidID(8);
90107
traceFlags = generateSampled();
91108
} else {
92-
print('Error: invalid traceFlags: ' + traceFlags);
109+
logMsg(logPrefix + 'Error: invalid traceFlags: ' + traceFlags);
93110
newTraceparent();
94111
}
95112
} else {
96-
print('Error: invalid parentID: ' + parentID);
113+
logMsg(logPrefix + 'Error: invalid parentID: ' + parentID);
97114
newTraceparent();
98115
}
99116
} else {
100-
print('Error: invalid traceID: ' + traceID);
117+
logMsg(logPrefix + 'Error: invalid traceID: ' + traceID);
101118
newTraceparent();
102119
}
103120

104121
// The vendor MAY validate the tracestate header. If the tracestate header cannot be parsed the vendor MAY discard the entire header. Invalid tracestate entries MAY also be discarded.
105122
var states = tracestate.split(',');
106123
if (states.length > 32) {
107-
print("Warning: tracestate has too many values: " + states.length);
124+
logMsg(RlogPrefix + 'Warning: tracestate has too many values: ' + states.length);
108125
}
109126
}
110127
}
@@ -118,13 +135,20 @@ else {
118135

119136
// The vendor sets the traceparent and tracestate header for the outgoing request.
120137
traceparent = versionFormat + '-' + traceID + '-' + parentID + '-' + traceFlags;
138+
logMsg(logPrefix + 'outbound traceparent: ' + traceparent);
121139

122-
context.targetRequest.headers['traceparent'] = traceparent;
123-
context.targetRequest.headers['tracestate'] = tracestate;
140+
//context.targetRequest.headers['traceparent'] = traceparent;
141+
//context.targetRequest.headers['tracestate'] = tracestate;
142+
//context.proxyRequest.headers['traceparent'] = traceparent;
143+
//context.proxyRequest.headers['tracestate'] = tracestate;
144+
context.setVariable('request.header.traceparent', traceparent);
145+
context.setVariable('request.header.tracestate', tracestate);
124146

125147
// Values needed by the Reponse to write to New Relic
126-
context.setVariable("traceContext.requestParentID", requestParentID);
127-
context.setVariable("traceContext.parentID", parentID);
128-
context.setVariable("traceContext.traceID", traceID);
129-
context.setVariable("traceContext.traceFlags", traceFlags);
130-
context.setVariable("traceContext.parentStartTime", context.getVariable("client.received.start.timestamp"));
148+
context.setVariable(ctxPrefix + 'requestParentID', requestParentID);
149+
context.setVariable(ctxPrefix + 'parentID', parentID);
150+
context.setVariable(ctxPrefix+ 'traceID', traceID);
151+
context.setVariable(ctxPrefix+ 'traceFlags', traceFlags);
152+
context.setVariable(ctxPrefix+ 'parentStartTime', startTime);
153+
154+
logMsg(logPrefix + 'exit');
Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,77 @@
1-
"use strict"
2-
var traceId;
3-
try {
4-
//const buf = crypto.randomBytes(16);
5-
//var _timeNow = Number(context.getVariable('system.timestamp'));
6-
//traceId = crypto.dateFormat('YYYY-MM-DD HH:mm:ss.SSS','CST', _timeNow);
7-
8-
traceId = crypto.randomBytes(16);
9-
} catch (err) {
10-
traceId = 'TraceID';
11-
context.proxyResponse.headers['W3C-Trace-Context-Response-Err'] = err;
12-
context.proxyResponse.headers['W3C-Trace-Context-Response-Debug'] = Object.getOwnPropertyNames(crypto).filter(item => typeof crypto[item] === 'function');
1+
'use strict'
2+
3+
var RequestResponsePairName = properties.RequestResponsePairName;
4+
var logPrefix = RequestResponsePairName + '.TraceContextResponse: ';
5+
logMsg(logPrefix + 'enter');
6+
var ctxPrefix = 'traceContext.' + RequestResponsePairName;
7+
8+
var requestParentID = context.getVariable(ctxPrefix + 'requestParentID');
9+
var parentID = context.getVariable(ctxPrefix + 'parentID');
10+
var traceID = context.getVariable(ctxPrefix + 'traceID');
11+
var traceFlags = context.getVariable(ctxPrefix+ 'traceFlags');
12+
var parentStartTime = context.getVariable(ctxPrefix+ 'parentStartTime');
13+
14+
logMsg(logPrefix + 'RequestParentID: ' + requestParentID);
15+
logMsg(logPrefix + 'ParentID: ' + parentID);
16+
logMsg(logPrefix + 'TraceID: ' + traceID);
17+
logMsg(logPrefix + 'TraceFlags: ' + traceFlags);
18+
logMsg(logPrefix + 'parentStartTime: ' + parentStartTime);
19+
20+
var endTime = Date.now();
21+
var configuredEndTime = context.getVariable(properties.TraceEndTimestamp);
22+
if (configuredEndTime != null) {
23+
endTime = configuredEndTime;
24+
} else {
25+
logMsg(logPrefix + 'WARN: null value for TraceEndTimestamp: ' + properties.TraceEndTimestamp)
26+
}
27+
28+
var attributes= {
29+
'duration.ms': endTime - parentStartTime,
30+
'name': properties.RequestResponsePairName,
31+
'service.name': 'Apigee'
32+
};
33+
34+
if (requestParentID != null && requestParentID != ''){
35+
attributes['parent.id'] = requestParentID;
36+
}
37+
38+
var span = {
39+
'id': parentID,
40+
'trace.id': traceID,
41+
timestamp: parentStartTime,
42+
attributes: attributes
1343
}
14-
context.proxyResponse.headers['W3C-Trace-Context-Response'] = traceId;
44+
// TODO Is there a way to 'queue' the spans so they can be written as a batch? Maybe push them into the context?
45+
46+
var msg = [{
47+
'common': {
48+
'attributes': {
49+
50+
}
51+
},
52+
'spans': [span]
53+
}]
54+
55+
// TODO parameterize API Key & endpoint
56+
var headers = {
57+
'Content-Type': 'application/json',
58+
'Api-Key': '',
59+
'Data-Format': 'newrelic',
60+
'Data-Format-Version': '1'
61+
62+
};
63+
64+
var req = new Request('https://trace-api.newrelic.com/trace/v1', 'POST', headers, JSON.stringify(msg));
65+
function onComplete(response,error) {
66+
// Check if the HTTP request was successful
67+
if (response) {
68+
logMsg(logPrefix + 'onComplete: response.status: ' + response.status);
69+
} else {
70+
logMsg(logPrefix + 'onComplete: error: ' + error);
71+
}
72+
logsToResponseHeader();
73+
}
74+
75+
var exchange = httpClient.send(req, onComplete);
76+
77+
logMsg(logPrefix + 'exit');
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function logMsg(msg) {
2+
var index = context.getVariable("logger.index");
3+
if (index == null) {
4+
index = 1;
5+
}
6+
context.setVariable("logger.logMsg-" + index, msg);
7+
index = index + 1;
8+
context.setVariable("logger.index", index);
9+
}
10+
11+
// Call this function during Response processing
12+
function logsToResponseHeader() {
13+
var index = context.getVariable("logger.index");
14+
if (index == null) {
15+
context.proxyResponse.headers['Z-Log-0'] = 'logger.index == null';
16+
return
17+
}
18+
19+
context.proxyResponse.headers['Z-Log-0'] = 'logger.index == ' + index + ' type: ' + typeof (index);
20+
for (var i = 1; i < index; i++) {
21+
var msg = context.getVariable("logger.logMsg-" + i);
22+
if (msg == null) {
23+
context.proxyResponse.headers['Z-Log-' + i] = 'Null msg';
24+
} else {
25+
context.proxyResponse.headers['Z-Log-' + i] = msg;
26+
}
27+
}
28+
29+
}
30+

src/main/apigee/apiproxies/W3C-Trace-Context/apiproxy/targets/default.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
<TargetEndpoint name="default">
2+
<PreFlow name="PreFlow">
3+
<Request>
4+
<Step>
5+
<Name>Target-Trace-Context-Request</Name>
6+
</Step>
7+
<Step>
8+
<Name>Add-Trace-Context-To-Pub-Sub-Message</Name>
9+
</Step>
10+
</Request>
11+
</PreFlow>
12+
<PostFlow name="PostFlow">
13+
<Response>
14+
<Step>
15+
<Name>Target-Trace-Context-Response</Name>
16+
</Step>
17+
</Response>
18+
</PostFlow>
219
<HTTPTargetConnection>
320
<URL>https://mocktarget.apigee.net</URL>
421
</HTTPTargetConnection>

0 commit comments

Comments
 (0)