Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ Endpoint & API Key config #22

Merged
merged 16 commits into from
Jan 19, 2024
Merged

feat: ✨ Endpoint & API Key config #22

merged 16 commits into from
Jan 19, 2024

Conversation

pkanal
Copy link
Contributor

@pkanal pkanal commented Jan 5, 2024

Which problem is this PR solving?

Short description of the changes

  • Add Honeycomb specific config option types
  • Wire up endpoint and api key config

Out of scope(for this PR):

  • Baggage Span Processor
  • Resource attributes
  • Sample rate
  • Debug mode
  • Local visualizations
  • Environment variable support

How to verify that this has the expected result

  • Run the example app with an API key and see traces in Honeycomb

@pkanal pkanal changed the title Purvi/basic-config feat: ✨ Endpoint & API Key config Jan 5, 2024
@pkanal pkanal requested review from a team and jessitron January 5, 2024 19:00
@pkanal pkanal self-assigned this Jan 5, 2024
@pkanal pkanal added the type: enhancement New feature or request label Jan 5, 2024
@pkanal pkanal added this to the Alpha milestone Jan 5, 2024
@pkanal pkanal marked this pull request as ready for review January 5, 2024 19:01
@pkanal pkanal requested a review from a team as a code owner January 5, 2024 19:01
serviceName?: string;

/** The sample rate used to determine whether a trace is exported. Defaults to 1 (send everything). */
sampleRate?: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is at a trace level, not a session level, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep this is trace level!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tell people what to set it to for something else. I think it's a whole number, "send 1 of every sampleRate traces"

