Skip to content
This repository was archived by the owner on Aug 5, 2024. It is now read-only.

Commit dce6633

Browse files
pelikhanmmoskal
andauthored
expansion of exception stack trace (#10)
* storage of sha * parse exceptions --------- Co-authored-by: Michal Moskal <michal@moskal.me>
1 parent c9046cc commit dce6633

File tree

7 files changed

+151
-22
lines changed

7 files changed

+151
-22
lines changed

src/fwdsock.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ export async function fwdSockInitRoute(
145145
break
146146
}
147147
case "exn": {
148-
// TODO PARSE MESSAGE
149148
const m = msg as ExnFromDevice
150149
conn.socket.send(
151150
JSON.stringify({
152151
type: "exception",
153152
deviceId: m.deviceId,
154-
message: m.logs[0],
155-
name: "Error",
156-
stack: m.logs.slice(1),
153+
message: msg.exn.message,
154+
name: msg.exn.name,
155+
stack: msg.exn.stack,
156+
logs: msg.logs.join("\n"),
157157
} as SideExceptionFromDevice)
158158
)
159159
break

src/interop.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export {
1313
SideUploadJsonFromDevice,
1414
SideExceptionFromDevice,
1515
} from "../devicescript/interop/src/interop"
16+
17+
export { parseStackFrame } from "../devicescript/interop/src/debug"

src/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export interface FrameFromDevice extends DeviceMessage {
8484

8585
export interface ExnFromDevice extends DeviceMessage {
8686
type: "exn"
87+
exn: Error
8788
logs: string[]
8889
}
8990

src/storage.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import { DeviceId, DeviceInfo, DeviceStats, zeroDeviceStats } from "./schema"
1414
import { delay, throwStatus } from "./util"
1515
import { createSecretClient } from "./vault"
1616
import { DebugInfo } from "./interop"
17+
import { createHash } from "crypto"
1718

1819
const suff = "4"
1920

2021
let devicesTable: TableClient
2122
let messageHooksTable: TableClient
2223
let scriptsTable: TableClient
2324
let scriptVersionsTable: TableClient
25+
let scriptVersionShaTable: TableClient
2426
let blobClient: BlobServiceClient
2527
let scriptsBlobs: ContainerClient
2628

@@ -71,6 +73,10 @@ export async function setup() {
7173
connStr,
7274
"scrver" + suff
7375
)
76+
scriptVersionShaTable = TableClient.fromConnectionString(
77+
connStr,
78+
"scrversha" + suff
79+
)
7480

7581
blobClient = BlobServiceClient.fromConnectionString(connStr)
7682
scriptsBlobs = blobClient.getContainerClient("scripts" + suff)
@@ -79,6 +85,7 @@ export async function setup() {
7985
await messageHooksTable.createTable()
8086
await scriptsTable.createTable()
8187
await scriptVersionsTable.createTable()
88+
await scriptVersionShaTable.createTable()
8289
await scriptsBlobs.createIfNotExists()
8390

8491
if (false) {
@@ -269,6 +276,7 @@ interface ScriptInfo {
269276
name: string
270277
metaJSON: string
271278
updated: number
279+
sha?: string
272280
}
273281

274282
export interface ScriptHeader {
@@ -331,6 +339,53 @@ export async function getScript(
331339
)
332340
}
333341

342+
function binarySha(hex: string) {
343+
if (!hex) return undefined
344+
const s = createHash("sha256")
345+
s.update(Buffer.from(hex, "hex"))
346+
const sha = s.digest("hex")
347+
return sha
348+
}
349+
350+
export async function resolveScriptBodyFromSha(
351+
partId: string,
352+
sha: string
353+
): Promise<ScriptBody> {
354+
if (!sha) return undefined
355+
356+
// check sha format, roundtrip throug buffer parser
357+
sha = Buffer.from(sha, "hex").toString("hex")
358+
359+
const res = await scriptVersionShaTable.getEntity(partId, sha)
360+
if (!res) return undefined
361+
362+
const scriptId = res.scriptId as string
363+
const scriptVersion = res.scriptVersion as number
364+
const body = await getScriptBody(scriptId, scriptVersion)
365+
body.program.binarySHA256 = sha
366+
return body
367+
}
368+
369+
async function upsertScriptVersionShaSnapshot(
370+
partitionKey: string,
371+
scriptId: string,
372+
scriptVersion: number,
373+
sha: string
374+
) {
375+
// check sha format, roundtrip throug buffer parser
376+
sha = Buffer.from(sha, "hex").toString("hex")
377+
// TODO: avoid sha collisions
378+
await scriptVersionShaTable.upsertEntity(
379+
{
380+
partitionKey,
381+
rowKey: sha,
382+
scriptId,
383+
scriptVersion,
384+
},
385+
"Replace"
386+
)
387+
}
388+
334389
function versionKey(version: number) {
335390
if (!version) throw new Error("bad version")
336391
return 1e6 - version + ""
@@ -432,7 +487,17 @@ export async function updateScript(
432487
}
433488

434489
const body = updates.body || (await getScriptBody(scr.id, scr.version))
490+
info.sha = binarySha(body?.program?.binary?.hex)
435491
await createScriptSnapshot(scr.id, newVersion, body)
492+
if (info.sha) {
493+
// only create index entry after creating blob
494+
await upsertScriptVersionShaSnapshot(
495+
scr.partition,
496+
scr.id,
497+
newVersion,
498+
info.sha
499+
)
500+
}
436501

437502
if (newVersion == 1) await scriptsTable.createEntity(info)
438503
else await scriptsTable.updateEntity(info, "Merge")

src/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function delay(ms: number) {
5858
}
5959

6060
export function runInBg(log: FastifyBaseLogger, lbl: string, p: Promise<any>) {
61-
log.debug(`bg ${lbl}`)
61+
// log.debug(`bg ${lbl}`)
6262
p.then(
6363
_ => {},
6464
err => {

src/wssk.ts

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ import {
2525
TelemetryType,
2626
TraceTelemetry,
2727
} from "applicationinsights/out/Declarations/Contracts"
28-
import { WsskCmd, WsskDataType, WsskStreamingType } from "./interop"
28+
import {
29+
DebugInfo,
30+
WsskCmd,
31+
WsskDataType,
32+
WsskStreamingType,
33+
parseStackFrame,
34+
} from "./interop"
2935
import { ingestLogs, ingestMessage } from "./messages"
3036

3137
const JD_AES_CCM_TAG_BYTES = 4
@@ -108,6 +114,8 @@ export class ConnectedDevice {
108114
private deployCmd: WsskCmd = 0
109115
streamingMask = WsskStreamingType.DefaultMask
110116

117+
private lastDbgInfo: DebugInfo
118+
111119
private lastExnBuffer: Buffer
112120
private exnBuffer: Buffer
113121
private exnLines: string[]
@@ -509,6 +517,63 @@ export class ConnectedDevice {
509517
}
510518
}
511519

520+
private async parseException(logs: string[]) {
521+
let sha = ""
522+
let startIdx = 0
523+
let exnName = ""
524+
let idx = 0
525+
for (const l of logs) {
526+
let m = /DevS-SHA256:\s+([a-f0-9]{64})/.exec(l)
527+
if (m) sha = m[1].toLowerCase()
528+
m = /^\* Exception:\s*(\S+)/.exec(l)
529+
if (m) {
530+
startIdx = idx
531+
exnName = m[1]
532+
}
533+
idx++
534+
}
535+
536+
if (sha) {
537+
if (this.lastDbgInfo?.binarySHA256 !== sha) {
538+
this.lastDbgInfo = null
539+
try {
540+
const body = await storage.resolveScriptBodyFromSha(
541+
this.dev.partitionKey,
542+
sha
543+
)
544+
this.lastDbgInfo = body?.program
545+
} catch {}
546+
}
547+
}
548+
549+
if (this.lastDbgInfo) {
550+
logs = logs.map(l =>
551+
parseStackFrame(this.lastDbgInfo, l).markedLine.replace(
552+
/\u001b\[[\d;]+m/g,
553+
""
554+
)
555+
)
556+
}
557+
558+
const stack = logs.slice(startIdx).filter(l => l.startsWith("*"))
559+
let message = ""
560+
for (const l of stack) {
561+
const m = /^\*\s+message: (.*)/.exec(l)
562+
if (m) message = m[1]
563+
}
564+
565+
const exn: Error = {
566+
name: exnName,
567+
message,
568+
stack: stack.join("\n"),
569+
}
570+
571+
return {
572+
exn,
573+
logs,
574+
}
575+
}
576+
512577
private async fastTick() {
513578
if (this.exnBuffer && this.lastExnBuffer === this.exnBuffer) {
514579
if (this.exnBuffer.length) {
@@ -517,11 +582,14 @@ export class ConnectedDevice {
517582
}
518583
this.exnBuffer = null
519584
if (this.exnLines?.length) {
520-
this.log.info(`exception: ${this.exnLines.join("\n")}`)
521-
this.traceException(this.exnLines, {})
585+
const { exn, logs } = await this.parseException(this.exnLines)
586+
this.log.info(`exception: ${exn.stack}`)
587+
// TODO send logs somehow
588+
this.traceException(exn, {})
522589
await this.notify({
523590
type: "exn",
524-
logs: this.exnLines,
591+
exn,
592+
logs,
525593
})
526594
this.exnLines = null
527595
}
@@ -604,23 +672,16 @@ export class ConnectedDevice {
604672
})
605673
}
606674

607-
public traceException(exn: string[], options: Partial<ExceptionTelemetry>) {
608-
console.log(`trace exception`)
609-
console.log(exn)
675+
public traceException(
676+
exception: Error,
677+
options: Partial<ExceptionTelemetry>
678+
) {
679+
console.log(`trace exception ${exception.name}(${exception.message})`)
610680
const { measurements = {}, ...rest } = options
611681
const deviceMeasurements: any = {}
612682
if (this.deployNumFail)
613683
deviceMeasurements.deployNumFail = this.deployNumFail
614684

615-
const name = "Error"
616-
const message = exn[0]
617-
const stack = exn.slice(1).join("\n")
618-
const exception: Error = {
619-
name,
620-
message,
621-
stack,
622-
}
623-
console.log(exception)
624685
this.track(
625686
{
626687
...rest,

0 commit comments

Comments
 (0)