logfire client for zig
1//! otel-zig OTLP export test
2//!
3//! tests that otel-zig's OTLP exporter works with logfire endpoint.
4//!
5//! run with:
6//! LOGFIRE_TOKEN=your_token zig build otel-test
7//!
8//! this validates phase 1 of the otel-zig adoption plan.
9
10const std = @import("std");
11const otel = @import("otel");
12const otel_api = otel.api;
13const otel_sdk = otel.sdk;
14const otel_exporters = otel.exporters;
15
16pub fn main() !void {
17 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
18 defer _ = gpa.deinit();
19 const allocator = gpa.allocator();
20
21 // get logfire token and endpoint from env
22 const token = std.posix.getenv("LOGFIRE_WRITE_TOKEN") orelse
23 std.posix.getenv("LOGFIRE_TOKEN") orelse {
24 std.debug.print("LOGFIRE_TOKEN not set, skipping OTLP test\n", .{});
25 return;
26 };
27
28 // determine endpoint from token region
29 const endpoint = getEndpointFromToken(token);
30 std.debug.print("using endpoint: {s}\n", .{endpoint});
31
32 // build authorization header
33 var auth_header_buf: [256]u8 = undefined;
34 const auth_value = std.fmt.bufPrint(&auth_header_buf, "Bearer {s}", .{token}) catch {
35 std.debug.print("token too long\n", .{});
36 return;
37 };
38
39 // configure OTLP exporter for logfire
40 const otlp_config = otel_exporters.otlp.OtlpExporterConfig{
41 .endpoint = endpoint,
42 .transport = .http_protobuf,
43 .headers = &[_]std.http.Header{
44 .{ .name = "Authorization", .value = auth_value },
45 },
46 };
47
48 // set up trace provider with OTLP exporter
49 const provider = try otel_sdk.trace.setupGlobalProvider(
50 allocator,
51 .{otel_sdk.trace.BasicSpanProcessor.PipelineStep.init({})
52 .flowTo(otel_exporters.otlp.OtlpTraceExporter.PipelineStep.init(otlp_config))},
53 );
54 defer {
55 provider.deinit();
56 provider.destroy();
57 }
58 defer otel_api.provider_registry.unsetAllProviders();
59
60 // get tracer
61 const scope = otel_api.InstrumentationScope{
62 .name = "logfire-zig-otel-test",
63 .version = "0.1.0",
64 };
65 var tracer = try otel_api.getGlobalTracerProvider().getTracerWithScope(scope);
66
67 // create a test span
68 const ctx = &[_]otel_api.ContextKeyValue{};
69 var span_result = try tracer.startSpan("otel-zig-test-span", .{
70 .kind = .internal,
71 .attributes = &[_]otel_api.common.AttributeKeyValue{
72 .{ .key = "test.source", .value = .{ .string = "logfire-zig" } },
73 .{ .key = "test.type", .value = .{ .string = "otel-adoption-validation" } },
74 },
75 }, ctx);
76
77 // simulate some work
78 std.posix.nanosleep(0, 50 * std.time.ns_per_ms);
79
80 // end span (triggers export)
81 span_result.end(null);
82 span_result.deinit();
83
84 std.debug.print("span exported successfully!\n", .{});
85 std.debug.print("check logfire dashboard for 'otel-zig-test-span'\n", .{});
86}
87
88/// extract region from logfire token and return base OTLP endpoint
89/// note: otel-zig appends /v1/traces path automatically
90fn getEndpointFromToken(token: []const u8) []const u8 {
91 // token format: pylf_v{version}_{region}_{token}
92 if (std.mem.startsWith(u8, token, "pylf_v")) {
93 var it = std.mem.splitScalar(u8, token, '_');
94 _ = it.next(); // pylf
95 _ = it.next(); // v1
96 if (it.next()) |region| {
97 if (std.mem.eql(u8, region, "eu")) {
98 return "https://logfire-eu.pydantic.dev:443";
99 }
100 }
101 }
102 return "https://logfire-us.pydantic.dev:443";
103}