1+ import { randomBytes } from "crypto" ;
12import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger" ;
2- import { buildOtlpTracePayload , type OtlpTraceOptions } from "../otlpPayload.js" ;
33
44export type OtlpTraceServiceOptions = {
55 endpointUrl : string ;
66 timeoutMs ?: number ;
77} ;
88
9+ export type OtlpTraceSpan = {
10+ traceId : string ;
11+ parentSpanId ?: string ;
12+ spanName : string ;
13+ startTimeMs : number ;
14+ endTimeMs : number ;
15+ resourceAttributes : Record < string , string | number | boolean > ;
16+ spanAttributes : Record < string , string | number | boolean > ;
17+ } ;
18+
919export class OtlpTraceService {
1020 private readonly logger = new SimpleStructuredLogger ( "otlp-trace" ) ;
1121
1222 constructor ( private opts : OtlpTraceServiceOptions ) { }
1323
1424 /** Fire-and-forget: build payload and send to the configured OTLP endpoint */
15- emit ( opts : OtlpTraceOptions ) : void {
16- const payload = buildOtlpTracePayload ( opts ) ;
25+ emit ( span : OtlpTraceSpan ) : void {
26+ const payload = buildPayload ( span ) ;
1727
1828 fetch ( `${ this . opts . endpointUrl } /v1/traces` , {
1929 method : "POST" ,
@@ -27,3 +37,57 @@ export class OtlpTraceService {
2737 } ) ;
2838 }
2939}
40+
41+ // ── Payload builder (internal) ───────────────────────────────────────────────
42+
43+ /** @internal Exported for tests only */
44+ export function buildPayload ( span : OtlpTraceSpan ) {
45+ const spanId = randomBytes ( 8 ) . toString ( "hex" ) ;
46+
47+ return {
48+ resourceSpans : [
49+ {
50+ resource : {
51+ attributes : [
52+ { key : "$trigger" , value : { boolValue : true } } ,
53+ ...toOtlpAttributes ( span . resourceAttributes ) ,
54+ ] ,
55+ } ,
56+ scopeSpans : [
57+ {
58+ scope : { name : "supervisor.compute" } ,
59+ spans : [
60+ {
61+ traceId : span . traceId ,
62+ spanId,
63+ parentSpanId : span . parentSpanId ,
64+ name : span . spanName ,
65+ kind : 3 , // SPAN_KIND_CLIENT
66+ startTimeUnixNano : String ( span . startTimeMs * 1_000_000 ) ,
67+ endTimeUnixNano : String ( span . endTimeMs * 1_000_000 ) ,
68+ attributes : toOtlpAttributes ( span . spanAttributes ) ,
69+ status : { code : 1 } , // STATUS_CODE_OK
70+ } ,
71+ ] ,
72+ } ,
73+ ] ,
74+ } ,
75+ ] ,
76+ } ;
77+ }
78+
79+ function toOtlpAttributes (
80+ attrs : Record < string , string | number | boolean >
81+ ) : Array < { key : string ; value : Record < string , unknown > } > {
82+ return Object . entries ( attrs ) . map ( ( [ key , value ] ) => ( {
83+ key,
84+ value : toOtlpValue ( value ) ,
85+ } ) ) ;
86+ }
87+
88+ function toOtlpValue ( value : string | number | boolean ) : Record < string , unknown > {
89+ if ( typeof value === "string" ) return { stringValue : value } ;
90+ if ( typeof value === "boolean" ) return { boolValue : value } ;
91+ if ( Number . isInteger ( value ) ) return { intValue : value } ;
92+ return { doubleValue : value } ;
93+ }
0 commit comments