logfire client for zig
at c7a46726fe7ff8bb65e4e72c985aa146fbf6d404 177 lines 5.9 kB view raw
1//! OTLP attribute types 2//! 3//! converts zig values to OTLP-compatible attribute format. 4//! string values are copied into internal storage for memory safety. 5 6const std = @import("std"); 7 8pub const Attribute = struct { 9 key: []const u8, 10 value: Value, 11 /// internal storage for copied strings 12 _string_storage: [max_string_len]u8 = undefined, 13 _string_len: usize = 0, 14 15 pub const max_string_len = 512; 16 17 pub const Value = union(enum) { 18 string: []const u8, 19 int: i64, 20 float: f64, 21 bool_val: bool, 22 }; 23 24 /// copy a string into internal storage and return slice pointing to it 25 fn copyString(self: *Attribute, str: []const u8) []const u8 { 26 const len = @min(str.len, max_string_len); 27 @memcpy(self._string_storage[0..len], str[0..len]); 28 self._string_len = len; 29 return self._string_storage[0..len]; 30 } 31 32 /// convert a comptime struct to attributes array 33 /// returns number of attributes written 34 /// string values are copied for memory safety 35 pub fn fromStruct(attrs: anytype, out: []Attribute) usize { 36 const T = @TypeOf(attrs); 37 const info = @typeInfo(T); 38 39 if (info != .@"struct") return 0; 40 41 const fields = info.@"struct".fields; 42 var count: usize = 0; 43 44 inline for (fields) |field| { 45 if (count >= out.len) break; 46 47 const field_value = @field(attrs, field.name); 48 if (toValueWithCopy(&out[count], field.name, field_value)) { 49 count += 1; 50 } 51 } 52 53 return count; 54 } 55 56 /// convert value and copy strings into attribute's internal storage 57 fn toValueWithCopy(attr: *Attribute, key: []const u8, value: anytype) bool { 58 const T = @TypeOf(value); 59 const info = @typeInfo(T); 60 61 switch (info) { 62 .int, .comptime_int => { 63 attr.* = .{ .key = key, .value = .{ .int = @intCast(value) } }; 64 return true; 65 }, 66 .float, .comptime_float => { 67 attr.* = .{ .key = key, .value = .{ .float = @floatCast(value) } }; 68 return true; 69 }, 70 .bool => { 71 attr.* = .{ .key = key, .value = .{ .bool_val = value } }; 72 return true; 73 }, 74 .pointer => |ptr| { 75 if (ptr.size == .slice and ptr.child == u8) { 76 // copy the string into internal storage 77 attr.* = .{ .key = key, .value = undefined }; 78 const copied = attr.copyString(value); 79 attr.value = .{ .string = copied }; 80 return true; 81 } 82 if (ptr.size == .one) { 83 const child_info = @typeInfo(ptr.child); 84 if (child_info == .array and child_info.array.child == u8) { 85 attr.* = .{ .key = key, .value = undefined }; 86 const copied = attr.copyString(value); 87 attr.value = .{ .string = copied }; 88 return true; 89 } 90 } 91 return false; 92 }, 93 .array => |arr| { 94 if (arr.child == u8) { 95 attr.* = .{ .key = key, .value = undefined }; 96 const copied = attr.copyString(&value); 97 attr.value = .{ .string = copied }; 98 return true; 99 } 100 return false; 101 }, 102 .optional => { 103 if (value) |v| { 104 return toValueWithCopy(attr, key, v); 105 } 106 return false; 107 }, 108 else => return false, 109 } 110 } 111 112 /// write attribute as OTLP JSON 113 pub fn writeJson(self: Attribute, w: anytype) !void { 114 try w.writeAll("{\"key\":"); 115 try writeJsonString(w, self.key); 116 try w.writeAll(",\"value\":{"); 117 118 switch (self.value) { 119 .string => |s| { 120 try w.writeAll("\"stringValue\":"); 121 try writeJsonString(w, s); 122 }, 123 .int => |i| { 124 try w.print("\"intValue\":\"{d}\"", .{i}); 125 }, 126 .float => |f| { 127 try w.print("\"doubleValue\":{d}", .{f}); 128 }, 129 .bool_val => |b| { 130 try w.print("\"boolValue\":{}", .{b}); 131 }, 132 } 133 134 try w.writeAll("}}"); 135 } 136}; 137 138fn writeJsonString(w: anytype, s: []const u8) !void { 139 try w.writeByte('"'); 140 for (s) |c| { 141 switch (c) { 142 '"' => try w.writeAll("\\\""), 143 '\\' => try w.writeAll("\\\\"), 144 '\n' => try w.writeAll("\\n"), 145 '\r' => try w.writeAll("\\r"), 146 '\t' => try w.writeAll("\\t"), 147 0x00...0x08, 0x0b, 0x0c, 0x0e...0x1f => try w.print("\\u00{x:0>2}", .{c}), 148 else => try w.writeByte(c), 149 } 150 } 151 try w.writeByte('"'); 152} 153 154// tests 155 156test "fromStruct basic" { 157 var attrs: [8]Attribute = undefined; 158 const count = Attribute.fromStruct(.{ 159 .name = "test", 160 .count = @as(i64, 42), 161 .enabled = true, 162 }, &attrs); 163 164 try std.testing.expectEqual(@as(usize, 3), count); 165 try std.testing.expectEqualStrings("name", attrs[0].key); 166 try std.testing.expectEqualStrings("test", attrs[0].value.string); 167 try std.testing.expectEqual(@as(i64, 42), attrs[1].value.int); 168 try std.testing.expectEqual(true, attrs[2].value.bool_val); 169} 170 171test "writeJson string" { 172 var buf: [256]u8 = undefined; 173 var fbs = std.io.fixedBufferStream(&buf); 174 const attr = Attribute{ .key = "foo", .value = .{ .string = "bar" } }; 175 try attr.writeJson(fbs.writer()); 176 try std.testing.expectEqualStrings("{\"key\":\"foo\",\"value\":{\"stringValue\":\"bar\"}}", fbs.getWritten()); 177}