1+ classdef OpenTelemetryPlugin < matlab .buildtool .plugins .BuildRunnerPlugin
2+
3+ % Copyright 2026 The MathWorks, Inc.
4+
5+ methods (Access = protected )
6+ function runBuild(plugin , pluginData )
7+ % Configure by attaching to span if passed in via environment
8+ % variable, and propagating baggage
9+ configureOTel();
10+
11+ tr = opentelemetry .trace .getTracer(" buildtool" );
12+ sp = tr .startSpan(" buildtool" );
13+ scope = makeCurrent(sp ); % #ok<NASGU>
14+
15+ % Run build
16+ runBuild@matlab.buildtool.plugins.BuildRunnerPlugin(plugin , pluginData );
17+
18+ % Update status
19+ if pluginData .BuildResult .Failed
20+ sp .setStatus(" Error" , " Build completed, results not successful" );
21+ else
22+ sp .setStatus(" Ok" );
23+ end
24+
25+ % Results-based attributes
26+ taskResults = pluginData .BuildResult .TaskResults ;
27+ successful = [taskResults([taskResults .Successful ]).Name];
28+ failed = [taskResults([taskResults .Failed ]).Name];
29+ skipped = [taskResults([taskResults .Skipped ]).Name];
30+
31+ sp .setAttributes( ...
32+ " buildtool.tasks" , numel(pluginData .BuildResult .TaskResults ), ...
33+ " buildtool.tasks.successful" , successful , ...
34+ " buildtool.tasks.failed" , failed , ...
35+ " buildtool.tasks.skipped" , skipped , ...
36+ " buildtool.build.successes" , numel(successful ), ...
37+ " buildtool.build.failures" , numel(failed ), ...
38+ " buildtool.build.skips" , numel(skipped ) ...
39+ );
40+
41+ % Update metrics
42+ meter = opentelemetry .metrics .getMeter(" buildtool" );
43+ successes = meter .createCounter(" buildtool.tasks.successful" );
44+ failures = meter .createCounter(" buildtool.tasks.failed" );
45+ skips = meter .createCounter(" buildtool.tasks.skipped" );
46+ buildSuccesses = meter .createCounter(" buildtool.build.successes" );
47+ buildFailures = meter .createCounter(" buildtool.build.failures" );
48+
49+ successes .add(numel(successful ));
50+ failures .add(numel(failed ));
51+ skips .add(numel(skipped ));
52+ buildSuccesses .add(double(~pluginData .BuildResult .Failed ));
53+ buildFailures .add(double(pluginData .BuildResult .Failed ));
54+
55+ cleanupOTel(sp );
56+ end
57+
58+ function runTask(plugin , pluginData )
59+ % TODO:
60+ % - buildtool.task.outputs
61+ % - buildtool.task.inputs
62+
63+ % Definitions
64+ task = pluginData .TaskGraph .Tasks ;
65+ taskName = pluginData .Name ;
66+ taskDescription = task .Description ;
67+
68+ % Attributes
69+ otelAttributes = dictionary( ...
70+ [ ...
71+ " buildtool.task.name" , ...
72+ " buildtool.task.description" , ...
73+ ], ...
74+ [ ...
75+ taskName , ...
76+ taskDescription ...
77+ ] ...
78+ );
79+
80+ tr = opentelemetry .trace .getTracer(taskName );
81+ sp = tr .startSpan(taskName , Attributes= otelAttributes );
82+ scope = makeCurrent(sp ); % #ok<NASGU>
83+
84+ % Run task
85+ runTask@matlab.buildtool.plugins.BuildRunnerPlugin(plugin , pluginData );
86+
87+ % Set results-based attributes
88+ resultAttributes = dictionary( ...
89+ [ ...
90+ " buildtool.task.successful" , ...
91+ " buildtool.task.failed" , ...
92+ " buildtool.task.skipped" ...
93+ ], ...
94+ [ ...
95+ pluginData .TaskResults .Successful , ...
96+ pluginData .TaskResults .Failed , ...
97+ pluginData .TaskResults .Skipped ...
98+ ] ...
99+ );
100+ sp .setAttributes(resultAttributes );
101+
102+ % Update span status
103+ if pluginData .TaskResults .Successful
104+ sp .setStatus(" Ok" );
105+ else
106+ sp .setStatus(" Error" , " Task completed, results not successful" );
107+ end
108+
109+ sp .endSpan();
110+ end
111+ end
112+ end
113+
114+ % Use the same configuration as PADV
115+ function extcontextscope = configureOTel()
116+
117+ % Skip configuration if NO_MBT_OTEL_CONFIG set
118+ if (getenv(" NO_MBT_OTEL_CONFIG" ))
119+ return ;
120+ end
121+
122+ % populate resource attributes
123+ otelservicename = " buildtool" ;
124+ otelresource = dictionary(" service.name" , otelservicename );
125+
126+ % baggage propagation
127+ otelbaggage = getenv(" BAGGAGE" );
128+ if ~isempty(otelbaggage )
129+ otelbaggage = split(split(string(otelbaggage ),' ,' ), " =" );
130+ otelresource = insert(otelresource , otelbaggage(: ,1 ), otelbaggage(: ,2 ));
131+ end
132+
133+ % check for passed in external context
134+ extcontextscope = [];
135+ traceid = getenv(" TRACE_ID" );
136+ spanid = getenv(" SPAN_ID" );
137+ if ~isempty(traceid ) && ~isempty(spanid )
138+ spcontext = opentelemetry .trace .SpanContext(traceid , spanid );
139+ extcontextscope = makeCurrent(spcontext );
140+ end
141+
142+ % tracer provider
143+ otelspexp = opentelemetry .exporters .otlp .OtlpGrpcSpanExporter ; % use gRPC because Otel plugin for Jenkins only use gRPC
144+ otelspproc = opentelemetry .sdk .trace .BatchSpanProcessor(otelspexp );
145+ oteltp = opentelemetry .sdk .trace .TracerProvider(otelspproc , Resource= otelresource );
146+ setTracerProvider(oteltp );
147+
148+ % meter provider
149+ otelmexp = opentelemetry .exporters .otlp .OtlpGrpcMetricExporter ; % use gRPC because Otel plugin for Jenkins only use gRPC
150+ otelmread = opentelemetry .sdk .metrics .PeriodicExportingMetricReader(otelmexp );
151+ otelmp = opentelemetry .sdk .metrics .MeterProvider(otelmread , Resource= otelresource );
152+ setMeterProvider(otelmp );
153+
154+ % logger provider
155+ otellgexp = opentelemetry .exporters .otlp .OtlpGrpcLogRecordExporter ; % use gRPC because Otel plugin for Jenkins only use gRPC
156+ otellgproc = opentelemetry .sdk .logs .BatchLogRecordProcessor(otellgexp );
157+ otellp = opentelemetry .sdk .logs .LoggerProvider(otellgproc , Resource= otelresource );
158+ setLoggerProvider(otellp );
159+ end
160+
161+ % Use the same cleanup as PADV
162+ function cleanupOTel(span )
163+
164+ % Skip cleanup if NO_MBT_OTEL_CONFIG set
165+ if (getenv(" NO_MBT_OTEL_CONFIG" ))
166+ return ;
167+ end
168+
169+ timeout = 5 ;
170+
171+ % end the input span before cleaning up
172+ if nargin > 0
173+ endSpan(span );
174+ end
175+
176+ % tracer provider
177+ oteltp = opentelemetry .trace .Provider .getTracerProvider ;
178+ opentelemetry .sdk .common .Cleanup .forceFlush(oteltp , timeout );
179+ opentelemetry .sdk .common .Cleanup .shutdown(oteltp );
180+
181+ % meter provider
182+ otelmp = opentelemetry .metrics .Provider .getMeterProvider ;
183+ opentelemetry .sdk .common .Cleanup .forceFlush(otelmp , timeout );
184+ opentelemetry .sdk .common .Cleanup .shutdown(otelmp );
185+
186+ % logger provider
187+ otellp = opentelemetry .logs .Provider .getLoggerProvider ;
188+ opentelemetry .sdk .common .Cleanup .forceFlush(otellp , timeout );
189+ opentelemetry .sdk .common .Cleanup .shutdown(otellp );
190+ end
0 commit comments