Skip to content

Commit

Permalink
chore: Pass telemetry resources from the node process to the browser (c…
Browse files Browse the repository at this point in the history
…ypress-io#26468)

* chore: pass resources from the node process to the browser

* don't check version

* Apply suggestions from code review

Co-authored-by: Bill Glesias <[email protected]>

* reuse type for attributes

* use auth header

* ts?

* Update packages/telemetry/src/index.ts

Co-authored-by: Matt Schile <[email protected]>

---------

Co-authored-by: Bill Glesias <[email protected]>
Co-authored-by: Matt Schile <[email protected]>
  • Loading branch information
3 people authored and astone123 committed Apr 19, 2023
1 parent 2594bc9 commit 2520695
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 100 deletions.
2 changes: 1 addition & 1 deletion packages/data-context/src/sources/HtmlDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class HtmlDataSource {
window.__CYPRESS_CONFIG__ = ${JSON.stringify(serveConfig)};
window.__CYPRESS_TESTING_TYPE__ = '${this.ctx.coreData.currentTestingType}'
window.__CYPRESS_BROWSER__ = ${JSON.stringify(this.ctx.coreData.activeBrowser)}
${telemetry.isEnabled() ? `window.__CYPRESS_TELEMETRY__ = ${JSON.stringify({ context: telemetry.getActiveContextObject() })}` : ''}
${telemetry.isEnabled() ? `window.__CYPRESS_TELEMETRY__ = ${JSON.stringify({ context: telemetry.getActiveContextObject(), resources: telemetry.getResources() })}` : ''}
${process.env.CYPRESS_INTERNAL_GQL_NO_SOCKET ? `window.__CYPRESS_GQL_NO_SOCKET__ = 'true';` : ''}
</script>
`)
Expand Down
1 change: 0 additions & 1 deletion packages/frontend-shared/src/graphql/urqlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ declare global {
__RUN_MODE_SPECS__: SpecFile[]
__CYPRESS_TESTING_TYPE__: 'e2e' | 'component'
__CYPRESS_BROWSER__: Partial<Browser> & {majorVersion: string | number}
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}}
__CYPRESS_CONFIG__: {
base64Config: string
namespace: AutomationElementId
Expand Down
7 changes: 4 additions & 3 deletions packages/telemetry/src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Span } from '@opentelemetry/api'
import type { Span, Attributes } from '@opentelemetry/api'
import type { startSpanOptions, findActiveSpanOptions, contextObject } from './index'
import { Telemetry as TelemetryClass, TelemetryNoop } from './index'
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
Expand All @@ -8,7 +8,7 @@ import { OTLPTraceExporter } from './span-exporters/websocket-span-exporter'

declare global {
interface Window {
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}}
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}, resources: Attributes}
cypressTelemetrySingleton?: TelemetryClass | TelemetryNoop
}
}
Expand All @@ -32,7 +32,7 @@ const init = ({ namespace, config }: { namespace: string, config: {version: stri
throw ('Telemetry instance has already be initialized')
}

const { context } = window.__CYPRESS_TELEMETRY__
const { context, resources } = window.__CYPRESS_TELEMETRY__

// We always use the websocket exporter for browser telemetry
const exporter = new OTLPTraceExporter()
Expand All @@ -51,6 +51,7 @@ const init = ({ namespace, config }: { namespace: string, config: {version: stri
// TODO: create a browser batch span processor to account for navigation.
// See https://github.com/open-telemetry/opentelemetry-js/issues/2613
SpanProcessor: SimpleSpanProcessor,
resources,
})

window.cypressTelemetrySingleton = telemetryInstance
Expand Down
17 changes: 16 additions & 1 deletion packages/telemetry/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Span, SpanOptions, Tracer, Context } from '@opentelemetry/api'
import type { Span, SpanOptions, Tracer, Context, Attributes } from '@opentelemetry/api'
import type { BasicTracerProvider, SimpleSpanProcessor, BatchSpanProcessor, SpanExporter } from '@opentelemetry/sdk-trace-base'
import type { DetectorSync } from '@opentelemetry/resources'

Expand Down Expand Up @@ -30,6 +30,7 @@ export interface TelemetryApi {
findActiveSpan(fn: findActiveSpanOptions): Span | undefined
endActiveSpanAndChildren (span?: Span | undefined): void
getActiveContextObject (): contextObject
getResources (): Attributes
shutdown (): Promise<void>
getExporter (): SpanExporter | undefined
setRootContext (rootContextObject?: contextObject): void
Expand All @@ -51,6 +52,7 @@ export class Telemetry implements TelemetryApi {
version,
SpanProcessor,
exporter,
resources = {},
}: {
namespace?: string
Provider: typeof BasicTracerProvider
Expand All @@ -59,13 +61,15 @@ export class Telemetry implements TelemetryApi {
version: string
SpanProcessor: typeof SimpleSpanProcessor | typeof BatchSpanProcessor
exporter: SpanExporter
resources?: Attributes
}) {
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL)

// Setup default resources
const resource = Resource.default().merge(
new Resource({
...resources,
[ SemanticResourceAttributes.SERVICE_NAME ]: 'cypress-app',
[ SemanticResourceAttributes.SERVICE_NAMESPACE ]: namespace,
[ SemanticResourceAttributes.SERVICE_VERSION ]: version,
Expand Down Expand Up @@ -217,6 +221,14 @@ export class Telemetry implements TelemetryApi {
return myCtx
}

/**
* Gets a list of the resources currently set on the provider.
* @returns Attributes of resources
*/
getResources (): Attributes {
return this.provider.resource.attributes
}

/**
* Shuts down telemetry and flushes any batched spans.
* @returns promise
Expand Down Expand Up @@ -266,6 +278,9 @@ export class TelemetryNoop implements TelemetryApi {
getActiveContextObject (): contextObject {
return {}
}
getResources () {
return {}
}
shutdown () {
return Promise.resolve()
}
Expand Down
1 change: 1 addition & 0 deletions packages/telemetry/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const telemetry = {
findActiveSpan: (arg: findActiveSpanOptions) => telemetryInstance.findActiveSpan(arg),
endActiveSpanAndChildren: (arg?: Span): void => telemetryInstance.endActiveSpanAndChildren(arg),
getActiveContextObject: () => telemetryInstance.getActiveContextObject(),
getResources: () => telemetryInstance.getResources(),
shutdown: () => telemetryInstance.shutdown(),
exporter: (): void | OTLPTraceExporterIpc | OTLPTraceExporterCloud => telemetryInstance.getExporter() as void | OTLPTraceExporterIpc | OTLPTraceExporterCloud,
}
Expand Down
26 changes: 20 additions & 6 deletions packages/telemetry/src/span-exporters/cloud-span-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
onError: (error: OTLPExporterError) => void
}[]
enc: OTLPExporterNodeConfigBasePlusEncryption['encryption'] | undefined
projectId?: string
recordKey?: string
sendWithHttp: typeof sendWithHttp
constructor (config: OTLPExporterNodeConfigBasePlusEncryption = {}) {
super(config)
Expand All @@ -51,8 +53,10 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
return
}

// Continue to send this header for passivity until the cloud is released.
this.headers['x-project-id'] = projectId
this.sendDelayedItems()
this.projectId = projectId
this.setAuthorizationHeader()
}

/**
Expand All @@ -64,15 +68,25 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
return
}

this.headers['x-record-key'] = recordKey
this.sendDelayedItems()
this.recordKey = recordKey
this.setAuthorizationHeader()
}

/**
* Sets the auth header based on the project id and record key.
*/
setAuthorizationHeader () {
if (this.projectId && this.recordKey) {
this.headers.Authorization = `Basic ${Buffer.from(`${this.projectId}:${this.recordKey}`).toString('base64')}`
this.sendDelayedItems()
}
}

/**
* exports delayed spans if both the record key and project id are present
*/
sendDelayedItems () {
if (this.headers['x-project-id'] && this.headers['x-record-key']) {
if (this.headers.Authorization) {
this.delayedItemsToExport.forEach((item) => {
this.send(item.serviceRequest, item.onSuccess, item.onError)
})
Expand Down Expand Up @@ -107,8 +121,8 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
serviceRequest = objects
}

// Delay items if we want encryption but don't have a project id and a record key
if (this.enc && !(this.headers['x-project-id'] && this.headers['x-record-key'])) {
// Delay items if we want encryption but don't have an authorization header
if (this.enc && !this.headers.Authorization) {
this.delayedItemsToExport.push({ serviceRequest, onSuccess, onError })

return
Expand Down
6 changes: 5 additions & 1 deletion packages/telemetry/test/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,14 @@ describe('telemetry is disabled', () => {

describe('telemetry is enabled', () => {
before('init', () => {
// @ts-expect-error
global.window.__CYPRESS_TELEMETRY__ = {
context: {
traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01',
},
resources: {
herp: 'derp',
},
}

expect(telemetry.init({
Expand All @@ -90,13 +94,13 @@ describe('telemetry is enabled', () => {
})).to.not.throw

expect(window.cypressTelemetrySingleton).to.be.instanceOf(TelemetryClass)
expect(window.cypressTelemetrySingleton.getResources()).to.contain({ herp: 'derp' })
})

describe('attachWebSocket', () => {
it('returns true', () => {
telemetry.attachWebSocket('ws')

// @ts-expect-error
expect(window.cypressTelemetrySingleton?.getExporter()?.ws).to.equal('ws')
})
})
Expand Down
29 changes: 29 additions & 0 deletions packages/telemetry/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,35 @@ describe('getActiveContextObject', () => {
})
})

describe('getResources', () => {
it('returns the active resources', () => {
const exporter = new OTLPTraceExporterCloud()

const tel = new Telemetry({
namespace: 'namespace',
Provider: NodeTracerProvider,
detectors: [],
exporter,
version: 'version',
rootContextObject: { traceparent: 'id' },
SpanProcessor: BatchSpanProcessor,
resources: {
herp: 'derp',
'service.name': 'not overridden',
},
})

expect(tel.getResources()).to.contain({
'service.name': 'cypress-app',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
herp: 'derp',
'service.namespace': 'namespace',
'service.version': 'version',
})
})
})

describe('shutdown', () => {
it('confirms shutdown is called', async () => {
const exporter = new OTLPTraceExporterCloud()
Expand Down
18 changes: 18 additions & 0 deletions packages/telemetry/test/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ describe('telemetry is disabled', () => {
})
})

describe('getResources', () => {
it('returns an empty object', () => {
expect(telemetry.getResources()).to.not.be.undefined
})
})

describe('shutdown', () => {
it('does not throw', () => {
expect(telemetry.shutdown()).to.not.throw
Expand Down Expand Up @@ -126,6 +132,18 @@ describe('telemetry is enabled', () => {
})
})

describe('getResources', () => {
it('returns an empty object', () => {
expect(telemetry.getResources()).to.include({
'service.name': 'cypress-app',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'service.namespace': 'namespace',
'service.version': 'version',
})
})
})

describe('shutdown', () => {
it('does not throw', () => {
expect(telemetry.shutdown()).to.not.throw
Expand Down
Loading

0 comments on commit 2520695

Please sign in to comment.