Skip to content

Commit f27a14a

Browse files
committed
Ensure all SDKs and E2E apps build.
1 parent fd77bfb commit f27a14a

File tree

29 files changed

+557
-479
lines changed

29 files changed

+557
-479
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
diff --git a/lib/ansi-color.js b/lib/ansi-color.js
2+
index 1062c87b46c5515a2d0c813b38de333b88149d0b..4fc2847ace3bddc79b17720f99fec86952cd6f03 100644
3+
--- a/lib/ansi-color.js
4+
+++ b/lib/ansi-color.js
5+
@@ -32,8 +32,8 @@ exports.set = function(str, color) {
6+
var color_attrs = color.split("+");
7+
var ansi_str = "";
8+
for(var i=0, attr; attr = color_attrs[i]; i++) {
9+
- ansi_str += "\033[" + ANSI_CODES[attr] + "m";
10+
+ ansi_str += "\x1b[" + ANSI_CODES[attr] + "m";
11+
}
12+
- ansi_str += str + "\033[" + ANSI_CODES["off"] + "m";
13+
+ ansi_str += str + "\x1b[" + ANSI_CODES["off"] + "m";
14+
return ansi_str;
15+
};

e2e/aws-lambda/.aws-sam/build.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# This file is auto generated by SAM CLI build command
22

3-
[function_build_definitions.a4401efe-3046-4aca-bd72-e11b2453bf76]
4-
codeuri = "/Users/vkorolik/work/highlight/e2e/aws-lambda"
5-
runtime = "nodejs22.x"
3+
[function_build_definitions.f491aa90-6601-4fae-b66b-2d7ab02eab70]
4+
codeuri = "/Users/vkorolik/work/observability-sdk/e2e/aws-lambda"
5+
runtime = "nodejs20.x"
66
architecture = "x86_64"
77
handler = "src/handlers/api.handler"
8-
manifest_hash = "43c319f517af8c1de7698d947f954ade"
8+
manifest_hash = "8f96f24f1b795d5dd644f39174ed8dd4"
99
packagetype = "Zip"
1010
functions = ["apiFunction"]
1111

e2e/mock-otel-server/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# mock-otel-server

e2e/mock-otel-server/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "mock-otel-server",
3+
"main": "src/index.ts",
4+
"private": true,
5+
"type": "module",
6+
"packageManager": "yarn@3.5.0",
7+
"dependencies": {
8+
"@opentelemetry/api": "^1.9.0",
9+
"@opentelemetry/otlp-transformer": "^0.57.1",
10+
"express": "^4.19.2"
11+
},
12+
"scripts": {
13+
"start": "tsx src/index.ts --start"
14+
},
15+
"devDependencies": {
16+
"tsx": "^4.6.2"
17+
}
18+
}

