Skip to content
Merged
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
26 changes: 26 additions & 0 deletions examples/express/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const baseConfig = require('../../eslint.config');

module.exports = {
...baseConfig,
env: {
node: true,
},
};
2 changes: 2 additions & 0 deletions examples/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"version": "0.1.0",
"description": "Example of Express integration with OpenTelemetry",
"scripts": {
"lint": "eslint . --ext=ts,js,mjs",
"lint:fix": "eslint . --ext=ts,js,mjs --fix",
"server": "ts-node src/server.ts",
"client": "ts-node src/client.ts",
"compile": "tsc -p ."
Expand Down
65 changes: 45 additions & 20 deletions examples/express/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
'use strict';
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// eslint-disable-next-line import/order
import { setupTracing } from "./tracer";
const tracer = setupTracing('example-express-client');
// eslint-disable-next-line import/order, import/extensions
import { setupTracing } from './tracer';

import * as api from '@opentelemetry/api';
import { default as axios } from 'axios';
import * as axios from 'axios';

const tracer = setupTracing('example-express-client');

function makeRequest() {
async function makeRequest() {
const span = tracer.startSpan('client.makeRequest()', {
kind: api.SpanKind.CLIENT,
});

api.context.with(api.trace.setSpan(api.ROOT_CONTEXT, span), async () => {
try {
const res = await axios.get('http://localhost:8080/run_test');
console.log('status:', res.statusText);
span.setStatus({ code: api.SpanStatusCode.OK });
} catch (e) {
if (e instanceof Error) {
console.log('failed:', e.message);
span.setStatus({ code: api.SpanStatusCode.ERROR, message: e.message });
await api.context.with(
api.trace.setSpan(api.ROOT_CONTEXT, span),
async () => {
try {
const res = await axios.get('http://localhost:8080/run_test');
console.log('status:', res.statusText);
span.setStatus({ code: api.SpanStatusCode.OK });
} catch (e) {
if (e instanceof Error) {
console.log('failed:', e.message);
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: e.message,
});
}
}
span.end();
console.log(
'Sleeping 5 seconds before shutdown to ensure all records are flushed.'
);
setTimeout(() => {
console.log('Completed.');
}, 5000);
}
span.end();
console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.');
setTimeout(() => { console.log('Completed.'); }, 5000);
});
);
}

makeRequest();
makeRequest().catch(err => console.log(err));
47 changes: 33 additions & 14 deletions examples/express/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import { setupTracing } from './tracer'
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

setupTracing('example-express-server');
// eslint-disable-next-line import/order, import/extensions
import { setupTracing } from './tracer';

// Require in rest of modules
import * as express from 'express';
import { default as axios } from 'axios';
import { RequestHandler } from "express";
import * as axios from 'axios';
import { RequestHandler } from 'express';

setupTracing('example-express-server');

// Setup express
const app = express();
Expand All @@ -32,19 +49,21 @@ const authMiddleware: RequestHandler = (req, res, next) => {
};

app.use(express.json());
app.get('/health', (req, res) => res.status(200).send("HEALTHY")); // endpoint that is called by framework/cluster
app.get('/health', (req, res) => res.status(200).send('HEALTHY')); // endpoint that is called by framework/cluster
app.get('/run_test', async (req, res) => {
// Calls another endpoint of the same API, somewhat mimicking an external API call
const createdCat = await axios.post(`http://localhost:${PORT}/cats`, {
name: 'Tom',
friends: [
'Jerry',
],
}, {
headers: {
Authorization: 'secret_token',
const createdCat = await axios.post(
`http://localhost:${PORT}/cats`,
{
name: 'Tom',
friends: ['Jerry'],
},
});
{
headers: {
Authorization: 'secret_token',
},
}
);

return res.status(201).send(createdCat.data);
});
Expand Down
59 changes: 47 additions & 12 deletions examples/express/src/tracer.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
'use strict';
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { trace, SamplingDecision, SpanKind, Attributes } from '@opentelemetry/api';
import {
trace,
SamplingDecision,
SpanKind,
Attributes,
} from '@opentelemetry/api';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { Sampler, AlwaysOnSampler, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import {
Sampler,
AlwaysOnSampler,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
import {
ATTR_SERVICE_NAME,
ATTR_HTTP_ROUTE,
} from '@opentelemetry/semantic-conventions';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';

// eslint-disable-next-line import/prefer-default-export
export const setupTracing = (serviceName: string) => {
const exporter = new OTLPTraceExporter({});
const provider = new NodeTracerProvider({
resource: new Resource({
[ATTR_SERVICE_NAME]: serviceName,
}),
spanProcessors: [
new SimpleSpanProcessor(exporter),
],
spanProcessors: [new SimpleSpanProcessor(exporter)],
sampler: filterSampler(ignoreHealthCheck, new AlwaysOnSampler()),
});
registerInstrumentations({
Expand All @@ -36,7 +61,11 @@ export const setupTracing = (serviceName: string) => {
return trace.getTracer(serviceName);
};

type FilterFunction = (spanName: string, spanKind: SpanKind, attributes: Attributes) => boolean;
type FilterFunction = (
spanName: string,
spanKind: SpanKind,
attributes: Attributes
) => boolean;

function filterSampler(filterFn: FilterFunction, parent: Sampler): Sampler {
return {
Expand All @@ -48,10 +77,16 @@ function filterSampler(filterFn: FilterFunction, parent: Sampler): Sampler {
},
toString() {
return `FilterSampler(${parent.toString()})`;
}
}
},
};
}

function ignoreHealthCheck(spanName: string, spanKind: SpanKind, attributes: Attributes) {
return spanKind !== SpanKind.SERVER || attributes[ATTR_HTTP_ROUTE] !== "/health";
function ignoreHealthCheck(
spanName: string,
spanKind: SpanKind,
attributes: Attributes
) {
return (
spanKind !== SpanKind.SERVER || attributes[ATTR_HTTP_ROUTE] !== '/health'
);
}