Skip to content

Commit 1da45eb

Browse files
Auto-Instrumented Express example (signalfx#22)
1 parent d553d73 commit 1da45eb

File tree

14 files changed

+710
-0
lines changed

14 files changed

+710
-0
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,18 @@ language/platform.
109109
- [AWS Lambda](./aws-lambda): Examples for instrumenting spans for AWS Lambda written in Java, Python, Node, Go
110110

111111

112+
## Auto-Instrumentation
113+
114+
For customers who have not instrumented their applications, or have done so in an OpenTracing-compatible
115+
fashion, we offer several SignalFx Tracing libraries. Their detailed documentation is available in their
116+
respective source locations:
117+
118+
- [Java](https://github.com/signalfx/signalfx-java-tracing)
119+
- [Node.js](https://github.com/signalfx/signalfx-nodejs-tracing)
120+
- [Python](https://github.com/signalfx/signalfx-python-tracing)
121+
- [Ruby](https://github.com/signalfx/signalfx-ruby-tracing)
122+
123+
Please note these offerings are currently in Beta status.
124+
125+
- [SignalFx Tracing Examples](./signalfx-tracing)
126+

signalfx-tracing/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SignalFx Tracing Libraries
2+
3+
This repository contains example applications for some popular open source web
4+
frameworks and libraries that have been auto-instrumented using a SignalFx Tracing
5+
library for their respective platforms and languages. It is meant as a potentially
6+
more tangible introduction to getting starting with auto-instrumentation in your
7+
environment of choice than general documentation can sometimes be.
8+
9+
## SignalFx Tracing Supported Languages and Platforms
10+
11+
### [Node.js](./signalfx-nodejs-tracing) - https://github.com/signalfx/signalfx-nodejs-tracing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SignalFx Tracing Library for JavaScript
2+
3+
This repository contains an example application that utilizes auto-instrumentation
4+
for a supported web framework and the Node.js http client. This is made possible
5+
via the [SignalFx Tracing Library for JavaScript](https://github.com/signalfx/signalfx-nodejs-tracing).
6+
7+
For more detailed information about the functionality of the provided tracer and
8+
instrumentation configuration options, please see the
9+
[`signalfx-tracing`](https://www.npmjs.com/package/signalfx-tracing) module's
10+
[API documentation](https://github.com/signalfx/signalfx-nodejs-tracing/blob/master/docs/API.md).
11+
12+
## Libraries and Framework Examples
13+
14+
### [Express](./express)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Express.js Auto-Instrumentation Example
2+
3+
This is an example of automatically producing distributed traces using the
4+
[SignalFx Tracing Library for JavaScript](https://github.com/signalfx/signalfx-nodejs-tracing).
5+
Please examine the instrumented [client](./client.js) and [server](./server.js) for
6+
some basic patterns in accessing the instrumentations of an [http](https://nodejs.org/api/http.html)
7+
client and an [Express](http://expressjs.com/) application. This example is of a simple
8+
guess-the-word game named "Snowman" that is auto-instrumented by a lone
9+
[tracer invocation](./snowman/tracer.js).
10+
11+
## Building the example app and client
12+
13+
To run this example locally and send traces to your available Smart Agent or Gateway,
14+
from this directory do the following:
15+
16+
```bash
17+
$ npm install
18+
$ # Run the server from one shell session:
19+
$ npm start
20+
$ # Run the client commands from another:
21+
$ # You can also use ` ./client.js --help` directly
22+
$ npm run client -- --help
23+
Snowman [command]
24+
25+
Commands:
26+
snowman new Make a new game.
27+
snowman guess [letter] Make a guess.
28+
snowman answer Report the answer
29+
snowman delete Delete your current game
30+
```
31+
32+
The `signalfx-tracing` module and this application configuration assume that your Smart Agent
33+
or Gateway is accepting traces at http://localhost:9080/v1/trace. If this is not the case,
34+
you can set the `SIGNALFX_INGEST_URL` environment variable to the desired url to suit your
35+
environment before launching the server and client.
36+
37+
## Playing Snowman
38+
39+
The example application is a simple word game where you must guess an unknown word by its
40+
constituent letters from a pool of 1000 randomly selected Unix `words` file entries. Each
41+
client request will automatically create an initiating parent span for distributed propagation
42+
to some basic REST api endpoints implemented via Express.
43+
44+
```bash
45+
$ # You can also use ` ./client.js new` directly
46+
$ npm run client new
47+
message: Game successfully created.
48+
progress: _________
49+
50+
$ npm run client guess x
51+
message: Error: There is no "x" in the word.
52+
guesses: x
53+
progress: _________
54+
remainingMisses: 7
55+
( ............... )
56+
57+
$ npm run client answer
58+
message: The answer was: manducate. Deleting game.
59+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env node
2+
// Initialize the tracer and initiate auto-instrumentation of all supported libraries
3+
// and frameworks. In this client example, we instrumented the http module used by the
4+
// snowman client module.
5+
const tracer = require('./snowman/tracer')
6+
7+
// Note that importing other modules should occur after init() to ensure their supported
8+
// dependencies have been auto-instrumented.
9+
const yargs = require('yargs')
10+
const fs = require('fs')
11+
const client = require('./snowman/client')
12+
13+
function writeId(id) {
14+
// Write a new game's id to local .snowman file
15+
return new Promise((resolve, reject) => {
16+
fs.writeFile('.snowman', id, (err) => {
17+
if (err) {
18+
reject(err)
19+
} else {
20+
resolve()
21+
}
22+
})
23+
})
24+
}
25+
26+
function getId() {
27+
// Obtain an existing game's id from local .snowman
28+
return new Promise((resolve, reject) => {
29+
fs.readFile('.snowman', (err, data) => {
30+
if (err) {
31+
reject(`Error obtaining game id: ${err}. Be sure to run "new" to create a game.`)
32+
} else {
33+
const id = data.toString()
34+
resolve(id)
35+
}
36+
})
37+
})
38+
}
39+
40+
function printResponse(response) {
41+
const properties = ['message', 'guesses', 'progress', 'remainingMisses']
42+
for (let i = 0; i < properties.length; i++) {
43+
const prop = properties[i]
44+
if (response[prop]) {
45+
console.log(`${prop}: ${response[prop]}`)
46+
}
47+
}
48+
}
49+
50+
yargs
51+
.scriptName('snowman')
52+
.command('new', 'Make a new game.', {}, (argv) => {
53+
client.newGame().then((response) => {
54+
return writeId(response.id).then(() => {
55+
printResponse(response)
56+
}).catch((e) => console.error(e))
57+
}).catch((e) => console.error(e))
58+
})
59+
.command('guess [letter]', 'Make a guess.', {}, (argv) => {
60+
getId().then((id) => {
61+
client.makeGuess(argv.letter, id).then((response) => {
62+
printResponse(response)
63+
if (response.remainingMisses !== undefined) {
64+
// render the snowman
65+
for (let i = response.remainingMisses; i < 8; i++ ) {
66+
console.log(client.snowman[i])
67+
}
68+
}
69+
}).catch((e) => console.error(e))
70+
}).catch((e) => console.error(e))
71+
})
72+
.command('answer', 'Report the answer', {}, (argv) => {
73+
getId().then((id) => {
74+
client.getAnswer(id).then(response => {
75+
printResponse(response)
76+
}).catch((e) => console.error(e))
77+
}).catch((e) => console.error(e))
78+
})
79+
.command('delete', 'Delete your current game', {}, (argv) => {
80+
getId().then((id) => {
81+
client.deleteGame(id).then(response => {
82+
printResponse(response)
83+
}).catch((e) => console.error(e))
84+
}).catch((e) => console.error(e))
85+
})
86+
.help().argv
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "traced-express-snowman",
3+
"version": "1.0.0",
4+
"description": "An auto-instrumented Express word game",
5+
"main": "server.js",
6+
"scripts": {
7+
"start": "SIGNALFX_TRACING_DEBUG=true SIGNALFX_SERVICE_NAME=snowman-server node ./server.js",
8+
"client": "SIGNALFX_SERVICE_NAME=snowman-client node ./client.js"
9+
},
10+
"dependencies": {
11+
"body-parser": ">=1.0.0",
12+
"express": ">=4.0.0",
13+
"signalfx-tracing": "latest",
14+
"uuid": ">=3.0.0",
15+
"yargs": "^13.2.1"
16+
},
17+
"author": "SignalFx, Inc.",
18+
"license": "Apache-2.0",
19+
"devDependencies": {
20+
"eslint": "^5.15.1"
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// The core of the auto-instrumentation process is made in the
2+
// tracer.js module. It consists of a single import and invocation
3+
// of the 'signalfx-tracing' module.
4+
const tracer = require('./snowman/tracer')
5+
6+
// Note express module import must come after init() that occurs
7+
// in snowman/tracer module import, as well as any modules that load it.
8+
const express = require('express')
9+
const bodyParser = require('body-parser')
10+
const snowman = require('./snowman')
11+
12+
const app = express()
13+
app.use(bodyParser.json())
14+
app.use('/snowman', snowman.router)
15+
16+
app.listen(snowman.config.serverPort)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const { serverUrl } = require('./config')
2+
3+
// http module auto-instrumentation will occur once the tracer is initialized,
4+
// which occurs in the sourcing client script.
5+
const http = require('http')
6+
7+
const snowmanUrl = `${serverUrl}/snowman`
8+
9+
// progress graphic
10+
let snowman = [' (\'_\')']
11+
for (let i = 1; i < 8; i++) {
12+
snowman.push(`${' '.repeat(8 - i)}( ${'.'.repeat(i * 2 + 1)} )`)
13+
}
14+
15+
function resolveData(res, resolve) {
16+
// resolves a Promise with parsed http response
17+
let data = ''
18+
res.on('data', d => {
19+
data += d
20+
})
21+
res.on('end', () => {
22+
resolve(JSON.parse(data))
23+
})
24+
}
25+
26+
function newGame() {
27+
return new Promise((resolve, reject) => {
28+
const req = http.request(`${snowmanUrl}/new`, { method: 'POST' }, (res) => {
29+
resolveData(res, resolve)
30+
})
31+
req.on('error', (e) => reject(e))
32+
req.end()
33+
})
34+
}
35+
36+
function makeGuess(guess, id) {
37+
return new Promise((resolve, reject) => {
38+
const req = http.request(`${snowmanUrl}/${id}`, { method: 'POST' }, res => {
39+
resolveData(res, resolve)
40+
})
41+
req.on('error', (e) => reject(e))
42+
43+
const content = { guess }
44+
req.setHeader('Content-Type', 'application/json')
45+
req.write(JSON.stringify(content))
46+
req.end()
47+
})
48+
}
49+
50+
function getAnswer(id) {
51+
return new Promise((resolve, reject) => {
52+
const req = http.request(`${snowmanUrl}/${id}/answer`, { method: 'GET' }, res => {
53+
resolveData(res, resolve)
54+
})
55+
req.on('error', (e) => reject(e))
56+
req.end()
57+
})
58+
}
59+
60+
function deleteGame(id) {
61+
return new Promise((resolve, reject) => {
62+
const req = http.request(`${snowmanUrl}/${id}`, { method: 'DELETE' }, res => {
63+
resolveData(res, resolve)
64+
})
65+
req.on('error', (e) => reject(e))
66+
req.end()
67+
})
68+
}
69+
70+
module.exports = { snowman, newGame, makeGuess, getAnswer, deleteGame }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const serverPort = 3000
2+
3+
module.exports= {
4+
serverPort,
5+
serverUrl: `http://localhost:${serverPort}`
6+
}

0 commit comments

Comments
 (0)