Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 3 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,14 @@ A utility providing a means of easily capturing trace messages for offline analy

## Installation

The only prerequisites not handled during the installation are a functional Node environment, the availability of npm, and sufficient priviledges to run commands as adminstrator. The steps below are applicable to a Mac OS X environment, similar steps work under Linux or Windows.

Clone this project to your local machine:

`$ git clone https://github.com/apigeecs/apigee-cli-trace.git`

Alternatively you can download the zip file via the GitHub home page and unzip the archive.

Navigate to the package directory:

`$ cd path/to/apigee-cli-trace/package/`

Install globally:

`$ sudo npm install . -g`
npm install apigee-coverage

## Usage
```
var trace = require("./package/apigee-cli-trace");
apigee-coverage -o askanapigeek -e test -a No-Target -r 4
Set Apigee_User and Apigee_Secret to utilize this feature as your environment variables

trace.capture({
debug: true,
org: "davidwallen2014",
env: "prod",
api: "24Solver",
rev: "19",
auth: "Basic encodeduserandsecret",
saveTo: "./capturedTraceFiles"
});
```

Execute the following:

`$ node ./capture.js`

Where `capture.js` is a script as outlined above. Note the script runs until cancelled.

Output includes a information summarizing captured trace messages:

Note that the utility captures a subset of traffic - it is not capable of nor intended to capture all traffic in a given run. Consider it as sampling as much as 90% or as low as 60% of traffic depending on the speed of your local machine, local network, and rate of traffic in the target proxy.

## Tests

none yet
Expand Down
301 changes: 301 additions & 0 deletions lib/apigee-cli-trace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
// packages
var path = require("path"),
fs = require("fs"),
https = require("https"),
traceResponse = {
"traceFiles": [],
"curTraceFile": {}
},
traceMessages = {},
config,
count = 0;

function print(msg) {
try {
if (msg && (typeof msg === "object")) {
console.log(JSON.stringify(msg));
} else {
console.log(msg);
}
} catch (error) {
console.log(error);
}
}

function debugPrint(msg) {
if (config.debug) {
print(msg);
}
}

function getStackTrace(e) {
return e.stack.replace(/^[^\(]+?[\n$]/gm, "")
.replace(/^\s+at\s+/gm, "")
.replace(/^Object.<anonymous>\s*\(/gm, "{anonymous}()@")
.split("\n");
}

function mkdirSync(path) {
try {
fs.existsSync(path) || fs.mkdirSync(path);
} catch (e) {
if (e.code !== "EEXIST") {
throw e;
}
}
}

function mkdirpSync(dirpath) {
var parts = dirpath.split(path.sep);
for (var i = 1; i <= parts.length; i++) {
mkdirSync(path.join.apply(null, parts.slice(0, i)));
}
}

function writeTraceFile(id, data) {
//file exists? If not create
//append to it
if (config.saveTo) {
fs.existsSync(config.saveTo) || mkdirpSync(config.saveTo);
fs.writeFile(config.saveTo + "/" + id + ".xml", data, function(err) {
if (err) { console.error(err); }
});
if ((++count % 10) === 0) print(count + " messages saved...");
}
}

function processTraceTransaction(trans) {
var data = "",
id = trans.id;

var options = {
host: "api.enterprise.apigee.com",
port: 443,
path: "/v1/organizations/" + config.org + "/environments/" + config.env + "/apis/" + config.api + "/revisions/" + config.rev + "/debugsessions/" + config.debugSessionId + "/data/" + id,
method: "GET",
headers: {
Accept: "application/xml",
Authorization: config.auth
}
};

var req = https.request(options, function(res) {
res.on("data", function(d) {
data += d;
});
res.on("end", function() {
if (data.indexOf("<Completed>true</Completed>") > -1) {
writeTraceFile(id, data);
}
});
});

req.on("error", function(e) {
print("error in the https call");
console.error(e);
trans.processed = false;
});
req.end();

}

function uuid() {
var d = new Date().getTime();
var theUuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === "x" ? r : (r & 0x7 | 0x8)).toString(16);
});
return theUuid;
}

function isJson(blob) {
return (/^[\],:{}\s]*$/.test(blob.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, "")));
}

function processDebugSession() {
var options = {
host: "api.enterprise.apigee.com",
port: 443,
path: "/v1/organizations/" + config.org + "/environments/" + config.env + "/apis/" + config.api + "/revisions/" + config.rev + "/debugsessions?session=" + config.debugSessionId,
method: "POST",
headers: {
//Accept: "application/json",
Authorization: config.auth
// "Content-Type": "application/x-www-url-form-encoded"
}
};

var req = https.request(options, function(res) {
res.setEncoding("utf8");
if (res.statusCode >= 300) {
console.error(res.statusCode + ": " + res.statusMessage + " with " + JSON.stringify(options));
}
res.on("data", function(d) {
d = JSON.parse(d);
config.debugSessionId = d.name;
config.debugStart = new Date();
//now we want to call the retrieval loop
processTraceTransactions();
});
});

req.on("error", function(e) {
console.error(e);
console.error("error in creating a debug session with " + JSON.stringify(options));
});
req.end();
}

