Skip to content

Commit

Permalink
feat: add BaggageSpanProcessor (#74)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?

- Closes #66 

## Short description of the changes

- Add BaggageSpanProcessor, and configure it in the span processor
builder
- Add and update tests to confirm baggage gets added as span attributes
- Add debug log for entries in baggage

## How to verify that this has the expected result

In the example app, add something to baggage and see it show up as an
attribute on a span created from parent context:

```js
  const tracer = trace.getTracer('example-tracer');
  // new context based on current, with key/values added to baggage
  const ctx = propagation.setBaggage(
    context.active(),
    propagation.createBaggage({
      for_the_spans: { value: 'important value' },
    }),
  );
  // starting a new span on the tracer using the saved context
  context.with(ctx, () => {
    tracer.startActiveSpan('heygirl', (span) => {
      // do work here
      span.end();
    })
  })
```

![baggage is span
attribute](https://github.com/honeycombio/honeycomb-opentelemetry-web/assets/29520003/8a29d739-710a-439c-8394-b8cffa68c39b)

or nest even further to get on downstream spans as well

```js
  const tracer = trace.getTracer('example-tracer');
  // new context based on current, with key/values added to baggage
  const ctx = propagation.setBaggage(
    context.active(),
    propagation.createBaggage({
      for_the_spans: { value: 'important value' },
    }),
  );
  // starting a new span on the tracer using the saved context
  context.with(ctx, () => {
    tracer.startActiveSpan('heygirl', (span) => {
      // do work here
      context.with(trace.setSpan(context.active(), span), () => {
        tracer.startActiveSpan('heygirl2', (childspan) => {
          // do work here
          childspan.end();
        });
        span.end();
      })
    })
  });
```


![image](https://github.com/honeycombio/honeycomb-opentelemetry-web/assets/29520003/9ff3e930-3b26-40c5-865d-88888b9142dc)
  • Loading branch information
JamieDanielson authored Feb 15, 2024
1 parent 800475a commit 3bf5219
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 7 deletions.
46 changes: 46 additions & 0 deletions src/baggage-span-processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Context, diag, propagation, Span } from '@opentelemetry/api';
import { SpanProcessor } from '@opentelemetry/sdk-trace-base';

/**
* A {@link SpanProcessor} that reads entries stored in {@link Baggage}
* from the parent context and adds the baggage entries' keys and values
* to the span as attributes on span start.
*
* Keys and values added to Baggage will appear on subsequent child
* spans for a trace within this service *and* be propagated to external
* services in accordance with any configured propagation formats
* configured. If the external services also have a Baggage span
* processor, the keys and values will appear in those child spans as
* well.
*
* ⚠ Warning ⚠️
*
* Do not put sensitive information in Baggage.
*
* To repeat: a consequence of adding data to Baggage is that the keys and
* values will appear in all outgoing HTTP headers from the application.
*/
export class BaggageSpanProcessor implements SpanProcessor {
constructor() {}

onStart(span: Span, parentContext: Context): void {
(propagation.getBaggage(parentContext)?.getAllEntries() ?? []).forEach(
(entry) => {
span.setAttribute(entry[0], entry[1].value);
diag.debug(
`@honeycombio/opentelemetry-web: 🚨 Baggage in all outgoing headers: ${entry[0]}=${entry[1].value} `,
);
},
);
}

onEnd() {}

forceFlush() {
return Promise.resolve();
}

shutdown() {
return Promise.resolve();
}
}
6 changes: 5 additions & 1 deletion src/span-processor-builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HoneycombOptions } from './types';
import { BaggageSpanProcessor } from './baggage-span-processor';
import { BrowserAttributesSpanProcessor } from './browser-attributes-span-processor';
import {
BatchSpanProcessor,
Expand All @@ -18,12 +19,15 @@ import { configureHoneycombHttpJsonTraceExporter } from './http-json-trace-expor
export const configureSpanProcessors = (options?: HoneycombOptions) => {
const honeycombSpanProcessor = new CompositeSpanProcessor();

// We have to configure the exporter here becuase the way the base SDK is setup
// We have to configure the exporter here because the way the base SDK is setup
// does not allow having both a `spanProcessor` and `traceExporter` configured.
honeycombSpanProcessor.addProcessor(
new BatchSpanProcessor(configureHoneycombHttpJsonTraceExporter(options)),
);

// we always want to add the baggage span processor
honeycombSpanProcessor.addProcessor(new BaggageSpanProcessor());

// we always want to add the browser attrs span processor
honeycombSpanProcessor.addProcessor(new BrowserAttributesSpanProcessor());

Expand Down
45 changes: 45 additions & 0 deletions test/baggage-span-processor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BaggageSpanProcessor } from '../src/baggage-span-processor';
import {
propagation,
ROOT_CONTEXT,
SpanKind,
TraceFlags,
} from '@opentelemetry/api';
import { BasicTracerProvider, Span } from '@opentelemetry/sdk-trace-base';

describe('BaggageSpanProcessor', () => {
const baggageProcessor = new BaggageSpanProcessor();

const bag = propagation.createBaggage({
brand: { value: 'samsonite' },
});

const expectedAttrs = {
brand: 'samsonite',
};

let span: Span;

beforeEach(() => {
span = new Span(
new BasicTracerProvider().getTracer('baggage-testing'),
ROOT_CONTEXT,
'Edward W. Span',
{
traceId: 'e4cda95b652f4a1592b449d5929fda1b',
spanId: '7e0c63257de34c92',
traceFlags: TraceFlags.SAMPLED,
},
SpanKind.SERVER,
);
});

test('onStart adds current Baggage entries to a span as attributes', () => {
expect(span.attributes).toEqual({});
const ctx = propagation.setBaggage(ROOT_CONTEXT, bag);

baggageProcessor.onStart(span, ctx);

expect(span.attributes).toEqual(expectedAttrs);
});
});
21 changes: 15 additions & 6 deletions test/span-processor-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
Span,
SpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import { ROOT_CONTEXT, SpanKind, TraceFlags } from '@opentelemetry/api';
import {
propagation,
ROOT_CONTEXT,
SpanKind,
TraceFlags,
} from '@opentelemetry/api';

class TestSpanProcessorOne implements SpanProcessor {
onStart(span: Span): void {
Expand Down Expand Up @@ -97,15 +102,19 @@ describe('configureSpanProcessors', () => {
SpanKind.CLIENT,
);
});
test('Configures BatchSpanProcessor & BrowserAttributesSpanProcessor by default', () => {
test('Configures BatchSpanProcessor, BaggageSpanProcessor, & BrowserAttributesSpanProcessor by default', () => {
const honeycombSpanProcessors = configureSpanProcessors({});
expect(honeycombSpanProcessors.getSpanProcessors()).toHaveLength(2);
expect(honeycombSpanProcessors.getSpanProcessors()).toHaveLength(3);
expect(honeycombSpanProcessors.getSpanProcessors()[0]).toBeInstanceOf(
BatchSpanProcessor,
);

honeycombSpanProcessors.onStart(span, ROOT_CONTEXT);
const bag = propagation.createBaggage({
'app.message': { value: 'heygirl' },
});
const ctx = propagation.setBaggage(ROOT_CONTEXT, bag);
honeycombSpanProcessors.onStart(span, ctx);
expect(span.attributes).toEqual({
'app.message': 'heygirl',
'browser.width': 1024,
'browser.height': 768,
'page.hash': '#the-hash',
Expand All @@ -120,7 +129,7 @@ describe('configureSpanProcessors', () => {
const honeycombSpanProcessors = configureSpanProcessors({
spanProcessor: new TestSpanProcessorOne(),
});
expect(honeycombSpanProcessors.getSpanProcessors()).toHaveLength(3);
expect(honeycombSpanProcessors.getSpanProcessors()).toHaveLength(4);
expect(honeycombSpanProcessors.getSpanProcessors()[0]).toBeInstanceOf(
BatchSpanProcessor,
);
Expand Down

0 comments on commit 3bf5219

Please sign in to comment.