logfire client for zig
at main 170 lines 5.0 kB view raw
1//! logfire configuration 2//! 3//! mirrors the rust client's builder pattern. 4//! supports both explicit config and environment variables. 5 6const std = @import("std"); 7 8pub const Config = struct { 9 /// service name (required for logfire) 10 /// env: LOGFIRE_SERVICE_NAME, OTEL_SERVICE_NAME 11 service_name: ?[]const u8 = null, 12 13 /// service version 14 /// env: LOGFIRE_SERVICE_VERSION, OTEL_SERVICE_VERSION 15 service_version: ?[]const u8 = null, 16 17 /// deployment environment (e.g., "production", "staging") 18 /// env: LOGFIRE_ENVIRONMENT 19 environment: ?[]const u8 = null, 20 21 /// logfire/OTLP write token 22 /// env: LOGFIRE_TOKEN 23 token: ?[]const u8 = null, 24 25 /// base URL for logfire API 26 /// env: OTEL_EXPORTER_OTLP_ENDPOINT 27 /// defaults based on token region (us/eu) 28 base_url: ?[]const u8 = null, 29 30 /// whether to send to logfire 31 /// if false, just prints to console 32 send_to_logfire: SendToLogfire = .if_token_present, 33 34 /// minimum log level to capture 35 default_level: Level = .info, 36 37 /// console output options 38 console: ?ConsoleOptions = .{}, 39 40 /// batch export settings 41 batch_size: usize = 512, 42 batch_timeout_ms: u64 = 500, 43 44 pub const SendToLogfire = enum { 45 yes, 46 no, 47 if_token_present, 48 }; 49 50 pub const Level = enum { 51 trace, 52 debug, 53 info, 54 warn, 55 err, 56 57 pub fn severity(self: Level) u8 { 58 return switch (self) { 59 .trace => 1, 60 .debug => 5, 61 .info => 9, 62 .warn => 13, 63 .err => 17, 64 }; 65 } 66 67 pub fn name(self: Level) []const u8 { 68 return @tagName(self); 69 } 70 }; 71 72 pub const ConsoleOptions = struct { 73 enabled: bool = true, 74 colors: bool = true, 75 min_level: Level = .info, 76 }; 77 78 /// resolve config from explicit values + environment 79 pub fn resolve(self: Config) Config { 80 var resolved = self; 81 82 // token 83 if (resolved.token == null) { 84 resolved.token = std.posix.getenv("LOGFIRE_WRITE_TOKEN") orelse 85 std.posix.getenv("LOGFIRE_TOKEN"); 86 } 87 88 // service name 89 if (resolved.service_name == null) { 90 resolved.service_name = std.posix.getenv("LOGFIRE_SERVICE_NAME") orelse 91 std.posix.getenv("OTEL_SERVICE_NAME"); 92 } 93 94 // service version 95 if (resolved.service_version == null) { 96 resolved.service_version = std.posix.getenv("LOGFIRE_SERVICE_VERSION") orelse 97 std.posix.getenv("OTEL_SERVICE_VERSION"); 98 } 99 100 // environment 101 if (resolved.environment == null) { 102 resolved.environment = std.posix.getenv("LOGFIRE_ENVIRONMENT"); 103 } 104 105 // base URL - derive from token region if not explicit 106 if (resolved.base_url == null) { 107 if (std.posix.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")) |endpoint| { 108 resolved.base_url = endpoint; 109 } else if (resolved.token) |token| { 110 resolved.base_url = getBaseUrlFromToken(token); 111 } 112 } 113 114 return resolved; 115 } 116 117 /// determine if we should actually send data 118 pub fn shouldSend(self: Config) bool { 119 return switch (self.send_to_logfire) { 120 .yes => true, 121 .no => false, 122 .if_token_present => self.token != null, 123 }; 124 } 125}; 126 127/// extract region from logfire token and return appropriate endpoint 128/// token format: pylf_v{version}_{region}_{token} 129fn getBaseUrlFromToken(token: []const u8) []const u8 { 130 // pylf_v1_eu_xxxxx -> eu 131 // pylf_v1_us_xxxxx -> us 132 if (std.mem.startsWith(u8, token, "pylf_v")) { 133 var it = std.mem.splitScalar(u8, token, '_'); 134 _ = it.next(); // pylf 135 _ = it.next(); // v1 136 if (it.next()) |region| { 137 if (std.mem.eql(u8, region, "eu")) { 138 return "https://logfire-eu.pydantic.dev"; 139 } 140 } 141 } 142 // default to US 143 return "https://logfire-us.pydantic.dev"; 144} 145 146// tests 147 148test "resolve from environment" { 149 // can't easily test env vars, but test the default resolution 150 const config = Config{ 151 .service_name = "test", 152 }; 153 const resolved = config.resolve(); 154 try std.testing.expectEqualStrings("test", resolved.service_name.?); 155} 156 157test "getBaseUrlFromToken us" { 158 const url = getBaseUrlFromToken("pylf_v1_us_abc123"); 159 try std.testing.expectEqualStrings("https://logfire-us.pydantic.dev", url); 160} 161 162test "getBaseUrlFromToken eu" { 163 const url = getBaseUrlFromToken("pylf_v1_eu_abc123"); 164 try std.testing.expectEqualStrings("https://logfire-eu.pydantic.dev", url); 165} 166 167test "getBaseUrlFromToken unknown defaults to us" { 168 const url = getBaseUrlFromToken("some_other_token"); 169 try std.testing.expectEqualStrings("https://logfire-us.pydantic.dev", url); 170}