@@ -20,6 +20,11 @@ pub const ConfigError = error{
20
20
InvalidProtocol ,
21
21
};
22
22
23
+ /// Error set for the OTLP Export operation.
24
+ pub const ExportError = error {
25
+ UnimplementedTransportProtocol ,
26
+ };
27
+
23
28
/// The combination of underlying transport protocol and format used to send the data.
24
29
pub const Protocol = enum {
25
30
// In order of precedence: SDK MUST support http/protobuf and SHOULD support grpc and http/json.
@@ -75,15 +80,60 @@ pub const Signal = enum {
75
80
// TODO add other signals when implemented
76
81
// profiles,
77
82
78
- fn defaulttHttpPath (self : Signal ) []const u8 {
83
+ const Self = @This ();
84
+
85
+ fn defaulttHttpPath (self : Self ) []const u8 {
79
86
switch (self ) {
80
87
.metrics = > return "/v1/metrics" ,
81
88
.logs = > return "/v1/logs" ,
82
89
.traces = > return "/v1/traces" ,
83
90
}
84
91
}
92
+
93
+ /// Actual signal data as protobuf messages.
94
+ pub const Data = union (Self ) {
95
+ metrics : pbmetrics.MetricsData ,
96
+ logs : pblogs.LogsData ,
97
+ traces : pbtrace.TracesData ,
98
+ // TODO add other signals when implemented
99
+ // profiles: profiles.ProfilesData,
100
+
101
+ fn toOwnedSlice (self : Data , allocator : std.mem.Allocator , protocol : Protocol ) ! []const u8 {
102
+ return switch (protocol ) {
103
+ .http_json = > {
104
+ switch (self ) {
105
+ // All protobuf-generated structs have a json_encode method.
106
+ inline else = > | data | return data .json_encode (.{}, allocator ),
107
+ }
108
+ },
109
+ .http_protobuf , .grpc = > {
110
+ switch (self ) {
111
+ // All protobuf-generated structs have a encode method.
112
+ inline else = > | data | return data .encode (allocator ),
113
+ }
114
+ },
115
+ };
116
+ }
117
+
118
+ fn signal (self : Data ) Signal {
119
+ return std .meta .activeTag (self );
120
+ }
121
+ };
85
122
};
86
123
124
+ test "otlp Signal.Data get payload bytes" {
125
+ const allocator = std .testing .allocator ;
126
+ var data = Signal.Data {
127
+ .metrics = pbmetrics.MetricsData {
128
+ .resource_metrics = std .ArrayList (pbmetrics .ResourceMetrics ).init (allocator ),
129
+ },
130
+ };
131
+ const payload = try data .toOwnedSlice (allocator , Protocol .http_protobuf );
132
+ defer allocator .free (payload );
133
+
134
+ try std .testing .expectEqual (payload .len , 0 );
135
+ }
136
+
87
137
/// Configuration options for the OTLP transport.
88
138
pub const ConfigOptions = struct {
89
139
allocator : std.mem.Allocator ,
@@ -293,6 +343,8 @@ const HTTPClient = struct {
293
343
config : ConfigOptions ,
294
344
// Default HTTP Client
295
345
client : http.Client ,
346
+ // Retries are processed using a separate thread.
347
+ // A priority queue is maintained in the ExpBackoffRetry struct.
296
348
retry : * ExpBackoffRetry ,
297
349
298
350
pub fn init (allocator : std.mem.Allocator , config : ConfigOptions ) ! * Self {
@@ -339,9 +391,10 @@ const HTTPClient = struct {
339
391
return request_options ;
340
392
}
341
393
342
- /// For the Signal type, send the data to the OTLP endpoint using the client's configuration.
343
- /// Data passed as argument should either be protobuf or JSON encoded, as specified in the config.
344
- pub fn send (self : * Self , signal : Signal , data : []u8 ) ! void {
394
+ // Send the OTLP data to the url using the client's configuration.
395
+ // Data passed as argument should either be protobuf or JSON encoded, as specified in the config.
396
+ // Data will be compressed here.
397
+ fn send (self : * Self , url : []const u8 , data : []u8 ) ! void {
345
398
var resp_body = std .ArrayList (u8 ).init (self .allocator );
346
399
defer resp_body .deinit ();
347
400
@@ -363,9 +416,6 @@ const HTTPClient = struct {
363
416
};
364
417
defer self .allocator .free (req_body );
365
418
366
- const url = try self .config .httpUrlForSignal (signal , self .allocator );
367
- defer self .allocator .free (url );
368
-
369
419
const req_opts = try requestOptions (self .config );
370
420
371
421
const fetch_request = http.Client.FetchOptions {
@@ -654,7 +704,41 @@ test "otlp HTTPClient send fails for missing server" {
654
704
const client = try HTTPClient .init (allocator , config .* );
655
705
defer client .deinit ();
656
706
707
+ const url = try config .httpUrlForSignal (.metrics , allocator );
708
+ defer allocator .free (url );
709
+
657
710
var payload = [_ ]u8 {0 } ** 1024 ;
658
- const result = client .send (Signal . metrics , & payload );
711
+ const result = client .send (url , & payload );
659
712
try std .testing .expectError (std .posix .ConnectError .ConnectionRefused , result );
660
713
}
714
+
715
+ const pbmetrics = @import ("opentelemetry/proto/metrics/v1.pb.zig" );
716
+ const pblogs = @import ("opentelemetry/proto/logs/v1.pb.zig" );
717
+ const pbtrace = @import ("opentelemetry/proto/trace/v1.pb.zig" );
718
+
719
+ /// Export the data to the OTLP endpoint using the options configured with ConfigOptions.
720
+ pub fn Export (
721
+ allocator : std.mem.Allocator ,
722
+ config : ConfigOptions ,
723
+ otlp_payload : Signal.Data ,
724
+ ) ! void {
725
+ // Determine the type of client to be used, currently only HTTP is supported.
726
+ const client = switch (config .protocol ) {
727
+ .http_json , .http_protobuf = > try HTTPClient .init (allocator , config ),
728
+ .grpc = > return ExportError .UnimplementedTransportProtocol ,
729
+ };
730
+
731
+ const payload = otlp_payload .toOwnedSlice (allocator , config .protocol ) catch | err | {
732
+ std .debug .print ("OTLP transport: failed to encode payload via {s}: {?}\n " , .{ @tagName (config .protocol ), err });
733
+ return err ;
734
+ };
735
+ defer allocator .free (payload );
736
+
737
+ const url = try config .httpUrlForSignal (otlp_payload .signal (), allocator );
738
+ defer allocator .free (url );
739
+
740
+ client .send (url , payload ) catch | err | {
741
+ std .debug .print ("OTLP transport: failed to send payload: {?}\n " , .{err });
742
+ return err ;
743
+ };
744
+ }
0 commit comments