Skip to content

Commit ee1533a

Browse files
authored
gRPC example using proto-loader (#385)
* chore: gRPC example using proto-loader * chore: gRPC example using proto-loader * fix: review comments * fix: we use latest OT package versions
1 parent aab87a2 commit ee1533a

File tree

6 files changed

+269
-0
lines changed

6 files changed

+269
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Overview
2+
3+
Our service takes in a payload containing bytes and capitalizes them.
4+
5+
Using OpenTelemetry gRPC Instrumentation, we can collect traces of our system and export them to the backend of our choice (we can use Zipkin or Jaeger for this example), to give observability to our distributed systems.
6+
7+
> This is the dynamic code generation variant of the gRPC examples. Code in these examples is generated at runtime using Protobuf.js.
8+
9+
## Installation
10+
11+
```sh
12+
$ # from this directory
13+
$ npm install
14+
```
15+
16+
Setup [Zipkin Tracing](https://zipkin.io/pages/quickstart.html)
17+
or
18+
Setup [Jaeger Tracing](https://www.jaegertracing.io/docs/latest/getting-started/#all-in-one)
19+
20+
## Run the Application
21+
22+
### Zipkin
23+
24+
- Run the server
25+
26+
```sh
27+
$ # from this directory
28+
$ npm run zipkin:server
29+
```
30+
31+
- Run the client
32+
33+
```sh
34+
$ # from this directory
35+
$ npm run zipkin:client
36+
```
37+
38+
### Jaeger
39+
40+
- Run the server
41+
42+
```sh
43+
$ # from this directory
44+
$ npm run jaeger:server
45+
```
46+
47+
- Run the client
48+
49+
```sh
50+
$ # from this directory
51+
$ npm run jaeger:client
52+
```
53+
54+
## Useful links
55+
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
56+
- For more information on OpenTelemetry for Node.js, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node-sdk>
57+
58+
## LICENSE
59+
60+
Apache License 2.0
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const opentelemetry = require('@opentelemetry/core');
4+
const config = require('./setup');
5+
6+
/**
7+
* The trace instance needs to be initialized first, if you want to enable
8+
* automatic tracing for built-in plugins (gRPC in this case).
9+
*/
10+
config.setupTracerAndExporters('grpc-client-service');
11+
12+
const path = require('path');
13+
const grpc = require('grpc');
14+
const protoLoader = require('@grpc/proto-loader');
15+
16+
const tracer = opentelemetry.getTracer();
17+
18+
const PROTO_PATH = path.join(__dirname, 'protos/defs.proto');
19+
const PROTO_OPTIONS = { keepCase: true, enums: String, defaults: true, oneofs: true };
20+
const definition = protoLoader.loadSync(PROTO_PATH, PROTO_OPTIONS);
21+
const rpcProto = grpc.loadPackageDefinition(definition).rpc;
22+
23+
function main() {
24+
const client = new rpcProto.Fetch('localhost:50051',
25+
grpc.credentials.createInsecure());
26+
const data = process.argv[2] || 'opentelemetry';
27+
console.log('> ', data);
28+
29+
const span = tracer.startSpan('tutorialsClient.capitalize');
30+
tracer.withSpan(span, () => {
31+
client.capitalize({ data: Buffer.from(data) }, function (err, response) {
32+
if (err) {
33+
console.log('could not get grpc response');
34+
return;
35+
}
36+
console.log('< ', response.data.toString('utf8'));
37+
// display traceid in the terminal
38+
console.log(`traceid: ${span.context().traceId}`);
39+
span.end();
40+
});
41+
});
42+
43+
// The process must live for at least the interval past any traces that
44+
// must be exported, or some risk being lost if they are recorded after the
45+
// last export.
46+
console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.')
47+
setTimeout(() => { console.log('Completed.'); }, 5000);
48+
}
49+
50+
main();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const { SpanKind } = require('@opentelemetry/types');
4+
const opentelemetry = require('@opentelemetry/core');
5+
6+
/**
7+
* The trace instance needs to be initialized first, if you want to enable
8+
* automatic tracing for built-in plugins (gRPC in this case).
9+
*/
10+
const config = require('./setup');
11+
config.setupTracerAndExporters('grpc-server-service');
12+
13+
const path = require('path');
14+
const grpc = require('grpc');
15+
const protoLoader = require('@grpc/proto-loader');
16+
17+
const PROTO_PATH = path.join(__dirname, 'protos/defs.proto');
18+
const PROTO_OPTIONS = { keepCase: true, enums: String, defaults: true, oneofs: true };
19+
const definition = protoLoader.loadSync(PROTO_PATH, PROTO_OPTIONS);
20+
const rpcProto = grpc.loadPackageDefinition(definition).rpc;
21+
22+
const tracer = opentelemetry.getTracer();
23+
24+
/** Implements the Capitalize RPC method. */
25+
function capitalize(call, callback) {
26+
const currentSpan = tracer.getCurrentSpan();
27+
// display traceid in the terminal
28+
console.log(`traceid: ${currentSpan.context().traceId}`);
29+
30+
const span = tracer.startSpan('tutorials.FetchImpl.capitalize', {
31+
parent: currentSpan,
32+
kind: SpanKind.SERVER,
33+
});
34+
35+
const data = call.request.data.toString('utf8');
36+
const capitalized = data.toUpperCase();
37+
for (let i = 0; i < 100000000; i++) {}
38+
span.end();
39+
callback(null, { data: Buffer.from(capitalized) });
40+
}
41+
42+
/**
43+
* Starts an RPC server that receives requests for the Fetch service at the
44+
* sample server port.
45+
*/
46+
function main() {
47+
const server = new grpc.Server();
48+
server.addService(rpcProto.Fetch.service, { capitalize: capitalize });
49+
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
50+
server.start();
51+
}
52+
53+
main();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "grpc-dynamic-codegen-example",
3+
"version": "0.1.1",
4+
"description": "Example of gRPC integration with OpenTelemetry",
5+
"main": "index.js",
6+
"scripts": {
7+
"zipkin:server": "cross-env EXPORTER=zipkin node ./capitalize_server.js",
8+
"zipkin:client": "cross-env EXPORTER=zipkin node ./capitalize_client.js",
9+
"jaeger:server": "cross-env EXPORTER=jaeger node ./capitalize_server.js",
10+
"jaeger:client": "cross-env EXPORTER=jaeger node ./capitalize_client.js"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+ssh://[email protected]/open-telemetry/opentelemetry-js.git"
15+
},
16+
"keywords": [
17+
"opentelemetry",
18+
"grpc",
19+
"tracing"
20+
],
21+
"engines": {
22+
"node": ">=8"
23+
},
24+
"author": "OpenTelemetry Authors",
25+
"license": "Apache-2.0",
26+
"bugs": {
27+
"url": "https://github.com/open-telemetry/opentelemetry-js/issues"
28+
},
29+
"dependencies": {
30+
"@grpc/proto-loader": "^0.4.0",
31+
"@opentelemetry/tracing": "^0.1.1",
32+
"@opentelemetry/types": "^0.1.1",
33+
"@opentelemetry/core": "^0.1.1",
34+
"@opentelemetry/exporter-jaeger": "^0.1.1",
35+
"@opentelemetry/exporter-zipkin": "^0.1.1",
36+
"@opentelemetry/node": "^0.1.1",
37+
"@opentelemetry/plugin-grpc": "^0.1.1",
38+
"grpc": "^1.23.3",
39+
"node-pre-gyp": "0.12.0"
40+
},
41+
"homepage": "https://github.com/open-telemetry/opentelemetry-js#readme",
42+
"devDependencies": {
43+
"cross-env": "^6.0.0"
44+
}
45+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
syntax = "proto3";
2+
3+
option java_multiple_files = true;
4+
option java_package = "io.grpc.examples.helloworld";
5+
option java_outer_classname = "HelloWorldProto";
6+
option objc_class_prefix = "HLW";
7+
8+
package rpc;
9+
10+
service Fetch {
11+
// Sends a capitalizes payload
12+
rpc Capitalize(Payload) returns (Payload) {}
13+
}
14+
15+
// The request and response payload containing the id and data.
16+
message Payload {
17+
int32 id = 1;
18+
bytes data = 2;
19+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const opentelemetry = require('@opentelemetry/core');
4+
const { NodeTracer } = require('@opentelemetry/node');
5+
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
6+
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
7+
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
8+
const EXPORTER = process.env.EXPORTER || '';
9+
10+
function setupTracerAndExporters(service) {
11+
const tracer = new NodeTracer({
12+
plugins: {
13+
grpc: {
14+
enabled: true,
15+
// You may use a package name or absolute path to the file.
16+
path: '@opentelemetry/plugin-grpc'
17+
}
18+
}
19+
});
20+
21+
let exporter;
22+
if (EXPORTER.toLowerCase().startsWith('z')) {
23+
exporter = new ZipkinExporter({
24+
serviceName: service,
25+
});
26+
} else {
27+
exporter = new JaegerExporter({
28+
serviceName: service,
29+
// The default flush interval is 5 seconds.
30+
flushInterval: 2000
31+
});
32+
}
33+
34+
// It is recommended to use this `BatchSpanProcessor` for better performance
35+
// and optimization, especially in production.
36+
tracer.addSpanProcessor(new SimpleSpanProcessor(exporter));
37+
38+
// Initialize the OpenTelemetry APIs to use the BasicTracer bindings
39+
opentelemetry.initGlobalTracer(tracer);
40+
}
41+
42+
exports.setupTracerAndExporters = setupTracerAndExporters;

0 commit comments

Comments
 (0)