function processTraceMessages() {
for (var id in traceMessages) {
if ({}.hasOwnProperty.call(traceMessages, id)) {
if (!traceMessages[id].processed && !traceMessages[id].inProcess) {
traceMessages[id].inProcess = true;
processTraceTransaction(traceMessages[id]);

}
}
}
}

function processTransactionPayload(str) {
var d = JSON.parse(str);
/*"{
"code" : "distribution.DebugSessionNotFound",
"message" : "DebugSession bdbfa0a5-3dc3-4971-edfb-25a277f5d7bd not found",
"contexts" : [ ]
}"*/

if (d.code === "distribution.DebugSessionNotFound" || d.length >= 20 || ((new Date() - config.debugStart) > 10 * 60 * 1000)) {
config.debugSessionId = uuid();
processDebugSession();
} else {
for (var i = d.length; i-- > 0;) {
traceMessages[d[i]] = traceMessages[d[i]] || {
id: d[i],
processed: false,
inProcess: false
};
}
processTraceMessages();
processTraceTransactions();
}
}

function processTraceTransactions() {
var options = {
host: "api.enterprise.apigee.com",
port: 443,
path: "/v1/organizations/" + config.org + "/environments/" + config.env + "/apis/" + config.api + "/revisions/" + config.rev + "/debugsessions/" + config.debugSessionId + "/data",
method: "GET",
headers: {
Accept: "application/json",
Authorization: config.auth
}
},
data = "";

var req = https.request(options, function(res) {
res.setEncoding("utf8");
res.on("data", function(d) {
data += d;
});
res.on("end", function() {
if (isJson(data)) {
processTransactionPayload(data);
} else {
print("error in the the response - JSON not found");
print(data);
}
});
});

req.setTimeout(30000, function() {
//when timeout, this callback will be called
});

req.on("error", function(e) {
print("error in the https call");
console.error(e);
});
req.end();
}

function buildAuth() {
var user = process.env.Apigee_User,
secret = process.env.Apigee_Secret;
if (!user || !secret) {
var errMsg = "no authorization provided and no env variable(s) for Apigee_User and/or Apigee_Secret";
print(errMsg);
print(process.env);
throw new Error(errMsg);
}
return ("Basic " + (new Buffer(user + ":" + secret)).toString("base64"));
}

function processStopTraceSession() {

var options = {
host: "api.enterprise.apigee.com",
port: 443,
path: "/v1/organizations/" + config.org + "/environments/" + config.env + "/apis/" + config.api + "/revisions/" + config.rev + "/debugsessions/" + config.debugSessionId,
method: "DELETE",
headers: {
//Accept: "application/json",
Authorization: config.auth,
"Content-length":0,
"Content-Type": "application/x-www-url-form-encoded"
}
};
var req = https.request(options, function(res) {
res.setEncoding("utf8");
if (res.statusCode >= 400) {
console.error(res.statusCode + ": " + res.statusMessage + " with " + JSON.stringify(options));
}
res.on("data", function(d) {
d = JSON.parse(d);
process.exit(130);
});

res.on("error", function(e) {
console.log("error="+e);
}

);
});
req.on("error", function(e) {
console.error(e);
console.error("error in creating a debug session with " + JSON.stringify(options));
});
req.end();
}


var capture = function(aConfig) {
config = aConfig;

process.on('SIGINT', function() {
print("Caught interrupt signal");
print(count + " messages saved...");
print("________cleaningup-wait____________");
processStopTraceSession();
});


try {
console.log("loading live trace data");
config.debugSessionId = config.debugSessionId || uuid();
config.auth = config.auth || buildAuth();
processDebugSession();
} catch (e) {
var stack = getStackTrace(e);
print("error:");
print(e);
print(stack);
}
};

module.exports = {
capture
};
35 changes: 35 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env node
var trace = require("./lib/apigee-cli-trace"),
program = require('commander');

program
.usage('<options>')
.version('0.1.0')
.option('-o --org <orgname>','organization name')
.option('-e --env <envname>','environment name')
.option('-a --api <apiname>','Apiproxy name')
.option('-r --revision <revision>','Proxy revision number')

program.on('--help', function(){
console.log("example");
console.log('');
console.log('apigee-coverage -o askanapigeek -e test -a No-Target -r 4');
console.log('');
});

program.parse(process.argv);

var config = {};
config.org = program.org;
config.env = program.env;
config.api = program.api;
config.rev = program.revision;
config.saveTo = "./capturedTraceFiles";


if (!process.argv.slice(2).length) {
program.outputHelp();
}
else {
trace.capture(config);
}
Loading