diff --git a/.eslintignore b/.eslintignore index ae01ad91..a97a0513 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ lib src/profiling/proto +examples/typescript diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d40827c1..d8152d2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,8 @@ jobs: name: 'Log injection' - target: '-f profiling.override.yml' name: 'Profiling' + - target: '-f typescript.override.yml' + name: 'TypeScript' name: e2e local ${{ matrix.name }} steps: - name: Checkout diff --git a/.github/workflows/e2e-test-released-package.yml b/.github/workflows/e2e-test-released-package.yml index 0b0c035a..1ed964cb 100644 --- a/.github/workflows/e2e-test-released-package.yml +++ b/.github/workflows/e2e-test-released-package.yml @@ -23,6 +23,8 @@ jobs: name: 'Log injection' - target: '-f profiling.override.yml' name: 'Profiling' + - target: '-f typescript.override.yml' + name: 'TypeScript' name: e2e published ${{ matrix.name }} steps: - name: Checkout diff --git a/examples/typescript/.dockerignore b/examples/typescript/.dockerignore new file mode 100644 index 00000000..d5f19d89 --- /dev/null +++ b/examples/typescript/.dockerignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/examples/typescript/index.ts b/examples/typescript/index.ts new file mode 100644 index 00000000..897b2c2a --- /dev/null +++ b/examples/typescript/index.ts @@ -0,0 +1,34 @@ +import { start } from '@splunk/otel'; +import * as otel from '@opentelemetry/api'; + +start({ + serviceName: 'random-number-generator', + // Set up the OpenTelemetry metrics pipeline and start collecting runtime metrics. + metrics: { + runtimeMetricsEnabled: true, + }, +}); + +// Load libraries after calling start() +import * as express from 'express'; + +const tracer = otel.trace.getTracer('rng-app'); +const meter = otel.metrics.getMeter('rng-app'); +const requestCounter = meter.createCounter('requests'); + +const app = express(); + +app.get('/', (req, res) => { + requestCounter.add(1); + const randomValue = tracer.startActiveSpan('calculate-random', (span) => { + const result = (Math.random() * 42) | 0; + span.setAttribute('random-result', result); + span.setAttribute('random-method', 'diceroll'); + span.end(); + return result; + }); + + res.json({ randomValue }); +}); + +app.listen(process.env.PORT || 8080); diff --git a/examples/typescript/package.json b/examples/typescript/package.json new file mode 100644 index 00000000..af69980a --- /dev/null +++ b/examples/typescript/package.json @@ -0,0 +1,16 @@ +{ + "name": "splunk-otel-example-typescript", + "main": "index.js", + "scripts": { + "start": "npm run compile && node index.js", + "compile": "tsc" + }, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "@splunk/otel": "2.4.1", + "express": "^4.18.2" + }, + "devDependencies": { + "typescript": "5.2.2" + } +} diff --git a/examples/typescript/tsconfig.json b/examples/typescript/tsconfig.json new file mode 100644 index 00000000..68733d52 --- /dev/null +++ b/examples/typescript/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "strict": true + } +} diff --git a/test/examples/collector.config.yml b/test/examples/collector.config.yml index aee6c640..58951c57 100644 --- a/test/examples/collector.config.yml +++ b/test/examples/collector.config.yml @@ -21,6 +21,9 @@ service: traces: receivers: [otlp] exporters: [httpsink, logging/debug] + metrics: + receivers: [otlp] + exporters: [logging/debug] logs/profiling: receivers: [otlp] exporters: [logging/debug] diff --git a/test/examples/typescript.override.yml b/test/examples/typescript.override.yml new file mode 100644 index 00000000..80371de5 --- /dev/null +++ b/test/examples/typescript.override.yml @@ -0,0 +1,15 @@ +services: + app: + working_dir: /home/node/app/examples/typescript + env_file: ./typescript/app.env + command: npm run start + depends_on: + - collector + test: + command: node ./typescript + environment: + REQ_URL: http://app/ + COLLECTOR_URL: http://collector:8378 + depends_on: + - app + - collector diff --git a/test/examples/typescript/app.env b/test/examples/typescript/app.env new file mode 100644 index 00000000..bc6229a0 --- /dev/null +++ b/test/examples/typescript/app.env @@ -0,0 +1,4 @@ +PORT=80 +OTEL_RESOURCE_ATTRIBUTES='deployment.environment=dev' +OTEL_LOG_LEVEL='DEBUG' +OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317 diff --git a/test/examples/typescript/index.js b/test/examples/typescript/index.js new file mode 100644 index 00000000..42cfed1d --- /dev/null +++ b/test/examples/typescript/index.js @@ -0,0 +1,16 @@ +const { + assertSpans, + logSpanTable, + request, + waitSpans, +} = require('../utils.js'); +const snapshot = require('./snapshot.js'); + +waitSpans(snapshot.length).then((data) => { + logSpanTable(data); + return assertSpans(data, snapshot); +}).then((validatedSpans) => { + console.log(`${validatedSpans} spans validated.`); +}); + +request(process.env.REQ_URL ?? 'http://localhost:8080'); diff --git a/test/examples/typescript/snapshot.js b/test/examples/typescript/snapshot.js new file mode 100644 index 00000000..7d86d6ff --- /dev/null +++ b/test/examples/typescript/snapshot.js @@ -0,0 +1,108 @@ +module.exports = [ + { + traceId: 'neZpe/CP+vAd5/rhg0Tw3A==', + id: 'fe+RxrZ76pE=', + startTime: '2023-09-18T20:16:38.412999936Z', + hrStartTime: 1695068198412999936n, + name: 'middleware - query', + kind: 'internal', + parentSpanId: 'NVeO5hUT2bw=', + parent: { id: 'NVeO5hUT2bw=', traceId: 'neZpe/CP+vAd5/rhg0Tw3A==' }, + status: { code: undefined }, + attributes: { + 'otel.library.name': '@opentelemetry/instrumentation-express', + 'otel.library.version': '0.33.0', + 'http.route': '/', + 'express.name': 'query', + 'express.type': 'middleware', + 'span.kind': 'internal' + } + }, + { + traceId: 'neZpe/CP+vAd5/rhg0Tw3A==', + id: 'CnuvgyiHryk=', + startTime: '2023-09-18T20:16:38.416Z', + hrStartTime: 1695068198416000000n, + name: 'middleware - expressInit', + kind: 'internal', + parentSpanId: 'NVeO5hUT2bw=', + parent: { id: 'NVeO5hUT2bw=', traceId: 'neZpe/CP+vAd5/rhg0Tw3A==' }, + status: { code: undefined }, + attributes: { + 'otel.library.name': '@opentelemetry/instrumentation-express', + 'otel.library.version': '0.33.0', + 'http.route': '/', + 'express.name': 'expressInit', + 'express.type': 'middleware', + 'span.kind': 'internal' + } + }, + { + traceId: 'neZpe/CP+vAd5/rhg0Tw3A==', + id: 'ZXuWjxuKTqw=', + startTime: '2023-09-18T20:16:38.416999936Z', + hrStartTime: 1695068198416999936n, + name: 'request handler - /', + kind: 'internal', + parentSpanId: 'NVeO5hUT2bw=', + parent: { id: 'NVeO5hUT2bw=', traceId: 'neZpe/CP+vAd5/rhg0Tw3A==' }, + status: { code: undefined }, + attributes: { + 'otel.library.name': '@opentelemetry/instrumentation-express', + 'otel.library.version': '0.33.0', + 'http.route': '/', + 'express.name': '/', + 'express.type': 'request_handler', + 'span.kind': 'internal' + } + }, + { + traceId: 'neZpe/CP+vAd5/rhg0Tw3A==', + id: '2rGvSI5L/p4=', + startTime: '2023-09-18T20:16:38.417999872Z', + hrStartTime: 1695068198417999872n, + name: 'calculate-random', + kind: 'internal', + parentSpanId: 'NVeO5hUT2bw=', + parent: { id: 'NVeO5hUT2bw=', traceId: 'neZpe/CP+vAd5/rhg0Tw3A==' }, + status: { code: undefined }, + attributes: { + 'otel.library.name': 'rng-app', + 'random-result': undefined, + 'random-method': 'diceroll', + 'span.kind': 'internal' + } + }, + { + traceId: 'neZpe/CP+vAd5/rhg0Tw3A==', + id: 'NVeO5hUT2bw=', + startTime: '2023-09-18T20:16:38.400999936Z', + hrStartTime: 1695068198400999936n, + name: 'GET /', + kind: 'server', + parentSpanId: undefined, + parent: undefined, + status: { code: undefined }, + attributes: { + 'otel.library.name': '@opentelemetry/instrumentation-http', + 'otel.library.version': '0.41.2', + 'http.url': 'http://app/', + 'http.host': 'app', + 'net.host.name': 'app', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': '/', + 'http.user_agent': 'undici', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': '::ffff:172.31.0.3', + 'net.host.port': undefined, + 'net.peer.ip': '::ffff:172.31.0.4', + 'net.peer.port': undefined, + 'http.status_code': undefined, + 'http.status_text': 'OK', + 'http.route': '/', + 'span.kind': 'server' + } + } +];