e2e/mock-otel-server/src/index.ts

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import express from 'express'
2+
import zlib from 'zlib'
3+
4+
import {
5+
IEvent,
6+
IExportTraceServiceRequest,
7+
IResourceSpans,
8+
ISpan,
9+
} from '@opentelemetry/otlp-transformer/build/esm/trace/internal-types'
10+
11+
const DEFAULT_PORT = 3101
12+
const RESOURCE_SPANS_BY_PORT = new Map<number, IResourceSpans[]>()
13+
const shouldStart = !!process.argv.find((arg) => arg.includes('--start'))
14+
15+
if (shouldStart) {
16+
const portFlag =
17+
process.argv.find((arg) => arg.includes('--port')) ||
18+
`--port=${DEFAULT_PORT}`
19+
const port = +portFlag.split('=')[1]
20+
21+
startMockOtelServer({ port })
22+
}
23+
24+
export function startMockOtelServer({
25+
port = DEFAULT_PORT,
26+
}: {
27+
port?: number
28+
}) {
29+
const app = express()
30+
app.use(unzipBody)
31+
app.use((req, res, next) => {
32+
try {
33+
const trace = JSON.parse(
34+
req.body.toString(),
35+
) as IExportTraceServiceRequest
36+
37+
if (trace.resourceSpans) {
38+
const resourceSpans = getResourceSpansByPort(port)
39+
const spanNames = trace.resourceSpans.flatMap(
40+
(resourceSpan) =>
41+
resourceSpan.scopeSpans.flatMap((scopeSpan) =>
42+
scopeSpan.spans?.flatMap((span) => span.name),
43+
) ?? [],
44+
)
45+
const spanAttributes = [
46+
...trace.resourceSpans.flatMap(
47+
(resourceSpan) =>
48+
resourceSpan.resource?.attributes.flatMap(
49+
(attr) => attr.key,
50+
) ?? [],
51+
),
52+
...trace.resourceSpans.flatMap(
53+
(resourceSpan) =>
54+
resourceSpan.scopeSpans.flatMap(
55+
(span) => span.scope?.attributes,
56+
) ?? [],
57+
),
58+
...trace.resourceSpans.flatMap(
59+
(resourceSpan) =>
60+
resourceSpan.scopeSpans.flatMap((span) =>
61+
span.spans?.flatMap((s) => s.attributes),
62+
) ?? [],
63+
),
64+
]
65+
console.log({ trace, spanNames, spanAttributes })
66+
67+
resourceSpans.push(...trace.resourceSpans)
68+
69+
setResourceSpansByPort(port, resourceSpans)
70+
}
71+
} catch (error) {
72+
console.error(error)
73+
}
74+
next()
75+
})
76+
77+
app.post('/v1/traces', (req, res) => {
78+
res.status(200).send('OK')
79+
})
80+
81+
const server = app.listen(port, () => {
82+
console.info(`Server is running on ${port}`)
83+
})
84+
85+
return () => server.close()
86+
}
87+
88+
export function getResourceSpans(port = DEFAULT_PORT, names: string[] = []) {
89+
return new Promise<{
90+
details: ReturnType<typeof aggregateAttributes>
91+
resourceSpans: IResourceSpans[]
92+
}>((resolve, reject) => {
93+
const interval = setInterval(() => {
94+
const resourceSpans = getResourceSpansByPort(port)
95+
const details = aggregateAttributes(resourceSpans)
96+
const hasSessionId = details.some(
97+
(detail) => detail.attributes['highlight.session_id'],
98+
)
99+
const hasName =
100+
!names.length ||
101+
details
102+
.flatMap((detail) => detail.events)
103+
.some((event) => names.includes(event.name))
104+
105+
if (hasSessionId && hasName) {
106+
clearInterval(interval)
107+
108+
resolve({ details, resourceSpans })
109+
}
110+
}, 250)
111+
})
112+
}
113+
114+
export function clearResourceSpans(port = DEFAULT_PORT) {
115+
setResourceSpansByPort(port, [])
116+
}
117+
118+
function unzipBody(
119+
req: express.Request,
120+
res: express.Response,
121+
next: express.NextFunction,
122+
) {
123+
if (req.headers['content-encoding'] === 'gzip') {
124+
const unzip = zlib.createGunzip()
125+
const stream = req.pipe(unzip)
126+
const buffers: Buffer[] = []
127+
128+
stream.on('data', (chunk) => {
129+
buffers.push(chunk)
130+
})
131+
132+
stream.on('end', () => {
133+
req.body = Buffer.concat(buffers)
134+
next()
135+
})
136+
} else {
137+
next()
138+
}
139+
}
140+
141+
function aggregateAttributes(resourceSpans: IResourceSpans[]) {
142+
const aggregatedAttributes: {
143+
spanNames: string[]
144+
events: IEvent[]
145+
attributes: Record<string, string>
146+
}[] = []
147+
148+
resourceSpans?.forEach((resourceSpan) => {
149+
const filteredSpans = resourceSpan.scopeSpans.flatMap(
150+
(scopeSpan) => scopeSpan.spans,
151+
) as ISpan[]
152+
const spanNames = filteredSpans.map((span) => span.name)
153+
154+
const resourceAttributes =
155+
resourceSpan.resource?.attributes.reduce<Record<string, string>>(
156+
(acc, attribute) => {
157+
acc[attribute.key] =
158+
attribute.value.stringValue ||
159+
attribute.value.boolValue?.toString() ||
160+
attribute.value.intValue?.toString() ||
161+
attribute.value.arrayValue?.toString() ||
162+
''
163+
164+
return acc
165+
},
166+
{},
167+
) || {}
168+
const spanAttributes = filteredSpans
169+
.flatMap((span) => span.attributes)
170+
.reduce<Record<string, string>>((acc, attribute) => {
171+
acc[attribute.key] =
172+
attribute.value.stringValue ||
173+
attribute.value.boolValue?.toString() ||
174+
attribute.value.intValue?.toString() ||
175+
attribute.value.arrayValue?.toString() ||
176+
''
177+
178+
return acc
179+
}, resourceAttributes)
180+
const events = filteredSpans.flatMap((span) => span.events) as IEvent[]
181+
const attributes = events.reduce<Record<string, string>>(
182+
(acc, event) => {
183+
event.attributes.forEach((attribute) => {
184+
acc[attribute.key] =
185+
attribute.value.stringValue ||
186+
attribute.value.boolValue?.toString() ||
187+
attribute.value.intValue?.toString() ||
188+
attribute.value.arrayValue?.toString() ||
189+
''
190+
})
191+
192+
return acc
193+
},
194+
spanAttributes,
195+
)
196+
197+
aggregatedAttributes.push({
198+
spanNames,
199+
attributes,
200+
events,
201+
})
202+
})
203+
204+
return aggregatedAttributes
205+
}
206+
207+
function getResourceSpansByPort(port = DEFAULT_PORT) {
208+
return RESOURCE_SPANS_BY_PORT.get(port) || []
209+
}
210+
211+
function setResourceSpansByPort(port = DEFAULT_PORT, resourceSpans: any[]) {
212+
RESOURCE_SPANS_BY_PORT.set(port, resourceSpans)
213+
}
214+
215+
export function getOtlpEndpoint(port = DEFAULT_PORT) {
216+
return `http://127.0.0.1:${port}`
217+
}
218+
219+
export function filterEventsByName(
220+
details: ReturnType<typeof aggregateAttributes>,
221+
name: string,
222+
) {
223+
const events = details.flatMap((detail) => detail.events)
224+
225+
return events.filter((event) => event.name === name)
226+
}
227+
228+
export function filterDetailsBySessionId(
229+
details: ReturnType<typeof aggregateAttributes>,
230+
sessionId: string,
231+
) {
232+
return details.filter(
233+
(detail) => detail.attributes['highlight.session_id'] === sessionId,
234+
)
235+
}
236+
237+
export function logDetails(
238+
details: Awaited<ReturnType<typeof getResourceSpans>>['details'],
239+
) {
240+
console.info(details.flatMap((d) => d.spanNames))
241+
console.info(details.flatMap((d) => d.events.map((e) => e.name)))
242+
}

