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