Skip to content

Commit 58cd219

Browse files
carolinee21obecny
andauthored
feat: Hapi auto-instrumentation (open-telemetry#171)
* feat: adding hapi instrumentation chore: more setup feat: finished wrapping for server.route feat: tracing for individual routes fix: add hapi router tests, add plugin end logic chore: testing with server feat: adding instrumentation for Hapi.server, Hapi.Server [sic], and Hapi plugin register function feat: improve tests + edge case coverage feat: enforcing that each route/plugin is instrumented at most once feat: add Hapi example code chore: update README Add files via upload chore: add example images fix: modifying type definitions and coverage feat: refactoring to add custom span name and attributes for plugins chore: add tests for package-based plugins chore: update example photos Delete jaeger.jpg Delete zipkin.jpg chore: update example photos chore: update examples feat: adding instrumentation for server.ext functions chore: add tests for extensions added within plugin fix: update example fix: update example photos to include ext spans fix: update example code to include request extension instrumentation chore: update README docs: add tsdoc comments for Hapi plugin functions chore: bump version to 0.9.0 chore: fix style fix: more test updates * chore: bump opentelemetry core module dependencies to ^0.10.2 * fix: code style with async/await * fix: small lint fix * fix: do not run tests for Hapi instrumentation in node8 and node10 * fix: changing type definitions and checks * chore: minor code style updates Co-authored-by: Bartlomiej Obecny <bobecny@gmail.com>
1 parent 17005e4 commit 58cd219

24 files changed

+2270
-0
lines changed

Diff for: examples/hapi/README.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Overview
2+
3+
OpenTelemetry Hapi Instrumentation allows the user to automatically collect trace data and export them to the backend of choice (we can use Zipkin or Jaeger for this example), to give observability to distributed systems.
4+
5+
This is a simple example that demonstrates tracing calls made in a Hapi application. The example shows key aspects of tracing such as
6+
- Root Span (on Client)
7+
- Child Span (on Client)
8+
- Span Attributes
9+
- Instrumentation for routes and request extension points
10+
- Instrumentation of Hapi plugins
11+
12+
## Installation
13+
14+
```sh
15+
$ # from this directory
16+
$ npm install
17+
```
18+
19+
Setup [Zipkin Tracing](https://zipkin.io/pages/quickstart.html)
20+
or
21+
Setup [Jaeger Tracing](https://www.jaegertracing.io/docs/latest/getting-started/#all-in-one)
22+
23+
## Run the Application
24+
25+
### Zipkin
26+
27+
- Run the server
28+
29+
```sh
30+
# from this directory
31+
$ npm run zipkin:server
32+
```
33+
34+
- Run the client
35+
36+
```sh
37+
# from this directory
38+
npm run zipkin:client
39+
```
40+
41+
#### Zipkin UI
42+
`zipkin:server` script should output the `traceid` in the terminal (e.g `traceid: 4815c3d576d930189725f1f1d1bdfcc6`).
43+
Go to Zipkin with your browser [http://localhost:9411/zipkin/traces/(your-trace-id)]() (e.g http://localhost:9411/zipkin/traces/4815c3d576d930189725f1f1d1bdfcc6)
44+
45+
<p align="center"><img src="./images/zipkin.jpg?raw=true"/></p>
46+
47+
### Jaeger
48+
49+
- Run the server
50+
51+
```sh
52+
# from this directory
53+
$ npm run jaeger:server
54+
```
55+
56+
- Run the client
57+
58+
```sh
59+
# from this directory
60+
npm run jaeger:client
61+
```
62+
63+
#### Jaeger UI
64+
65+
`jaeger:server` script should output the `traceid` in the terminal (e.g `traceid: 4815c3d576d930189725f1f1d1bdfcc6`).
66+
Go to Jaeger with your browser [http://localhost:16686/trace/(your-trace-id)]() (e.g http://localhost:16686/trace/4815c3d576d930189725f1f1d1bdfcc6)
67+
68+
<p align="center"><img src="images/jaeger.jpg?raw=true"/></p>
69+
70+
## Useful links
71+
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
72+
- For more information on OpenTelemetry for Node.js, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node>
73+
74+
## LICENSE
75+
76+
Apache License 2.0

Diff for: examples/hapi/client.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
// eslint-disable-next-line import/order
4+
const tracer = require('./tracer')('example-hapi-client');
5+
const api = require('@opentelemetry/api');
6+
const axios = require('axios').default;
7+
8+
function makeRequest() {
9+
const span = tracer.startSpan('client.makeRequest()', {
10+
parent: tracer.getCurrentSpan(),
11+
kind: api.SpanKind.CLIENT,
12+
});
13+
14+
tracer.withSpan(span, async () => {
15+
try {
16+
const res = await axios.get('http://localhost:8081/run_test');
17+
span.setStatus({ code: api.CanonicalCode.OK });
18+
console.log(res.statusText);
19+
} catch (e) {
20+
span.setStatus({ code: api.CanonicalCode.UNKNOWN, message: e.message });
21+
}
22+
span.end();
23+
console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.');
24+
setTimeout(() => { console.log('Completed.'); }, 5000);
25+
});
26+
}
27+
28+
makeRequest();

Diff for: examples/hapi/images/jaeger.jpg

490 KB
Loading

Diff for: examples/hapi/images/zipkin.jpg

616 KB
Loading

Diff for: examples/hapi/package.json

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "hapi-example",
3+
"private": true,
4+
"version": "0.9.0",
5+
"description": "Example of Hapi auto-instrumentation with OpenTelemetry",
6+
"main": "index.js",
7+
"scripts": {
8+
"zipkin:server": "cross-env EXPORTER=zipkin node ./server.js",
9+
"zipkin:client": "cross-env EXPORTER=zipkin node ./client.js",
10+
"jaeger:server": "cross-env EXPORTER=jaeger node ./server.js",
11+
"jaeger:client": "cross-env EXPORTER=jaeger node ./client.js",
12+
"lint": "eslint . --ext .js",
13+
"lint:fix": "eslint . --ext .js --fix"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js-contrib.git"
18+
},
19+
"keywords": [
20+
"opentelemetry",
21+
"hapi",
22+
"tracing",
23+
"instrumentation"
24+
],
25+
"engines": {
26+
"node": ">=8"
27+
},
28+
"author": "OpenTelemetry Authors",
29+
"license": "Apache-2.0",
30+
"bugs": {
31+
"url": "https://github.com/open-telemetry/opentelemetry-js-contrib/issues"
32+
},
33+
"dependencies": {
34+
"@hapi/hapi": "^19.2.0",
35+
"@opentelemetry/api": "^0.10.2",
36+
"@opentelemetry/exporter-jaeger": "^0.10.2",
37+
"@opentelemetry/exporter-zipkin": "^0.10.2",
38+
"@opentelemetry/hapi-instrumentation": "^0.9.0",
39+
"@opentelemetry/node": "^0.10.2",
40+
"@opentelemetry/plugin-http": "^0.9.0",
41+
"@opentelemetry/tracing": "^0.10.2",
42+
"axios": "^0.19.0"
43+
},
44+
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib#readme",
45+
"devDependencies": {
46+
"cross-env": "^6.0.0",
47+
"eslint": "^7.4.0"
48+
}
49+
}

Diff for: examples/hapi/server.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
3+
const tracer = require('./tracer')('example-hapi-server');
4+
// eslint-disable-next-line
5+
const Hapi = require('@hapi/hapi');
6+
7+
const PORT = 8081;
8+
const server = Hapi.server({
9+
port: PORT,
10+
host: 'localhost',
11+
});
12+
13+
const BlogPostPlugin = {
14+
name: 'blog-post-plugin',
15+
version: '1.0.0',
16+
async register(serverClone) {
17+
console.log('Registering basic hapi plugin');
18+
19+
serverClone.route([
20+
{
21+
method: 'GET',
22+
path: '/post/new',
23+
handler: addPost,
24+
},
25+
{
26+
method: 'GET',
27+
path: '/post/{id}',
28+
handler: showNewPost,
29+
}]);
30+
},
31+
};
32+
33+
async function setUp() {
34+
await server.register(
35+
{ plugin: BlogPostPlugin },
36+
);
37+
38+
server.route(
39+
{
40+
method: 'GET',
41+
path: '/run_test',
42+
handler: runTest,
43+
},
44+
);
45+
46+
server.ext('onRequest', async (request, h) => {
47+
console.log('No-op Hapi lifecycle extension method');
48+
const syntheticDelay = 50;
49+
await new Promise((r) => setTimeout(r, syntheticDelay));
50+
return h.continue;
51+
});
52+
53+
await server.start();
54+
console.log('Server running on %s', server.info.uri);
55+
console.log(`Listening on http://localhost:${PORT}`);
56+
}
57+
58+
/**
59+
* Blog Post functions: list, add, or show posts
60+
*/
61+
const posts = ['post 0', 'post 1', 'post 2'];
62+
63+
function addPost(_, h) {
64+
posts.push(`post ${posts.length}`);
65+
const currentSpan = tracer.getCurrentSpan();
66+
currentSpan.addEvent('Added post');
67+
currentSpan.setAttribute('Date', new Date());
68+
console.log(`Added post: ${posts[posts.length - 1]}`);
69+
return h.redirect('/post/3');
70+
}
71+
72+
async function showNewPost(request) {
73+
const { id } = request.params;
74+
console.log(`showNewPost with id: ${id}`);
75+
const post = posts[id];
76+
if (!post) throw new Error('Invalid post id');
77+
const syntheticDelay = 200;
78+
await new Promise((r) => setTimeout(r, syntheticDelay));
79+
return post;
80+
}
81+
82+
function runTest(_, h) {
83+
const currentSpan = tracer.getCurrentSpan();
84+
const { traceId } = currentSpan.context();
85+
console.log(`traceid: ${traceId}`);
86+
console.log(`Jaeger URL: http://localhost:16686/trace/${traceId}`);
87+
console.log(`Zipkin URL: http://localhost:9411/zipkin/traces/${traceId}`);
88+
return h.redirect('/post/new');
89+
}
90+
91+
setUp();
92+
process.on('unhandledRejection', (err) => {
93+
console.log(err);
94+
process.exit(1);
95+
});

Diff for: examples/hapi/tracer.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const opentelemetry = require('@opentelemetry/api');
4+
const { NodeTracerProvider } = require('@opentelemetry/node');
5+
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
6+
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
7+
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
8+
9+
const EXPORTER = process.env.EXPORTER || '';
10+
11+
module.exports = (serviceName) => {
12+
const provider = new NodeTracerProvider({
13+
plugins: {
14+
'@hapi/hapi': {
15+
enabled: true,
16+
path: '@opentelemetry/hapi-instrumentation',
17+
enhancedDatabaseReporting: true,
18+
},
19+
http: {
20+
enabled: true,
21+
path: '@opentelemetry/plugin-http',
22+
},
23+
},
24+
});
25+
26+
let exporter;
27+
if (EXPORTER === 'jaeger') {
28+
exporter = new JaegerExporter({ serviceName });
29+
} else {
30+
exporter = new ZipkinExporter({ serviceName });
31+
}
32+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
33+
34+
// Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings
35+
provider.register();
36+
37+
return opentelemetry.trace.getTracer('hapi-example');
38+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
"env": {
3+
"mocha": true,
4+
"node": true
5+
},
6+
...require('../../../eslint.config.js')
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
const semver = require('semver');
4+
5+
if (semver.satisfies(process.version, '>=12.0.0')) {
6+
module.exports = {
7+
spec: 'test/**/*.ts',
8+
};
9+
} else {
10+
console.log(`Hapi instrumentation tests skipped for Node.js ${process.version} - unsupported by Hapi`);
11+
module.exports = {};
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/bin
2+
/coverage
3+
/doc
4+
/test

0 commit comments

Comments
 (0)