e2e/mock-otel-server/tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"module": "ESNext",
5+
"strict": true,
6+
"esModuleInterop": true,
7+
"moduleResolution": "node",
8+
"allowSyntheticDefaultImports": true
9+
},
10+
"include": ["src/index.ts"],
11+
"exclude": ["node_modules"]
12+
}

e2e/nextjs/src/app/components/highlight-buttons.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@
22

33
import { Button } from '@/app/components/button'
44
import { H } from '@highlight-run/next/client'
5+
import { useMemo } from 'react'
56

67
export function HighlightButtons() {
8+
const ldClientPromise = useMemo(async () => {
9+
const { initialize } = await import('@launchdarkly/js-client-sdk')
10+
const ldClient = initialize(
11+
process.env.NEXT_PUBLIC_LAUNCHDARKLY_SDK_KEY ?? '',
12+
)
13+
H.registerLD(ldClient)
14+
return ldClient
15+
}, [])
716
return (
817
<div
918
style={{
@@ -14,21 +23,18 @@ export function HighlightButtons() {
1423
}}
1524
>
1625
<Button
17-
onClick={() => {
18-
// @ts-ignore
19-
const flag = window.ldClient.variation(
20-
'my-boolean-flag',
21-
true,
22-
)
26+
onClick={async () => {
27+
const ldClient = await ldClientPromise
28+
const flag = ldClient.variation('my-boolean-flag', true)
2329
console.log('flag', flag)
2430
}}
2531
>
2632
Variation
2733
</Button>
2834
<Button
29-
onClick={() => {
30-
// @ts-ignore
31-
window.ldClient.identify({
35+
onClick={async () => {
36+
const ldClient = await ldClientPromise
37+
await ldClient.identify({
3238
kind: 'multi',
3339
user: { key: 'vadim' },
3440
org: { key: 'tester' },
Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
'use client'
22

33
import { H } from '@highlight-run/next/client'
4-
import { initialize } from '@launchdarkly/js-client-sdk'
5-
import { useEffect } from 'react'
6-
7-
H.init('1', {
8-
debug: { clientInteractions: true, domRecording: true },
9-
})
10-
const ldClient = initialize(process.env.NEXT_PUBLIC_LAUNCHDARKLY_SDK_KEY)
11-
// @ts-ignore
12-
window.ldClient = ldClient
13-
H.registerLD(ldClient)
4+
import { useEffect, useMemo } from 'react'
145

156
export function HighlightIdentify() {
16-
useEffect(() => {
17-
// @ts-ignore
18-
window.ldClient.identify({
19-
kind: 'multi',
20-
user: { key: 'bob' },
21-
org: { key: 'MacDonwalds' },
22-
})
7+
const ldClientPromise = useMemo(async () => {
8+
const { initialize } = await import('@launchdarkly/js-client-sdk')
9+
const ldClient = initialize(
10+
process.env.NEXT_PUBLIC_LAUNCHDARKLY_SDK_KEY ?? '',
11+
)
12+
H.registerLD(ldClient)
13+
return ldClient
2314
}, [])
15+
useEffect(() => {
16+
;(async () => {
17+
const ldClient = await ldClientPromise
18+
await ldClient.identify({
19+
kind: 'multi',
20+
user: { key: 'bob' },
21+
org: { key: 'MacDonwalds' },
22+
})
23+
})()
24+
}, [ldClientPromise])
2425

2526
return null
2627
}

0 commit comments

Comments
 (0)