0f725cf2ba
## Summary Enable small cleanup rules for redundant boolean expressions, object ownership checks, assignments, and object construction. The existing call sites now use the simpler equivalent forms, keeping future code consistent without changing behavior. Base: [#4672](https://github.com/triggerdotdev/trigger.dev/pull/4672)
182 lines
4.6 KiB
TypeScript
182 lines
4.6 KiB
TypeScript
import type { Attributes } from "@opentelemetry/api";
|
|
import { RandomIdGenerator } from "@opentelemetry/sdk-trace-base";
|
|
import { parseTraceparent } from "@trigger.dev/core/v3/isomorphic";
|
|
import type {
|
|
ExceptionEventProperties,
|
|
SpanEvents,
|
|
TaskRunError,
|
|
} from "@trigger.dev/core/v3/schemas";
|
|
import { unflattenAttributes } from "@trigger.dev/core/v3/utils/flattenAttributes";
|
|
import { createHash } from "node:crypto";
|
|
|
|
export function extractContextFromCarrier(carrier: Record<string, unknown>) {
|
|
const traceparent = carrier["traceparent"];
|
|
const tracestate = carrier["tracestate"];
|
|
|
|
if (typeof traceparent !== "string") {
|
|
return undefined;
|
|
}
|
|
|
|
return {
|
|
...carrier,
|
|
traceparent: parseTraceparent(traceparent),
|
|
tracestate,
|
|
};
|
|
}
|
|
|
|
export function getNowInNanoseconds(): bigint {
|
|
return BigInt(new Date().getTime() * 1_000_000);
|
|
}
|
|
|
|
export function getDateFromNanoseconds(nanoseconds: bigint): Date {
|
|
return new Date(Number(nanoseconds) / 1_000_000);
|
|
}
|
|
|
|
export function calculateDurationFromStart(
|
|
startTime: bigint,
|
|
endTime: Date = new Date(),
|
|
minimumDuration?: number
|
|
) {
|
|
const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime;
|
|
|
|
const duration = Number(BigInt($endtime.getTime() * 1_000_000) - startTime);
|
|
|
|
if (minimumDuration && duration < minimumDuration) {
|
|
return minimumDuration;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
export function calculateDurationFromStartJsDate(startTime: Date, endTime: Date = new Date()) {
|
|
const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime;
|
|
|
|
return ($endtime.getTime() - startTime.getTime()) * 1_000_000;
|
|
}
|
|
|
|
export function convertDateToNanoseconds(date: Date): bigint {
|
|
return BigInt(date.getTime()) * BigInt(1_000_000);
|
|
}
|
|
|
|
/**
|
|
* Returns a deterministically random 8-byte span ID formatted/encoded as a 16 lowercase hex
|
|
* characters corresponding to 64 bits, based on the trace ID and seed.
|
|
*/
|
|
export function generateDeterministicSpanId(traceId: string, seed: string) {
|
|
const hash = createHash("sha1");
|
|
hash.update(traceId);
|
|
hash.update(seed);
|
|
const buffer = hash.digest();
|
|
let hexString = "";
|
|
for (let i = 0; i < 8; i++) {
|
|
const val = buffer.readUInt8(i);
|
|
const str = val.toString(16).padStart(2, "0");
|
|
hexString += str;
|
|
}
|
|
return hexString;
|
|
}
|
|
|
|
const randomIdGenerator = new RandomIdGenerator();
|
|
|
|
export function generateTraceId() {
|
|
return randomIdGenerator.generateTraceId();
|
|
}
|
|
|
|
export function generateSpanId() {
|
|
return randomIdGenerator.generateSpanId();
|
|
}
|
|
|
|
export function stripAttributePrefix(attributes: Attributes, prefix: string) {
|
|
const result: Attributes = {};
|
|
|
|
for (const [key, value] of Object.entries(attributes)) {
|
|
if (key.startsWith(prefix)) {
|
|
result[key.slice(prefix.length + 1)] = value;
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseEventsField(events: unknown): SpanEvents {
|
|
if (!events) return [];
|
|
if (!Array.isArray(events)) return [];
|
|
|
|
const unsafe = events
|
|
? (events as any[]).map((e) => ({
|
|
...e,
|
|
properties: unflattenAttributes(e.properties as Attributes),
|
|
}))
|
|
: undefined;
|
|
|
|
return unsafe as SpanEvents;
|
|
}
|
|
|
|
export function createExceptionPropertiesFromError(error: TaskRunError): ExceptionEventProperties {
|
|
switch (error.type) {
|
|
case "BUILT_IN_ERROR": {
|
|
return {
|
|
type: error.name,
|
|
message: error.message,
|
|
stacktrace: error.stackTrace,
|
|
};
|
|
}
|
|
case "CUSTOM_ERROR": {
|
|
return {
|
|
type: "Error",
|
|
message: error.raw,
|
|
};
|
|
}
|
|
case "INTERNAL_ERROR": {
|
|
return {
|
|
type: "Internal error",
|
|
message: [error.code, error.message].filter(Boolean).join(": "),
|
|
stacktrace: error.stackTrace,
|
|
};
|
|
}
|
|
case "STRING_ERROR": {
|
|
return {
|
|
type: "Error",
|
|
message: error.raw,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes internal/private attribute keys from span properties.
|
|
// Filters: "$" prefixed keys (private metadata) and "ctx." prefixed keys (Trigger.dev run context)
|
|
export function removePrivateProperties(
|
|
attributes: Attributes | undefined | null
|
|
): Attributes | undefined {
|
|
if (!attributes) {
|
|
return undefined;
|
|
}
|
|
|
|
const result: Attributes = {};
|
|
|
|
for (const [key, value] of Object.entries(attributes)) {
|
|
if (key.startsWith("$") || key.startsWith("ctx.")) {
|
|
continue;
|
|
}
|
|
|
|
result[key] = value;
|
|
}
|
|
|
|
if (Object.keys(result).length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function isEmptyObject(obj: object) {
|
|
for (var prop in obj) {
|
|
if (Object.hasOwn(obj, prop)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|