@prairielearn/opentelemetry 1.0.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
- @prairielearn/opentelemetry:build: cache hit, replaying output 97ac9cdfa1cb193f
1
+ @prairielearn/opentelemetry:build: cache hit, replaying output e17abcb154da23a6
2
2
  @prairielearn/opentelemetry:build: warning package.json: No license field
3
3
  @prairielearn/opentelemetry:build: $ tsc
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @prairielearn/opentelemetry
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 24a37b4b7: Bump OpenTelemetry dependencies to their latest versions
8
+
9
+ ## 1.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 2ef7a73e8: Add `instrumented()` helper to instrument function calls
14
+
15
+ ## 1.1.0
16
+
17
+ ### Minor Changes
18
+
19
+ - ff23653ef: Don't handle `SIGTERM`; expose `shutdown()` function
20
+
3
21
  ## 1.0.1
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -23,13 +23,28 @@ await init({
23
23
 
24
24
  This will automatically instrument a variety of commonly-used Node packages.
25
25
 
26
- To manually instrument code, you can use the `trace` export:
26
+ To easily instrument individual pieces of functionality, you can use the `instrumented()` helper function:
27
27
 
28
28
  ```ts
29
- import { trace } from '@prairielearn/opentelemetry';
29
+ import { instrumented } from '@prairielearn/opentelemetry';
30
+
31
+ async function doThing() {
32
+ return instrumented('span.name', async (span) => {
33
+ span.setAttribute('attribute.name', 'value');
34
+ await doThing();
35
+ });
36
+ }
37
+ ```
38
+
39
+ This will automatically set the span status and record any exceptions that occur.
40
+
41
+ If you have a more complex use case, you can manually instrument code with the `trace` export:
42
+
43
+ ```ts
44
+ import { trace, SpanStatusCode } from '@prairielearn/opentelemetry';
30
45
 
31
- const tracer = trace.getTracer('lib-name');
32
- await tracer.startActiveSpan('span-name', async (span) => {
46
+ const tracer = trace.getTracer('default');
47
+ await tracer.startActiveSpan('span.name', async (span) => {
33
48
  try {
34
49
  await doWork();
35
50
  span.setStatus({ status: SpanStatusCode.OK });
package/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ import { SpanExporter } from '@opentelemetry/sdk-trace-base';
2
+ import { Span } from '@opentelemetry/api';
1
3
  export interface OpenTelemetryConfig {
2
4
  openTelemetryEnabled: boolean;
3
- openTelemetryExporter?: 'console' | 'honeycomb';
4
- openTelemetrySamplerType?: 'always-on' | 'always-off' | 'trace-id-ratio';
5
+ openTelemetryExporter: 'console' | 'honeycomb' | SpanExporter;
6
+ openTelemetrySamplerType: 'always-on' | 'always-off' | 'trace-id-ratio';
5
7
  openTelemetrySampleRate?: number;
8
+ openTelemetrySpanProcessor?: 'batch' | 'simple';
6
9
  honeycombApiKey?: string;
7
10
  honeycombDataset?: string;
8
11
  serviceName?: string;
@@ -13,5 +16,11 @@ export interface OpenTelemetryConfig {
13
16
  * information available until we've loaded our config.
14
17
  */
15
18
  export declare function init(config: OpenTelemetryConfig): Promise<void>;
19
+ /**
20
+ * Gracefully shuts down the OpenTelemetry instrumentation. Should be called
21
+ * when a `SIGTERM` signal is handled.
22
+ */
23
+ export declare function shutdown(): Promise<void>;
24
+ export declare function instrumented<T>(name: string, fn: (span: Span) => Promise<T> | T): Promise<T>;
16
25
  export { trace, context, SpanStatusCode } from '@opentelemetry/api';
17
26
  export { suppressTracing } from '@opentelemetry/core';
package/dist/index.js CHANGED
@@ -1,18 +1,16 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.suppressTracing = exports.SpanStatusCode = exports.context = exports.trace = exports.init = void 0;
7
- const process_1 = __importDefault(require("process"));
3
+ exports.suppressTracing = exports.SpanStatusCode = exports.context = exports.trace = exports.instrumented = exports.shutdown = exports.init = void 0;
8
4
  const grpc_js_1 = require("@grpc/grpc-js");
9
5
  const sdk_node_1 = require("@opentelemetry/sdk-node");
10
6
  const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
7
+ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
11
8
  const resources_1 = require("@opentelemetry/resources");
12
9
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
13
10
  const exporter_otlp_grpc_1 = require("@opentelemetry/exporter-otlp-grpc");
14
11
  const instrumentation_express_1 = require("@opentelemetry/instrumentation-express");
15
- const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
12
+ const sdk_trace_base_2 = require("@opentelemetry/sdk-trace-base");
13
+ const api_1 = require("@opentelemetry/api");
16
14
  const core_1 = require("@opentelemetry/core");
17
15
  // Instrumentations go here.
18
16
  const instrumentation_aws_sdk_1 = require("@opentelemetry/instrumentation-aws-sdk");
@@ -30,7 +28,7 @@ const resources_2 = require("@opentelemetry/resources");
30
28
  * before they're queued up to send. This enhances our samping process so
31
29
  * that we can filter spans _after_ they've been emitted.
32
30
  */
33
- class FilterBatchSpanProcessor extends sdk_trace_base_1.BatchSpanProcessor {
31
+ class FilterBatchSpanProcessor extends sdk_trace_base_2.BatchSpanProcessor {
34
32
  constructor(exporter, filter) {
35
33
  super(exporter);
36
34
  this.filter = filter;
@@ -97,6 +95,7 @@ const instrumentations = [
97
95
  instrumentations.forEach((i) => {
98
96
  i.enable();
99
97
  });
98
+ let tracerProvider;
100
99
  /**
101
100
  * Should be called once we've loaded our config; this will allow us to set up
102
101
  * the correct metadata for the Honeycomb exporter. We don't actually have that
@@ -114,30 +113,35 @@ async function init(config) {
114
113
  return;
115
114
  }
116
115
  let exporter;
117
- switch (config.openTelemetryExporter) {
118
- case 'console': {
119
- // Export spans to the console for testing purposes.
120
- exporter = new sdk_node_1.tracing.ConsoleSpanExporter();
121
- break;
122
- }
123
- case 'honeycomb': {
124
- // Create a Honeycomb exporter with the appropriate metadata from the
125
- // config we've been provided with.
126
- const metadata = new grpc_js_1.Metadata();
127
- metadata.set('x-honeycomb-team', config.honeycombApiKey);
128
- metadata.set('x-honeycomb-dataset', config.honeycombDataset);
129
- exporter = new exporter_otlp_grpc_1.OTLPTraceExporter({
130
- url: 'grpc://api.honeycomb.io:443/',
131
- credentials: grpc_js_1.credentials.createSsl(),
132
- metadata,
133
- });
134
- break;
116
+ if (typeof config.openTelemetryExporter === 'object') {
117
+ exporter = config.openTelemetryExporter;
118
+ }
119
+ else {
120
+ switch (config.openTelemetryExporter) {
121
+ case 'console': {
122
+ // Export spans to the console for testing purposes.
123
+ exporter = new sdk_node_1.tracing.ConsoleSpanExporter();
124
+ break;
125
+ }
126
+ case 'honeycomb': {
127
+ // Create a Honeycomb exporter with the appropriate metadata from the
128
+ // config we've been provided with.
129
+ const metadata = new grpc_js_1.Metadata();
130
+ metadata.set('x-honeycomb-team', config.honeycombApiKey);
131
+ metadata.set('x-honeycomb-dataset', config.honeycombDataset);
132
+ exporter = new exporter_otlp_grpc_1.OTLPTraceExporter({
133
+ url: 'grpc://api.honeycomb.io:443/',
134
+ credentials: grpc_js_1.credentials.createSsl(),
135
+ metadata,
136
+ });
137
+ break;
138
+ }
139
+ default:
140
+ throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
135
141
  }
136
- default:
137
- throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
138
142
  }
139
143
  let sampler;
140
- switch (config.openTelemetrySamplerType) {
144
+ switch (config.openTelemetrySamplerType ?? 'always-on') {
141
145
  case 'always-on': {
142
146
  sampler = new core_1.AlwaysOnSampler();
143
147
  break;
@@ -155,6 +159,20 @@ async function init(config) {
155
159
  default:
156
160
  throw new Error(`Unknown OpenTelemetry sampler type: ${config.openTelemetrySamplerType}`);
157
161
  }
162
+ let spanProcessor;
163
+ switch (config.openTelemetrySpanProcessor ?? 'batch') {
164
+ case 'batch': {
165
+ spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
166
+ break;
167
+ }
168
+ case 'simple': {
169
+ spanProcessor = new sdk_trace_base_1.SimpleSpanProcessor(exporter);
170
+ break;
171
+ }
172
+ default: {
173
+ throw new Error(`Unknown OpenTelemetry span processor: ${config.openTelemetrySpanProcessor}`);
174
+ }
175
+ }
158
176
  // Much of this functionality is copied from `@opentelemetry/sdk-node`, but
159
177
  // we can't use the SDK directly because of the fact that we load our config
160
178
  // asynchronously. We need to initialize our instrumentations first; only
@@ -166,24 +184,53 @@ async function init(config) {
166
184
  if (config.serviceName) {
167
185
  resource = resource.merge(new resources_1.Resource({ [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: config.serviceName }));
168
186
  }
169
- const tracerProvider = new sdk_trace_node_1.NodeTracerProvider({
187
+ tracerProvider = new sdk_trace_node_1.NodeTracerProvider({
170
188
  sampler,
171
189
  resource,
172
190
  });
173
- const spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
174
191
  tracerProvider.addSpanProcessor(spanProcessor);
175
192
  tracerProvider.register();
176
193
  instrumentations.forEach((i) => i.setTracerProvider(tracerProvider));
177
- // When the process starts shutting down, terminate OTEL stuff.
178
- process_1.default.on('SIGTERM', () => {
179
- tracerProvider.shutdown().catch((error) => console.error('Error terminating tracing', error));
180
- });
181
194
  }
182
195
  exports.init = init;
183
- var api_1 = require("@opentelemetry/api");
184
- Object.defineProperty(exports, "trace", { enumerable: true, get: function () { return api_1.trace; } });
185
- Object.defineProperty(exports, "context", { enumerable: true, get: function () { return api_1.context; } });
186
- Object.defineProperty(exports, "SpanStatusCode", { enumerable: true, get: function () { return api_1.SpanStatusCode; } });
196
+ /**
197
+ * Gracefully shuts down the OpenTelemetry instrumentation. Should be called
198
+ * when a `SIGTERM` signal is handled.
199
+ */
200
+ async function shutdown() {
201
+ if (tracerProvider) {
202
+ await tracerProvider.shutdown();
203
+ tracerProvider = null;
204
+ }
205
+ }
206
+ exports.shutdown = shutdown;
207
+ async function instrumented(name, fn) {
208
+ return api_1.trace
209
+ .getTracer('default')
210
+ .startActiveSpan(name, async (span) => {
211
+ try {
212
+ const result = await fn(span);
213
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
214
+ return result;
215
+ }
216
+ catch (e) {
217
+ span.setStatus({
218
+ code: api_1.SpanStatusCode.ERROR,
219
+ message: e.message,
220
+ });
221
+ span.recordException(e);
222
+ throw e;
223
+ }
224
+ finally {
225
+ span.end();
226
+ }
227
+ });
228
+ }
229
+ exports.instrumented = instrumented;
230
+ var api_2 = require("@opentelemetry/api");
231
+ Object.defineProperty(exports, "trace", { enumerable: true, get: function () { return api_2.trace; } });
232
+ Object.defineProperty(exports, "context", { enumerable: true, get: function () { return api_2.context; } });
233
+ Object.defineProperty(exports, "SpanStatusCode", { enumerable: true, get: function () { return api_2.SpanStatusCode; } });
187
234
  var core_2 = require("@opentelemetry/core");
188
235
  Object.defineProperty(exports, "suppressTracing", { enumerable: true, get: function () { return core_2.suppressTracing; } });
189
236
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
4
+ const chai_1 = require("chai");
5
+ const index_1 = require("./index");
6
+ const context_async_hooks_1 = require("@opentelemetry/context-async-hooks");
7
+ describe('instrumented', () => {
8
+ let contextManager;
9
+ let exporter = new sdk_node_1.tracing.InMemorySpanExporter();
10
+ before(async () => {
11
+ await (0, index_1.init)({
12
+ openTelemetryEnabled: true,
13
+ openTelemetryExporter: exporter,
14
+ openTelemetrySamplerType: 'always-on',
15
+ openTelemetrySpanProcessor: 'simple',
16
+ });
17
+ });
18
+ beforeEach(async () => {
19
+ contextManager = new context_async_hooks_1.AsyncHooksContextManager();
20
+ index_1.context.setGlobalContextManager(contextManager.enable());
21
+ });
22
+ afterEach(async () => {
23
+ exporter.reset();
24
+ index_1.context.disable();
25
+ });
26
+ it('returns the value from the function', async () => {
27
+ const res = await (0, index_1.instrumented)('test', () => 'foo');
28
+ chai_1.assert.equal(res, 'foo');
29
+ });
30
+ it('records a span on success', async () => {
31
+ await (0, index_1.instrumented)('test-success', () => 'foo');
32
+ const spans = exporter.getFinishedSpans();
33
+ chai_1.assert.lengthOf(spans, 1);
34
+ chai_1.assert.equal(spans[0].name, 'test-success');
35
+ chai_1.assert.equal(spans[0].status.code, index_1.SpanStatusCode.OK);
36
+ });
37
+ it('records a span on failure', async () => {
38
+ let maybeError = null;
39
+ await (0, index_1.instrumented)('test-failure', () => {
40
+ throw new Error('foo');
41
+ }).catch((err) => {
42
+ maybeError = err;
43
+ });
44
+ // Ensure the error was propagated back to the caller.
45
+ chai_1.assert.isOk(maybeError);
46
+ chai_1.assert.equal(maybeError.message, 'foo');
47
+ // Ensure the correct span was recorded.
48
+ const spans = exporter.getFinishedSpans();
49
+ chai_1.assert.lengthOf(spans, 1);
50
+ chai_1.assert.equal(spans[0].name, 'test-failure');
51
+ chai_1.assert.equal(spans[0].status.code, index_1.SpanStatusCode.ERROR);
52
+ chai_1.assert.equal(spans[0].status.message, 'foo');
53
+ chai_1.assert.equal(spans[0].events[0].name, 'exception');
54
+ });
55
+ it('sets up context correctly', async () => {
56
+ const tracer = index_1.trace.getTracer('default');
57
+ const parentSpan = tracer.startSpan('parentSpan');
58
+ const parentContext = index_1.trace.setSpan(index_1.context.active(), parentSpan);
59
+ await (0, index_1.instrumented)('test', async () => {
60
+ const childContext = index_1.context.active();
61
+ chai_1.assert.notStrictEqual(childContext, parentContext);
62
+ });
63
+ });
64
+ });
65
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,31 +1,35 @@
1
1
  {
2
2
  "name": "@prairielearn/opentelemetry",
3
- "version": "1.0.1",
3
+ "version": "1.3.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsc",
7
- "dev": "tsc --watch --preserveWatchOutput"
7
+ "dev": "tsc --watch --preserveWatchOutput",
8
+ "test": "mocha --no-config --require ts-node/register src/index.test.ts"
8
9
  },
9
10
  "dependencies": {
10
- "@grpc/grpc-js": "^1.5.5",
11
- "@opentelemetry/api": "^1.0.4",
12
- "@opentelemetry/core": "^1.0.1",
11
+ "@grpc/grpc-js": "^1.6.7",
12
+ "@opentelemetry/api": "^1.1.0",
13
+ "@opentelemetry/core": "^1.3.1",
13
14
  "@opentelemetry/exporter-otlp-grpc": "^0.26.0",
14
- "@opentelemetry/instrumentation-aws-sdk": "^0.5.0",
15
- "@opentelemetry/instrumentation-connect": "^0.27.1",
16
- "@opentelemetry/instrumentation-dns": "^0.27.1",
17
- "@opentelemetry/instrumentation-express": "^0.28.0",
18
- "@opentelemetry/instrumentation-http": "^0.27.0",
19
- "@opentelemetry/instrumentation-pg": "^0.28.0",
20
- "@opentelemetry/instrumentation-redis": "^0.28.0",
21
- "@opentelemetry/resource-detector-aws": "^1.0.3",
22
- "@opentelemetry/resources": "^1.0.1",
23
- "@opentelemetry/sdk-node": "^0.27.0",
24
- "@opentelemetry/sdk-trace-base": "^1.0.1",
25
- "@opentelemetry/sdk-trace-node": "^1.0.1",
26
- "@opentelemetry/semantic-conventions": "^1.0.1"
15
+ "@opentelemetry/instrumentation-aws-sdk": "^0.7.0",
16
+ "@opentelemetry/instrumentation-connect": "^0.28.0",
17
+ "@opentelemetry/instrumentation-dns": "^0.28.0",
18
+ "@opentelemetry/instrumentation-express": "^0.29.0",
19
+ "@opentelemetry/instrumentation-http": "^0.29.2",
20
+ "@opentelemetry/instrumentation-pg": "^0.29.0",
21
+ "@opentelemetry/instrumentation-redis": "^0.31.0",
22
+ "@opentelemetry/resource-detector-aws": "^1.1.0",
23
+ "@opentelemetry/resources": "^1.3.1",
24
+ "@opentelemetry/sdk-node": "^0.29.2",
25
+ "@opentelemetry/sdk-trace-base": "^1.3.1",
26
+ "@opentelemetry/sdk-trace-node": "^1.3.1",
27
+ "@opentelemetry/semantic-conventions": "^1.3.1"
27
28
  },
28
29
  "devDependencies": {
29
- "typescript": "^4.5.5"
30
+ "@prairielearn/tsconfig": "*",
31
+ "mocha": "^10.0.0",
32
+ "ts-node": "^10.8.0",
33
+ "typescript": "^4.7.2"
30
34
  }
31
35
  }
@@ -0,0 +1,75 @@
1
+ import { tracing } from '@opentelemetry/sdk-node';
2
+ import { assert } from 'chai';
3
+
4
+ import { context, init, instrumented, shutdown, trace, SpanStatusCode } from './index';
5
+ import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
6
+
7
+ describe('instrumented', () => {
8
+ let contextManager: AsyncHooksContextManager;
9
+ let exporter = new tracing.InMemorySpanExporter();
10
+
11
+ before(async () => {
12
+ await init({
13
+ openTelemetryEnabled: true,
14
+ openTelemetryExporter: exporter,
15
+ openTelemetrySamplerType: 'always-on',
16
+ openTelemetrySpanProcessor: 'simple',
17
+ });
18
+ });
19
+
20
+ beforeEach(async () => {
21
+ contextManager = new AsyncHooksContextManager();
22
+ context.setGlobalContextManager(contextManager.enable());
23
+ });
24
+
25
+ afterEach(async () => {
26
+ exporter.reset();
27
+ context.disable();
28
+ });
29
+
30
+ it('returns the value from the function', async () => {
31
+ const res = await instrumented('test', () => 'foo');
32
+ assert.equal(res, 'foo');
33
+ });
34
+
35
+ it('records a span on success', async () => {
36
+ await instrumented('test-success', () => 'foo');
37
+
38
+ const spans = exporter.getFinishedSpans();
39
+ assert.lengthOf(spans, 1);
40
+ assert.equal(spans[0].name, 'test-success');
41
+ assert.equal(spans[0].status.code, SpanStatusCode.OK);
42
+ });
43
+
44
+ it('records a span on failure', async () => {
45
+ let maybeError = null;
46
+ await instrumented('test-failure', () => {
47
+ throw new Error('foo');
48
+ }).catch((err) => {
49
+ maybeError = err;
50
+ });
51
+
52
+ // Ensure the error was propagated back to the caller.
53
+ assert.isOk(maybeError);
54
+ assert.equal(maybeError.message, 'foo');
55
+
56
+ // Ensure the correct span was recorded.
57
+ const spans = exporter.getFinishedSpans();
58
+ assert.lengthOf(spans, 1);
59
+ assert.equal(spans[0].name, 'test-failure');
60
+ assert.equal(spans[0].status.code, SpanStatusCode.ERROR);
61
+ assert.equal(spans[0].status.message, 'foo');
62
+ assert.equal(spans[0].events[0].name, 'exception');
63
+ });
64
+
65
+ it('sets up context correctly', async () => {
66
+ const tracer = trace.getTracer('default');
67
+ const parentSpan = tracer.startSpan('parentSpan');
68
+ const parentContext = trace.setSpan(context.active(), parentSpan);
69
+
70
+ await instrumented('test', async () => {
71
+ const childContext = context.active();
72
+ assert.notStrictEqual(childContext, parentContext);
73
+ });
74
+ });
75
+ });
package/src/index.ts CHANGED
@@ -1,15 +1,19 @@
1
- import process from 'process';
2
1
  import { Metadata, credentials } from '@grpc/grpc-js';
3
2
 
4
3
  import { tracing } from '@opentelemetry/sdk-node';
5
4
  import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
- import type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
5
+ import {
6
+ SpanExporter,
7
+ ReadableSpan,
8
+ SpanProcessor,
9
+ SimpleSpanProcessor,
10
+ } from '@opentelemetry/sdk-trace-base';
7
11
  import { detectResources, Resource } from '@opentelemetry/resources';
8
12
  import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
9
13
  import { OTLPTraceExporter } from '@opentelemetry/exporter-otlp-grpc';
10
14
  import { ExpressLayerType } from '@opentelemetry/instrumentation-express';
11
15
  import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
12
- import { Sampler } from '@opentelemetry/api';
16
+ import { Sampler, Span, SpanStatusCode, context, trace } from '@opentelemetry/api';
13
17
  import {
14
18
  ParentBasedSampler,
15
19
  TraceIdRatioBasedSampler,
@@ -30,6 +34,7 @@ import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis';
30
34
  // Resource detectors go here.
31
35
  import { awsEc2Detector } from '@opentelemetry/resource-detector-aws';
32
36
  import { processDetector, envDetector } from '@opentelemetry/resources';
37
+ import { createBaggage } from '@opentelemetry/api/build/src/baggage/utils';
33
38
 
34
39
  /**
35
40
  * Extends `BatchSpanProcessor` to give it the ability to filter out spans
@@ -111,11 +116,14 @@ instrumentations.forEach((i) => {
111
116
  i.enable();
112
117
  });
113
118
 
119
+ let tracerProvider: NodeTracerProvider;
120
+
114
121
  export interface OpenTelemetryConfig {
115
122
  openTelemetryEnabled: boolean;
116
- openTelemetryExporter?: 'console' | 'honeycomb';
117
- openTelemetrySamplerType?: 'always-on' | 'always-off' | 'trace-id-ratio';
123
+ openTelemetryExporter: 'console' | 'honeycomb' | SpanExporter;
124
+ openTelemetrySamplerType: 'always-on' | 'always-off' | 'trace-id-ratio';
118
125
  openTelemetrySampleRate?: number;
126
+ openTelemetrySpanProcessor?: 'batch' | 'simple';
119
127
  honeycombApiKey?: string;
120
128
  honeycombDataset?: string;
121
129
  serviceName?: string;
@@ -139,33 +147,37 @@ export async function init(config: OpenTelemetryConfig) {
139
147
  }
140
148
 
141
149
  let exporter: SpanExporter;
142
- switch (config.openTelemetryExporter) {
143
- case 'console': {
144
- // Export spans to the console for testing purposes.
145
- exporter = new tracing.ConsoleSpanExporter();
146
- break;
147
- }
148
- case 'honeycomb': {
149
- // Create a Honeycomb exporter with the appropriate metadata from the
150
- // config we've been provided with.
151
- const metadata = new Metadata();
152
-
153
- metadata.set('x-honeycomb-team', config.honeycombApiKey);
154
- metadata.set('x-honeycomb-dataset', config.honeycombDataset);
155
-
156
- exporter = new OTLPTraceExporter({
157
- url: 'grpc://api.honeycomb.io:443/',
158
- credentials: credentials.createSsl(),
159
- metadata,
160
- });
161
- break;
150
+ if (typeof config.openTelemetryExporter === 'object') {
151
+ exporter = config.openTelemetryExporter;
152
+ } else {
153
+ switch (config.openTelemetryExporter) {
154
+ case 'console': {
155
+ // Export spans to the console for testing purposes.
156
+ exporter = new tracing.ConsoleSpanExporter();
157
+ break;
158
+ }
159
+ case 'honeycomb': {
160
+ // Create a Honeycomb exporter with the appropriate metadata from the
161
+ // config we've been provided with.
162
+ const metadata = new Metadata();
163
+
164
+ metadata.set('x-honeycomb-team', config.honeycombApiKey);
165
+ metadata.set('x-honeycomb-dataset', config.honeycombDataset);
166
+
167
+ exporter = new OTLPTraceExporter({
168
+ url: 'grpc://api.honeycomb.io:443/',
169
+ credentials: credentials.createSsl(),
170
+ metadata,
171
+ });
172
+ break;
173
+ }
174
+ default:
175
+ throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
162
176
  }
163
- default:
164
- throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
165
177
  }
166
178
 
167
179
  let sampler: Sampler;
168
- switch (config.openTelemetrySamplerType) {
180
+ switch (config.openTelemetrySamplerType ?? 'always-on') {
169
181
  case 'always-on': {
170
182
  sampler = new AlwaysOnSampler();
171
183
  break;
@@ -184,6 +196,21 @@ export async function init(config: OpenTelemetryConfig) {
184
196
  throw new Error(`Unknown OpenTelemetry sampler type: ${config.openTelemetrySamplerType}`);
185
197
  }
186
198
 
199
+ let spanProcessor: SpanProcessor;
200
+ switch (config.openTelemetrySpanProcessor ?? 'batch') {
201
+ case 'batch': {
202
+ spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
203
+ break;
204
+ }
205
+ case 'simple': {
206
+ spanProcessor = new SimpleSpanProcessor(exporter);
207
+ break;
208
+ }
209
+ default: {
210
+ throw new Error(`Unknown OpenTelemetry span processor: ${config.openTelemetrySpanProcessor}`);
211
+ }
212
+ }
213
+
187
214
  // Much of this functionality is copied from `@opentelemetry/sdk-node`, but
188
215
  // we can't use the SDK directly because of the fact that we load our config
189
216
  // asynchronously. We need to initialize our instrumentations first; only
@@ -200,20 +227,49 @@ export async function init(config: OpenTelemetryConfig) {
200
227
  );
201
228
  }
202
229
 
203
- const tracerProvider = new NodeTracerProvider({
230
+ tracerProvider = new NodeTracerProvider({
204
231
  sampler,
205
232
  resource,
206
233
  });
207
- const spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
208
234
  tracerProvider.addSpanProcessor(spanProcessor);
209
235
  tracerProvider.register();
210
236
 
211
237
  instrumentations.forEach((i) => i.setTracerProvider(tracerProvider));
238
+ }
212
239
 
213
- // When the process starts shutting down, terminate OTEL stuff.
214
- process.on('SIGTERM', () => {
215
- tracerProvider.shutdown().catch((error) => console.error('Error terminating tracing', error));
216
- });
240
+ /**
241
+ * Gracefully shuts down the OpenTelemetry instrumentation. Should be called
242
+ * when a `SIGTERM` signal is handled.
243
+ */
244
+ export async function shutdown(): Promise<void> {
245
+ if (tracerProvider) {
246
+ await tracerProvider.shutdown();
247
+ tracerProvider = null;
248
+ }
249
+ }
250
+
251
+ export async function instrumented<T>(
252
+ name: string,
253
+ fn: (span: Span) => Promise<T> | T
254
+ ): Promise<T> {
255
+ return trace
256
+ .getTracer('default')
257
+ .startActiveSpan<(span: Span) => Promise<T>>(name, async (span) => {
258
+ try {
259
+ const result = await fn(span);
260
+ span.setStatus({ code: SpanStatusCode.OK });
261
+ return result;
262
+ } catch (e) {
263
+ span.setStatus({
264
+ code: SpanStatusCode.ERROR,
265
+ message: e.message,
266
+ });
267
+ span.recordException(e);
268
+ throw e;
269
+ } finally {
270
+ span.end();
271
+ }
272
+ });
217
273
  }
218
274
 
219
275
  export { trace, context, SpanStatusCode } from '@opentelemetry/api';
package/tsconfig.json CHANGED
@@ -1,16 +1,7 @@
1
1
  {
2
+ "extends": "@prairielearn/tsconfig",
2
3
  "compilerOptions": {
3
- "allowJs": true,
4
- "declaration": true,
5
- "esModuleInterop": true,
6
4
  "outDir": "./dist",
7
- "rootDir": "src/",
8
- "sourceMap": true,
9
- // This package will only be used server-side on Node 14+, so target the
10
- // newest version of the ES spec that Node 14 supports.
11
- "target": "ES2020",
12
- // However, we don't yet make extensive use of ES Modules, so specifically
13
- // compile `import`/`export` down to CommonJS.
14
- "module": "CommonJS",
5
+ "rootDir": "./src",
15
6
  }
16
7
  }