@prairielearn/opentelemetry 1.0.0 → 1.2.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 7e08efa65485b8c4
1
+ @prairielearn/opentelemetry:build: cache hit, replaying output a5f2c3f86efdd20e
2
2
  @prairielearn/opentelemetry:build: warning package.json: No license field
3
3
  @prairielearn/opentelemetry:build: $ tsc
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @prairielearn/opentelemetry
2
+
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2ef7a73e8: Add `instrumented()` helper to instrument function calls
8
+
9
+ ## 1.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ff23653ef: Don't handle `SIGTERM`; expose `shutdown()` function
14
+
15
+ ## 1.0.1
16
+
17
+ ### Patch Changes
18
+
19
+ - 5346eedc1: Don't disable instrumentation if OpenTelemetry isn't enabled
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
@@ -104,35 +103,45 @@ instrumentations.forEach((i) => {
104
103
  */
105
104
  async function init(config) {
106
105
  if (!config.openTelemetryEnabled) {
107
- // Just disable all of the OTEL instrumentations to avoid any unnecessary overhead.
108
- instrumentations.forEach((i) => i.disable());
106
+ // If not enabled, do nothing. We used to disable the instrumentations, but
107
+ // per maintainers, that can actually be problematic. See the comments on
108
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/970
109
+ // The Express instrumentation also logs a benign error, which can be
110
+ // confusing to users. There's a fix in progress if we want to switch back
111
+ // to disabling instrumentations in the future:
112
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/pull/972
109
113
  return;
110
114
  }
111
115
  let exporter;
112
- switch (config.openTelemetryExporter) {
113
- case 'console': {
114
- // Export spans to the console for testing purposes.
115
- exporter = new sdk_node_1.tracing.ConsoleSpanExporter();
116
- break;
117
- }
118
- case 'honeycomb': {
119
- // Create a Honeycomb exporter with the appropriate metadata from the
120
- // config we've been provided with.
121
- const metadata = new grpc_js_1.Metadata();
122
- metadata.set('x-honeycomb-team', config.honeycombApiKey);
123
- metadata.set('x-honeycomb-dataset', config.honeycombDataset);
124
- exporter = new exporter_otlp_grpc_1.OTLPTraceExporter({
125
- url: 'grpc://api.honeycomb.io:443/',
126
- credentials: grpc_js_1.credentials.createSsl(),
127
- metadata,
128
- });
129
- 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}`);
130
141
  }
131
- default:
132
- throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
133
142
  }
134
143
  let sampler;
135
- switch (config.openTelemetrySamplerType) {
144
+ switch (config.openTelemetrySamplerType ?? 'always-on') {
136
145
  case 'always-on': {
137
146
  sampler = new core_1.AlwaysOnSampler();
138
147
  break;
@@ -150,6 +159,20 @@ async function init(config) {
150
159
  default:
151
160
  throw new Error(`Unknown OpenTelemetry sampler type: ${config.openTelemetrySamplerType}`);
152
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
+ }
153
176
  // Much of this functionality is copied from `@opentelemetry/sdk-node`, but
154
177
  // we can't use the SDK directly because of the fact that we load our config
155
178
  // asynchronously. We need to initialize our instrumentations first; only
@@ -161,24 +184,53 @@ async function init(config) {
161
184
  if (config.serviceName) {
162
185
  resource = resource.merge(new resources_1.Resource({ [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: config.serviceName }));
163
186
  }
164
- const tracerProvider = new sdk_trace_node_1.NodeTracerProvider({
187
+ tracerProvider = new sdk_trace_node_1.NodeTracerProvider({
165
188
  sampler,
166
189
  resource,
167
190
  });
168
- const spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
169
191
  tracerProvider.addSpanProcessor(spanProcessor);
170
192
  tracerProvider.register();
171
193
  instrumentations.forEach((i) => i.setTracerProvider(tracerProvider));
172
- // When the process starts shutting down, terminate OTEL stuff.
173
- process_1.default.on('SIGTERM', () => {
174
- tracerProvider.shutdown().catch((error) => console.error('Error terminating tracing', error));
175
- });
176
194
  }
177
195
  exports.init = init;
178
- var api_1 = require("@opentelemetry/api");
179
- Object.defineProperty(exports, "trace", { enumerable: true, get: function () { return api_1.trace; } });
180
- Object.defineProperty(exports, "context", { enumerable: true, get: function () { return api_1.context; } });
181
- 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; } });
182
234
  var core_2 = require("@opentelemetry/core");
183
235
  Object.defineProperty(exports, "suppressTracing", { enumerable: true, get: function () { return core_2.suppressTracing; } });
184
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,14 +1,15 @@
1
1
  {
2
2
  "name": "@prairielearn/opentelemetry",
3
- "version": "1.0.0",
3
+ "version": "1.2.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",
11
+ "@grpc/grpc-js": "^1.6.7",
12
+ "@opentelemetry/api": "^1.1.0",
12
13
  "@opentelemetry/core": "^1.0.1",
13
14
  "@opentelemetry/exporter-otlp-grpc": "^0.26.0",
14
15
  "@opentelemetry/instrumentation-aws-sdk": "^0.5.0",
@@ -16,16 +17,19 @@
16
17
  "@opentelemetry/instrumentation-dns": "^0.27.1",
17
18
  "@opentelemetry/instrumentation-express": "^0.28.0",
18
19
  "@opentelemetry/instrumentation-http": "^0.27.0",
19
- "@opentelemetry/instrumentation-pg": "^0.28.0",
20
+ "@opentelemetry/instrumentation-pg": "^0.29.0",
20
21
  "@opentelemetry/instrumentation-redis": "^0.28.0",
21
- "@opentelemetry/resource-detector-aws": "^1.0.3",
22
- "@opentelemetry/resources": "^1.0.1",
22
+ "@opentelemetry/resource-detector-aws": "^1.1.0",
23
+ "@opentelemetry/resources": "^1.2.0",
23
24
  "@opentelemetry/sdk-node": "^0.27.0",
24
25
  "@opentelemetry/sdk-trace-base": "^1.0.1",
25
- "@opentelemetry/sdk-trace-node": "^1.0.1",
26
- "@opentelemetry/semantic-conventions": "^1.0.1"
26
+ "@opentelemetry/sdk-trace-node": "^1.3.0",
27
+ "@opentelemetry/semantic-conventions": "^1.3.0"
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;
@@ -128,39 +136,48 @@ export interface OpenTelemetryConfig {
128
136
  */
129
137
  export async function init(config: OpenTelemetryConfig) {
130
138
  if (!config.openTelemetryEnabled) {
131
- // Just disable all of the OTEL instrumentations to avoid any unnecessary overhead.
132
- instrumentations.forEach((i) => i.disable());
139
+ // If not enabled, do nothing. We used to disable the instrumentations, but
140
+ // per maintainers, that can actually be problematic. See the comments on
141
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/970
142
+ // The Express instrumentation also logs a benign error, which can be
143
+ // confusing to users. There's a fix in progress if we want to switch back
144
+ // to disabling instrumentations in the future:
145
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/pull/972
133
146
  return;
134
147
  }
135
148
 
136
149
  let exporter: SpanExporter;
137
- switch (config.openTelemetryExporter) {
138
- case 'console': {
139
- // Export spans to the console for testing purposes.
140
- exporter = new tracing.ConsoleSpanExporter();
141
- break;
142
- }
143
- case 'honeycomb': {
144
- // Create a Honeycomb exporter with the appropriate metadata from the
145
- // config we've been provided with.
146
- const metadata = new Metadata();
147
-
148
- metadata.set('x-honeycomb-team', config.honeycombApiKey);
149
- metadata.set('x-honeycomb-dataset', config.honeycombDataset);
150
-
151
- exporter = new OTLPTraceExporter({
152
- url: 'grpc://api.honeycomb.io:443/',
153
- credentials: credentials.createSsl(),
154
- metadata,
155
- });
156
- 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}`);
157
176
  }
158
- default:
159
- throw new Error(`Unknown OpenTelemetry exporter: ${config.openTelemetryExporter}`);
160
177
  }
161
178
 
162
179
  let sampler: Sampler;
163
- switch (config.openTelemetrySamplerType) {
180
+ switch (config.openTelemetrySamplerType ?? 'always-on') {
164
181
  case 'always-on': {
165
182
  sampler = new AlwaysOnSampler();
166
183
  break;
@@ -179,6 +196,21 @@ export async function init(config: OpenTelemetryConfig) {
179
196
  throw new Error(`Unknown OpenTelemetry sampler type: ${config.openTelemetrySamplerType}`);
180
197
  }
181
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
+
182
214
  // Much of this functionality is copied from `@opentelemetry/sdk-node`, but
183
215
  // we can't use the SDK directly because of the fact that we load our config
184
216
  // asynchronously. We need to initialize our instrumentations first; only
@@ -195,20 +227,49 @@ export async function init(config: OpenTelemetryConfig) {
195
227
  );
196
228
  }
197
229
 
198
- const tracerProvider = new NodeTracerProvider({
230
+ tracerProvider = new NodeTracerProvider({
199
231
  sampler,
200
232
  resource,
201
233
  });
202
- const spanProcessor = new FilterBatchSpanProcessor(exporter, filter);
203
234
  tracerProvider.addSpanProcessor(spanProcessor);
204
235
  tracerProvider.register();
205
236
 
206
237
  instrumentations.forEach((i) => i.setTracerProvider(tracerProvider));
238
+ }
207
239
 
208
- // When the process starts shutting down, terminate OTEL stuff.
209
- process.on('SIGTERM', () => {
210
- tracerProvider.shutdown().catch((error) => console.error('Error terminating tracing', error));
211
- });
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
+ });
212
273
  }
213
274
 
214
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
  }