logfire client for zig

fix: use raw mode for string attribute serialization

jw.write() wasn't reliably serializing strings from internal storage.
bypass it with manual JSON string writing using beginWriteRaw/endWriteRaw
to maintain Stringify state while writing escaped strings directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+26 -3
+26 -3
src/exporter.zig
··· 584 584 switch (value) { 585 585 .string => |s| { 586 586 try jw.objectField("stringValue"); 587 - try jw.write(s); 587 + try writeStringValue(jw, s); 588 588 }, 589 589 .int => |i| { 590 590 try jw.objectField("intValue"); ··· 612 612 switch (attr.value) { 613 613 .string => |s| { 614 614 try jw.objectField("stringValue"); 615 - // explicit cast needed for slices pointing to internal storage 616 - try jw.write(@as([]const u8, s)); 615 + try writeStringValue(jw, s); 617 616 }, 618 617 .int => |i| { 619 618 try jw.objectField("intValue"); ··· 652 651 var buf: [24]u8 = undefined; 653 652 const s = std.fmt.bufPrint(&buf, "{d}", .{val}) catch unreachable; 654 653 try jw.write(s); 654 + } 655 + 656 + /// write a string value with proper JSON escaping 657 + /// uses raw mode to maintain Stringify state while writing manually 658 + fn writeStringValue(jw: *json.Stringify, s: []const u8) !void { 659 + try jw.beginWriteRaw(); 660 + try jw.writer.writeByte('"'); 661 + for (s) |c| { 662 + switch (c) { 663 + '"' => try jw.writer.writeAll("\\\""), 664 + '\\' => try jw.writer.writeAll("\\\\"), 665 + '\n' => try jw.writer.writeAll("\\n"), 666 + '\r' => try jw.writer.writeAll("\\r"), 667 + '\t' => try jw.writer.writeAll("\\t"), 668 + 0x00...0x08, 0x0b, 0x0c, 0x0e...0x1f => { 669 + var buf: [6]u8 = undefined; 670 + _ = std.fmt.bufPrint(&buf, "\\u00{x:0>2}", .{c}) catch unreachable; 671 + try jw.writer.writeAll(&buf); 672 + }, 673 + else => try jw.writer.writeByte(c), 674 + } 675 + } 676 + try jw.writer.writeByte('"'); 677 + jw.endWriteRaw(); 655 678 } 656 679 657 680 // tests