* @param apikey the apikey
* @returns a boolean to indicate if the apikey was a classic key
*/
export function isClassic(apikey?: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a note, this will soon have a third variation, not sure if you want to go ahead and prepare for that (talk to API&Partnerships for the doc around new key structure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, I was thinking about this and I believe this check still stands because we're checking if a key is Classic or not and Classic keys aren't changing from 32 characters. This would only be affected if any newer keys can be 32 characters which I don't think is the case but I will double check with API&Partnerships on that!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All our code uses this "isClassic" implementation.

Copy link
Contributor

@kentquirk kentquirk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some comments, but feel free to ignore them if I'm missing the point.

src/util.ts Outdated
* based exporter protocol.
*
* @param url the base URL to append traces path to if missing
* @param protocol the exporter protocol to send telemetry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this parameter isn't on the function


const main = () => {
// Set OTel to log in Debug mode
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);

// Initialize base OTel WebSDK
const sdk = new WebSDK({
const sdk = new HoneycombWebSDK({
endpoint: 'https://api.honeycomb.io/v1/traces',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you have code below to add /v1/traces if it's missing, maybe it would be better to leave it off?

* @param protocol the exporter protocol to send telemetry
* @returns the endpoint with traces path appended if missing
*/
export function maybeAppendTracesPath(url: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might have a situation where if they add a trailing slash, it doesn't work. If so, should we check for that?

Also, what about logs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Updated the logic and I'll make a PR to the Node distro to update it there as well 👍🏽

As for logs, the distro doesn't have a log exporter at the moment so there will be a separate function and exporter for that when we support it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, logs are not considered stable in otel-js yet.
We're going to need them for FEO, but maybe not for RUM


// classic keys are 32 chars long
const classicApiKey = 'this is a string that is 32 char';
// non-classic keys are 22 chars log
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically, because they're not 0-padded, they can be any length up to 23, IIRC. But we should never depend on it.

Also, classic keys are hex only, while non-classic are alphanumeric.

test/util.test.ts Outdated Show resolved Hide resolved
expect(endpoint).toBe('https://api.honeycomb.io/v1/traces');
});

it('does not append the traces path if the url ends with /v1/traces', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a test for if they give you /v1/traces/?

@jessitron
Copy link
Contributor

I don't see any changes

Meaning: there's nothing new in the README, so from an external perspective this project is the same 😛

Copy link
Contributor

@kentquirk kentquirk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

url: getTracesEndpoint(options),
headers: {
[TEAM_HEADER_KEY]: apiKey,
[DATASET_HEADER_KEY]: isClassic(apiKey) ? options?.dataset : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dataset is different from serviceName? we don't fall back to serviceName? We don't fail if it's classic and dataset is unpopulated?
... the send will fail, which is hard to diagnose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(my surprise will be cured by documentation)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there's no dataset header needed fpr ingest needed for non-classic. It just uses whatever service.name is set to, and the backend assigns a dataset for you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cartermp I was thinking that maybe it makes sense to default dataset to service.name in classic? ... but that's probably wrong too, because it would have to be coincidentally correct.

I do think it makes sense to fail when we don't get a dataset header in Classic mode, so we can give them a link to the page that explains it.

src/types.ts Outdated
/** The API key used to send telemetry to Honeycomb. */
apiKey?: string;

/** The API key used to send traces telemetry to Honeycomb. Defaults to apikey if not set. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as opposed to?
... metrics maybe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, no, I don't see a metrics one. so what is this one for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support metrics yet but this is there because if/when we do there will also be metricsApiKey and logsApiKey

export class HoneycombWebSDK extends WebSDK {
constructor(options?: HoneycombOptions) {
super({
spanProcessor: new BatchSpanProcessor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right here at this line I say, "oh, I'm not gonna use this."

Because I can't override the spanProcessor to send to my local collector when I'm ready to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm we're going to struggle with this because we want to provide our own BrowserAttributesSpanProcessor so this can be changed to passing directly to a trace exporter but eventually we'll have to use the spanProcessor attribute.

I wonder if there's a way we can accept a span processor that we extend with the things we want to add to it 🤔 . For now I'll change this to exporting through the traceExporter option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, accepting a span processor and wrapping it is a good idea!

dataset?: string;

/** The service name of the application and where traces telemetry is stored in Honeycomb. */
serviceName?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaults to "unknown_service"

src/types.ts Outdated
/** The API endpoint where traces telemetry is sent. Defaults to endpoint if not set. */
tracesEndpoint?: string;

/** The dataset where traces telemetry is stored in Honeycomb. Only used when using a classic API key. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and required when using a classic API key, link to https://docs.honeycomb.io/honeycomb-classic/#am-i-using-honeycomb-classic

/** The debug flag enables additional logging that us useful when debugging your application. Do not use in production. */
debug?: boolean;

/** The local visualizations flag enables logging Honeycomb URLs for completed traces. Do not use in production. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is my FAVORITE

* used in conjuction with an OpenTelemetry Collector (which will handle the API key and dataset configuration).
* Defaults to 'false'.
*/
skipOptionsValidation?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if we made an option for "I have a local collector"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea, right now all our other distros are structured this way so I think we would have to have a larger conversation about adding that option

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's OK for this distro to be ahead of the others, but it does make sense to talk to the other maintainers about this direction

const honeycomb = new HoneycombWebSDK();
expect(honeycomb).toBeDefined();
expect(honeycomb).toBeInstanceOf(WebSDK);
});
Copy link
Contributor

@jessitron jessitron Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... if you can pass in options of a span processor, then you can pass in a test spanprocessor and do more testing.

(another vote for passing in a span processor)

/** The API endpoint where telemetry is sent. Defaults to 'https://api.honeycomb.io' */
endpoint?: string;

/** The API endpoint where traces telemetry is sent. Defaults to endpoint if not set. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It defaults to endpoint/v1/traces !!! That is why you would set this endpoint

| *serviceName* | optional | string|unknown_service | The name of this browser application. Your telemetry will go to a Honeycomb dataset with this name. |
| *localVisualizations*| optional | boolean | false | For each trace created, print a link to the console so that you can find it in Honeycomb. Super useful in development! Do not use in production. |
| sampleRate | optional | number |1 | If you want to send a random fraction of traces, then make this a whole number greater than 1. Only 1 in `sampleRate` traces will be sent, and the rest never be created. |
| tracesEndpoint | optional | string|`${endpoint}/v1/traces` | Populate this to send traces to a route other than /v1/traces |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually doesn't append /v1/traces to an endpoint. This option exists similar to tracesApiKey for when you want to set different endpoints for different signals.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you don't set tracesEndpoint, then does it send to $endpoint/v1/traces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... because that's what the regular SDK does

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm so are we saying, we default tracesEndpoint to $endpoint, but then Otel adds /v1/traces under the hood if it's only a domain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic we follow for endpoints is laid out in our distro spec.

This is the order of logic:

  1. if tracesEndpoint is set, use that and do not append /v1/traces
  2. if tracesEndpoint is not set, fall back to endpoint which will append /v1/traces
  3. if endpoint is not set, default to https://api.honeycomb.io with /v1/traces appended

We add /v1/traces to endpoint automatically (OTel doesn't do this under the hood), as long as the endpointstring doesn't already end in/v1/traces`.

@pkanal
Copy link
Contributor Author

pkanal commented Jan 19, 2024

Merging this for now, will have follow up PRs for tests & collector option

@pkanal pkanal merged commit 40c3682 into main Jan 19, 2024
7 checks passed
@pkanal pkanal deleted the purvi/basic-config branch January 19, 2024 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Honeycomb config options
5 participants