Skip to content

Commit 2520695

Browse files
mjhenkesAtofStrykermschile
authored andcommitted
chore: Pass telemetry resources from the node process to the browser (cypress-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 <bglesias@gmail.com> * reuse type for attributes * use auth header * ts? * Update packages/telemetry/src/index.ts Co-authored-by: Matt Schile <mschile@cypress.io> --------- Co-authored-by: Bill Glesias <bglesias@gmail.com> Co-authored-by: Matt Schile <mschile@cypress.io>
1 parent 2594bc9 commit 2520695

File tree

10 files changed

+126
-100
lines changed

10 files changed

+126
-100
lines changed

packages/data-context/src/sources/HtmlDataSource.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class HtmlDataSource {
120120
window.__CYPRESS_CONFIG__ = ${JSON.stringify(serveConfig)};
121121
window.__CYPRESS_TESTING_TYPE__ = '${this.ctx.coreData.currentTestingType}'
122122
window.__CYPRESS_BROWSER__ = ${JSON.stringify(this.ctx.coreData.activeBrowser)}
123-
${telemetry.isEnabled() ? `window.__CYPRESS_TELEMETRY__ = ${JSON.stringify({ context: telemetry.getActiveContextObject() })}` : ''}
123+
${telemetry.isEnabled() ? `window.__CYPRESS_TELEMETRY__ = ${JSON.stringify({ context: telemetry.getActiveContextObject(), resources: telemetry.getResources() })}` : ''}
124124
${process.env.CYPRESS_INTERNAL_GQL_NO_SOCKET ? `window.__CYPRESS_GQL_NO_SOCKET__ = 'true';` : ''}
125125
</script>
126126
`)

packages/frontend-shared/src/graphql/urqlClient.ts

-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ declare global {
103103
__RUN_MODE_SPECS__: SpecFile[]
104104
__CYPRESS_TESTING_TYPE__: 'e2e' | 'component'
105105
__CYPRESS_BROWSER__: Partial<Browser> & {majorVersion: string | number}
106-
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}}
107106
__CYPRESS_CONFIG__: {
108107
base64Config: string
109108
namespace: AutomationElementId

packages/telemetry/src/browser.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Span } from '@opentelemetry/api'
1+
import type { Span, Attributes } from '@opentelemetry/api'
22
import type { startSpanOptions, findActiveSpanOptions, contextObject } from './index'
33
import { Telemetry as TelemetryClass, TelemetryNoop } from './index'
44
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
@@ -8,7 +8,7 @@ import { OTLPTraceExporter } from './span-exporters/websocket-span-exporter'
88

99
declare global {
1010
interface Window {
11-
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}}
11+
__CYPRESS_TELEMETRY__?: {context: {traceparent: string}, resources: Attributes}
1212
cypressTelemetrySingleton?: TelemetryClass | TelemetryNoop
1313
}
1414
}
@@ -32,7 +32,7 @@ const init = ({ namespace, config }: { namespace: string, config: {version: stri
3232
throw ('Telemetry instance has already be initialized')
3333
}
3434

35-
const { context } = window.__CYPRESS_TELEMETRY__
35+
const { context, resources } = window.__CYPRESS_TELEMETRY__
3636

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

5657
window.cypressTelemetrySingleton = telemetryInstance

packages/telemetry/src/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Span, SpanOptions, Tracer, Context } from '@opentelemetry/api'
1+
import type { Span, SpanOptions, Tracer, Context, Attributes } from '@opentelemetry/api'
22
import type { BasicTracerProvider, SimpleSpanProcessor, BatchSpanProcessor, SpanExporter } from '@opentelemetry/sdk-trace-base'
33
import type { DetectorSync } from '@opentelemetry/resources'
44

@@ -30,6 +30,7 @@ export interface TelemetryApi {
3030
findActiveSpan(fn: findActiveSpanOptions): Span | undefined
3131
endActiveSpanAndChildren (span?: Span | undefined): void
3232
getActiveContextObject (): contextObject
33+
getResources (): Attributes
3334
shutdown (): Promise<void>
3435
getExporter (): SpanExporter | undefined
3536
setRootContext (rootContextObject?: contextObject): void
@@ -51,6 +52,7 @@ export class Telemetry implements TelemetryApi {
5152
version,
5253
SpanProcessor,
5354
exporter,
55+
resources = {},
5456
}: {
5557
namespace?: string
5658
Provider: typeof BasicTracerProvider
@@ -59,13 +61,15 @@ export class Telemetry implements TelemetryApi {
5961
version: string
6062
SpanProcessor: typeof SimpleSpanProcessor | typeof BatchSpanProcessor
6163
exporter: SpanExporter
64+
resources?: Attributes
6265
}) {
6366
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
6467
// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL)
6568

6669
// Setup default resources
6770
const resource = Resource.default().merge(
6871
new Resource({
72+
...resources,
6973
[ SemanticResourceAttributes.SERVICE_NAME ]: 'cypress-app',
7074
[ SemanticResourceAttributes.SERVICE_NAMESPACE ]: namespace,
7175
[ SemanticResourceAttributes.SERVICE_VERSION ]: version,
@@ -217,6 +221,14 @@ export class Telemetry implements TelemetryApi {
217221
return myCtx
218222
}
219223

224+
/**
225+
* Gets a list of the resources currently set on the provider.
226+
* @returns Attributes of resources
227+
*/
228+
getResources (): Attributes {
229+
return this.provider.resource.attributes
230+
}
231+
220232
/**
221233
* Shuts down telemetry and flushes any batched spans.
222234
* @returns promise
@@ -266,6 +278,9 @@ export class TelemetryNoop implements TelemetryApi {
266278
getActiveContextObject (): contextObject {
267279
return {}
268280
}
281+
getResources () {
282+
return {}
283+
}
269284
shutdown () {
270285
return Promise.resolve()
271286
}

packages/telemetry/src/node.ts

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const telemetry = {
6969
findActiveSpan: (arg: findActiveSpanOptions) => telemetryInstance.findActiveSpan(arg),
7070
endActiveSpanAndChildren: (arg?: Span): void => telemetryInstance.endActiveSpanAndChildren(arg),
7171
getActiveContextObject: () => telemetryInstance.getActiveContextObject(),
72+
getResources: () => telemetryInstance.getResources(),
7273
shutdown: () => telemetryInstance.shutdown(),
7374
exporter: (): void | OTLPTraceExporterIpc | OTLPTraceExporterCloud => telemetryInstance.getExporter() as void | OTLPTraceExporterIpc | OTLPTraceExporterCloud,
7475
}

packages/telemetry/src/span-exporters/cloud-span-exporter.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
3131
onError: (error: OTLPExporterError) => void
3232
}[]
3333
enc: OTLPExporterNodeConfigBasePlusEncryption['encryption'] | undefined
34+
projectId?: string
35+
recordKey?: string
3436
sendWithHttp: typeof sendWithHttp
3537
constructor (config: OTLPExporterNodeConfigBasePlusEncryption = {}) {
3638
super(config)
@@ -51,8 +53,10 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
5153
return
5254
}
5355

56+
// Continue to send this header for passivity until the cloud is released.
5457
this.headers['x-project-id'] = projectId
55-
this.sendDelayedItems()
58+
this.projectId = projectId
59+
this.setAuthorizationHeader()
5660
}
5761

5862
/**
@@ -64,15 +68,25 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
6468
return
6569
}
6670

67-
this.headers['x-record-key'] = recordKey
68-
this.sendDelayedItems()
71+
this.recordKey = recordKey
72+
this.setAuthorizationHeader()
73+
}
74+
75+
/**
76+
* Sets the auth header based on the project id and record key.
77+
*/
78+
setAuthorizationHeader () {
79+
if (this.projectId && this.recordKey) {
80+
this.headers.Authorization = `Basic ${Buffer.from(`${this.projectId}:${this.recordKey}`).toString('base64')}`
81+
this.sendDelayedItems()
82+
}
6983
}
7084

7185
/**
7286
* exports delayed spans if both the record key and project id are present
7387
*/
7488
sendDelayedItems () {
75-
if (this.headers['x-project-id'] && this.headers['x-record-key']) {
89+
if (this.headers.Authorization) {
7690
this.delayedItemsToExport.forEach((item) => {
7791
this.send(item.serviceRequest, item.onSuccess, item.onError)
7892
})
@@ -107,8 +121,8 @@ export class OTLPTraceExporter extends OTLPTraceExporterHttp {
107121
serviceRequest = objects
108122
}
109123

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

114128
return

packages/telemetry/test/browser.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ describe('telemetry is disabled', () => {
7878

7979
describe('telemetry is enabled', () => {
8080
before('init', () => {
81+
// @ts-expect-error
8182
global.window.__CYPRESS_TELEMETRY__ = {
8283
context: {
8384
traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01',
8485
},
86+
resources: {
87+
herp: 'derp',
88+
},
8589
}
8690

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

9296
expect(window.cypressTelemetrySingleton).to.be.instanceOf(TelemetryClass)
97+
expect(window.cypressTelemetrySingleton.getResources()).to.contain({ herp: 'derp' })
9398
})
9499

95100
describe('attachWebSocket', () => {
96101
it('returns true', () => {
97102
telemetry.attachWebSocket('ws')
98103

99-
// @ts-expect-error
100104
expect(window.cypressTelemetrySingleton?.getExporter()?.ws).to.equal('ws')
101105
})
102106
})

packages/telemetry/test/index.spec.ts

+29
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,35 @@ describe('getActiveContextObject', () => {
244244
})
245245
})
246246

247+
describe('getResources', () => {
248+
it('returns the active resources', () => {
249+
const exporter = new OTLPTraceExporterCloud()
250+
251+
const tel = new Telemetry({
252+
namespace: 'namespace',
253+
Provider: NodeTracerProvider,
254+
detectors: [],
255+
exporter,
256+
version: 'version',
257+
rootContextObject: { traceparent: 'id' },
258+
SpanProcessor: BatchSpanProcessor,
259+
resources: {
260+
herp: 'derp',
261+
'service.name': 'not overridden',
262+
},
263+
})
264+
265+
expect(tel.getResources()).to.contain({
266+
'service.name': 'cypress-app',
267+
'telemetry.sdk.language': 'nodejs',
268+
'telemetry.sdk.name': 'opentelemetry',
269+
herp: 'derp',
270+
'service.namespace': 'namespace',
271+
'service.version': 'version',
272+
})
273+
})
274+
})
275+
247276
describe('shutdown', () => {
248277
it('confirms shutdown is called', async () => {
249278
const exporter = new OTLPTraceExporterCloud()

packages/telemetry/test/node.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ describe('telemetry is disabled', () => {
5454
})
5555
})
5656

57+
describe('getResources', () => {
58+
it('returns an empty object', () => {
59+
expect(telemetry.getResources()).to.not.be.undefined
60+
})
61+
})
62+
5763
describe('shutdown', () => {
5864
it('does not throw', () => {
5965
expect(telemetry.shutdown()).to.not.throw
@@ -126,6 +132,18 @@ describe('telemetry is enabled', () => {
126132
})
127133
})
128134

135+
describe('getResources', () => {
136+
it('returns an empty object', () => {
137+
expect(telemetry.getResources()).to.include({
138+
'service.name': 'cypress-app',
139+
'telemetry.sdk.language': 'nodejs',
140+
'telemetry.sdk.name': 'opentelemetry',
141+
'service.namespace': 'namespace',
142+
'service.version': 'version',
143+
})
144+
})
145+
})
146+
129147
describe('shutdown', () => {
130148
it('does not throw', () => {
131149
expect(telemetry.shutdown()).to.not.throw

0 commit comments

Comments
 (0)