22/**
33 * Manual tests to inspect tracing output
44 */
5+ import * as http2 from 'http2' ;
56import { SpanStatusCode } from '@opentelemetry/api' ;
67import { ExportResultCode } from '@opentelemetry/core' ;
78import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc' ;
@@ -17,11 +18,98 @@ import {
1718} from '@temporalio/interceptors-opentelemetry/lib/worker' ;
1819import { OpenTelemetrySinks , SpanName , SPAN_DELIMITER } from '@temporalio/interceptors-opentelemetry/lib/workflow' ;
1920import { DefaultLogger , InjectedSinks , Runtime } from '@temporalio/worker' ;
21+ import { TestWorkflowEnvironment } from '@temporalio/testing' ;
2022import * as activities from './activities' ;
2123import { ConnectionInjectorInterceptor } from './activities/interceptors' ;
2224import { RUN_INTEGRATION_TESTS , Worker } from './helpers' ;
2325import * as workflows from './workflows' ;
2426
27+ async function withHttp2Server (
28+ fn : ( port : number ) => Promise < void > ,
29+ requestListener ?: ( request : http2 . Http2ServerRequest , response : http2 . Http2ServerResponse ) => void
30+ ) : Promise < void > {
31+ return new Promise < void > ( ( resolve , reject ) => {
32+ const srv = http2 . createServer ( ) ;
33+ srv . listen ( { port : 0 , host : '127.0.0.1' } , ( ) => {
34+ const addr = srv . address ( ) ;
35+ if ( typeof addr === 'string' || addr === null ) {
36+ throw new Error ( 'Unexpected server address type' ) ;
37+ }
38+ srv . on ( 'request' , async ( req , res ) => {
39+ if ( requestListener ) await requestListener ( req , res ) ;
40+ res . end ( ) ;
41+ } ) ;
42+ fn ( addr . port )
43+ . catch ( ( e ) => reject ( e ) )
44+ . finally ( ( ) => srv . close ( ( _ ) => resolve ( ) ) ) ;
45+ } ) ;
46+ } ) ;
47+ }
48+
49+ test . serial ( 'Runtime.install() throws meaningful error when passed invalid metrics.otel.url' , async ( t ) => {
50+ t . throws ( ( ) => Runtime . install ( { telemetryOptions : { metrics : { otel : { url : ':invalid' } } } } ) , {
51+ instanceOf : TypeError ,
52+ message : / I n v a l i d t e l e m e t r y O p t i o n s .m e t r i c s .o t e l .u r l / ,
53+ } ) ;
54+ } ) ;
55+
56+ test . serial ( 'Runtime.install() accepts metrics.otel.url without headers' , async ( t ) => {
57+ try {
58+ Runtime . install ( { telemetryOptions : { metrics : { otel : { url : 'http://127.0.0.1:1234' } } } } ) ;
59+ t . pass ( ) ;
60+ } finally {
61+ // Cleanup the runtime so that it doesn't interfere with other tests
62+ await Runtime . _instance ?. shutdown ( ) ;
63+ }
64+ } ) ;
65+
66+ test . serial ( 'Exporting OTEL metrics from Core works' , async ( t ) => {
67+ let resolveCapturedRequest = ( _req : http2 . Http2ServerRequest ) => undefined as void ;
68+ const capturedRequest = new Promise < http2 . Http2ServerRequest > ( ( r ) => ( resolveCapturedRequest = r ) ) ;
69+ await withHttp2Server ( async ( port : number ) => {
70+ Runtime . install ( {
71+ telemetryOptions : {
72+ metrics : {
73+ otel : {
74+ url : `http://127.0.0.1:${ port } ` ,
75+ headers : {
76+ 'x-test-header' : 'test-value' ,
77+ } ,
78+ metricsExportInterval : 10 ,
79+ } ,
80+ } ,
81+ } ,
82+ } ) ;
83+
84+ const localEnv = await TestWorkflowEnvironment . createLocal ( ) ;
85+ try {
86+ const worker = await Worker . create ( {
87+ connection : localEnv . nativeConnection ,
88+ workflowsPath : require . resolve ( './workflows' ) ,
89+ taskQueue : 'test-otel' ,
90+ } ) ;
91+ const client = new WorkflowClient ( {
92+ connection : localEnv . connection ,
93+ } ) ;
94+ await worker . runUntil ( async ( ) => {
95+ await client . execute ( workflows . successString , {
96+ taskQueue : 'test-otel' ,
97+ workflowId : uuid4 ( ) ,
98+ } ) ;
99+ const req = await Promise . race ( [
100+ capturedRequest ,
101+ await new Promise < undefined > ( ( resolve ) => setTimeout ( ( ) => resolve ( undefined ) , 2000 ) ) ,
102+ ] ) ;
103+ t . truthy ( req ) ;
104+ t . is ( req ?. url , '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export' ) ;
105+ t . is ( req ?. headers [ 'x-test-header' ] , 'test-value' ) ;
106+ } ) ;
107+ } finally {
108+ await localEnv . teardown ( ) ;
109+ }
110+ } , resolveCapturedRequest ) ;
111+ } ) ;
112+
25113if ( RUN_INTEGRATION_TESTS ) {
26114 test . serial ( 'Otel interceptor spans are connected and complete' , async ( t ) => {
27115 const spans = Array < opentelemetry . tracing . ReadableSpan > ( ) ;
@@ -178,7 +266,7 @@ if (RUN_INTEGRATION_TESTS) {
178266 } ) ;
179267 await worker . runUntil ( client . execute ( workflows . smorgasbord , { taskQueue : 'test-otel' , workflowId : uuid4 ( ) } ) ) ;
180268 // Allow some time to ensure spans are flushed out to collector
181- await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
269+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
182270 t . pass ( ) ;
183271 } ) ;
184272
0 commit comments