diff --git a/README.md b/README.md index 07eb97c..4dda222 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,57 @@ const sdk = b.dependency("opentelemetry-sdk", .{ }); ``` +## C Language Bindings + +The SDK provides C-compatible bindings, allowing C programs to use OpenTelemetry instrumentation. The C API covers all three signals: Traces, Metrics, and Logs. + +### Using from C + +1. **Link with the compiled library**: Build the Zig library and link it with your C project. + +2. **Include the header**: Add `include/opentelemetry.h` to your project. + +3. **Basic usage example**: + +```c +#include "opentelemetry.h" + +int main() { + // Create a meter provider + otel_meter_provider_t* provider = otel_meter_provider_create(); + + // Create an exporter and reader + otel_metric_exporter_t* exporter = otel_metric_exporter_stdout_create(); + otel_metric_reader_t* reader = otel_metric_reader_create(exporter); + otel_meter_provider_add_reader(provider, reader); + + // Get a meter + otel_meter_t* meter = otel_meter_provider_get_meter( + provider, "my-service", "1.0.0", NULL); + + // Create and use a counter + otel_counter_u64_t* counter = otel_meter_create_counter_u64( + meter, "requests", "Total requests", "1"); + otel_counter_add_u64(counter, 1, NULL, 0); + + // Collect and export metrics + otel_metric_reader_collect(reader); + + // Cleanup + otel_meter_provider_shutdown(provider); + return 0; +} +``` + +### C API Features + +- **Opaque handles**: All SDK objects are exposed as opaque handles for type safety +- **Memory management**: The C API manages memory internally using page allocators +- **Error handling**: Functions return status codes (0 for success, negative for errors) +- **Examples**: See `examples/c/` for complete examples of traces, metrics, and logs + +For detailed API documentation, refer to `include/opentelemetry.h`. + ## Specification Support State ### Signals @@ -64,7 +115,9 @@ const sdk = b.dependency("opentelemetry-sdk", .{ ## Examples -Check out the [examples](./examples) folder for practical usage examples of traces, metrics, and logs. +Check out the [examples](./examples) folder for practical usage examples: +- `examples/` - Zig examples for traces, metrics, and logs +- `examples/c/` - C language examples demonstrating the C API bindings ## Contributing diff --git a/build.zig b/build.zig index eb8e327..8e91d6a 100644 --- a/build.zig +++ b/build.zig @@ -81,14 +81,27 @@ pub fn build(b: *std.Build) !void { }, }); + // Static library for the OpenTelemetry SDK C users const sdk_lib = b.addLibrary(.{ .name = "opentelemetry-sdk", - .root_module = sdk_mod, + .linkage = .static, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c.zig"), + .target = target, + .optimize = optimize, + }), }); sdk_lib.linkLibrary(zlib_lib); // TODO: remove when 0.16.0 is released b.installArtifact(sdk_lib); + // Install include headers for C users + b.installDirectory(.{ + .source_dir = b.path("include"), + .install_dir = .header, + .install_subdir = "", + }); + // Providing a way for the user to request running the unit tests. const test_step = b.step("test", "Run unit tests"); @@ -151,6 +164,47 @@ pub fn build(b: *std.Build) !void { } } + // C examples + const c_example_step = b.step("examples-c", "Build and run C examples"); + + const c_examples = [_][]const u8{ + "logs", + "metrics", + "trace", + }; + + for (c_examples) |example_name| { + const c_example_exe = b.addExecutable(.{ + .name = example_name, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + + c_example_exe.addCSourceFile(.{ + .file = b.path(b.pathJoin(&.{ + "examples", + "c", + b.fmt("{s}.c", .{example_name}), + })), + .flags = &.{ + "-std=c11", + "-Wall", + "-Wextra", + }, + }); + c_example_exe.addIncludePath(b.path("include")); + c_example_exe.linkLibrary(sdk_lib); + + const run_c_example = b.addRunArtifact(c_example_exe); + c_example_step.dependOn(&run_c_example.step); + + // Also install each C example executable + b.installArtifact(c_example_exe); + } + // Benchmarks const benchmarks_step = b.step("benchmarks", "Build and run all benchmarks"); diff --git a/examples/c/logs.c b/examples/c/logs.c new file mode 100644 index 0000000..7277159 --- /dev/null +++ b/examples/c/logs.c @@ -0,0 +1,359 @@ +/** + * @file basic_logs.c + * @brief Example demonstrating the OpenTelemetry Logs C API. + * + * This example shows how to: + * - Create a LoggerProvider + * - Configure a LogRecordProcessor with a stdout exporter + * - Get a Logger + * - Emit log records with different severities + * - Use attributes with log records + * - Properly shutdown the provider + * + * Build with: + * zig build c-examples + * + * Run with: + * ./zig-out/bin/basic_logs + */ + +#include +#include +#include +#include +#include "../include/opentelemetry.h" + +/* Helper to check status and exit on error */ +#define CHECK_STATUS(status, msg) \ + if ((status) != OTEL_STATUS_OK) { \ + fprintf(stderr, "Error: %s (status=%d)\n", msg, status); \ + exit(1); \ + } + +/* Helper to check pointer and exit on NULL */ +#define CHECK_PTR(ptr, msg) \ + if ((ptr) == NULL) { \ + fprintf(stderr, "Error: %s returned NULL\n", msg); \ + exit(1); \ + } + +/** + * @brief Demonstrates basic logging operations. + */ +void demo_basic_logging(otel_logger_t* logger) { + printf("\n=== Basic Logging Demo ===\n"); + otel_status_t status; + + /* Emit a simple INFO log */ + status = otel_logger_emit( + logger, + OTEL_SEVERITY_INFO, + "INFO", + "Application started successfully", + NULL, /* no attributes */ + 0 + ); + CHECK_STATUS(status, "otel_logger_emit INFO"); + printf("Emitted INFO log\n"); + + /* Emit a DEBUG log */ + status = otel_logger_emit( + logger, + OTEL_SEVERITY_DEBUG, + "DEBUG", + "Loading configuration from file", + NULL, + 0 + ); + CHECK_STATUS(status, "otel_logger_emit DEBUG"); + printf("Emitted DEBUG log\n"); + + /* Emit a WARNING log */ + status = otel_logger_emit( + logger, + OTEL_SEVERITY_WARN, + "WARN", + "Configuration file not found, using defaults", + NULL, + 0 + ); + CHECK_STATUS(status, "otel_logger_emit WARN"); + printf("Emitted WARN log\n"); + + /* Emit an ERROR log */ + status = otel_logger_emit( + logger, + OTEL_SEVERITY_ERROR, + "ERROR", + "Failed to connect to database", + NULL, + 0 + ); + CHECK_STATUS(status, "otel_logger_emit ERROR"); + printf("Emitted ERROR log\n"); +} + +/** + * @brief Demonstrates logging with attributes. + */ +void demo_logging_with_attributes(otel_logger_t* logger) { + printf("\n=== Logging with Attributes Demo ===\n"); + otel_status_t status; + + /* Create attributes for a request log */ + otel_attribute_t request_attrs[] = { + { + .key = "http.method", + .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + .value.string_value = "GET" + }, + { + .key = "http.url", + .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + .value.string_value = "/api/users" + }, + { + .key = "http.status_code", + .value_type = OTEL_ATTRIBUTE_TYPE_INT, + .value.int_value = 200 + }, + { + .key = "http.response_time_ms", + .value_type = OTEL_ATTRIBUTE_TYPE_DOUBLE, + .value.double_value = 45.7 + } + }; + + status = otel_logger_emit( + logger, + OTEL_SEVERITY_INFO, + "INFO", + "HTTP request completed", + request_attrs, + 4 /* number of attributes */ + ); + CHECK_STATUS(status, "otel_logger_emit with attributes"); + printf("Emitted log with HTTP request attributes\n"); + + /* Create attributes for an error log */ + otel_attribute_t error_attrs[] = { + { + .key = "error.type", + .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + .value.string_value = "ConnectionError" + }, + { + .key = "error.message", + .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + .value.string_value = "Connection refused" + }, + { + .key = "retry.attempt", + .value_type = OTEL_ATTRIBUTE_TYPE_INT, + .value.int_value = 3 + }, + { + .key = "retry.exhausted", + .value_type = OTEL_ATTRIBUTE_TYPE_BOOL, + .value.bool_value = true + } + }; + + status = otel_logger_emit( + logger, + OTEL_SEVERITY_ERROR, + "ERROR", + "Failed to connect to service after retries", + error_attrs, + 4 /* number of attributes */ + ); + CHECK_STATUS(status, "otel_logger_emit error with attributes"); + printf("Emitted error log with retry attributes\n"); +} + +/** + * @brief Demonstrates different severity levels. + */ +void demo_severity_levels(otel_logger_t* logger) { + printf("\n=== Severity Levels Demo ===\n"); + otel_status_t status; + + /* All TRACE levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_TRACE, "TRACE", "Trace level 1", NULL, 0); + CHECK_STATUS(status, "TRACE"); + + /* DEBUG levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_DEBUG, "DEBUG", "Debug level", NULL, 0); + CHECK_STATUS(status, "DEBUG"); + status = otel_logger_emit(logger, OTEL_SEVERITY_DEBUG2, "DEBUG2", "Debug level 2", NULL, 0); + CHECK_STATUS(status, "DEBUG2"); + + /* INFO levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_INFO, "INFO", "Info level", NULL, 0); + CHECK_STATUS(status, "INFO"); + + /* WARN levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_WARN, "WARN", "Warning level", NULL, 0); + CHECK_STATUS(status, "WARN"); + + /* ERROR levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_ERROR, "ERROR", "Error level", NULL, 0); + CHECK_STATUS(status, "ERROR"); + + /* FATAL levels */ + status = otel_logger_emit(logger, OTEL_SEVERITY_FATAL, "FATAL", "Fatal level", NULL, 0); + CHECK_STATUS(status, "FATAL"); + + printf("Emitted logs at all severity levels\n"); +} + +/** + * @brief Demonstrates checking if logging is enabled. + */ +void demo_enabled_check(otel_logger_t* logger) { + printf("\n=== Enabled Check Demo ===\n"); + + /* Check if different severity levels are enabled */ + bool trace_enabled = otel_logger_is_enabled(logger, OTEL_SEVERITY_TRACE); + bool debug_enabled = otel_logger_is_enabled(logger, OTEL_SEVERITY_DEBUG); + bool info_enabled = otel_logger_is_enabled(logger, OTEL_SEVERITY_INFO); + bool error_enabled = otel_logger_is_enabled(logger, OTEL_SEVERITY_ERROR); + + printf("TRACE enabled: %s\n", trace_enabled ? "yes" : "no"); + printf("DEBUG enabled: %s\n", debug_enabled ? "yes" : "no"); + printf("INFO enabled: %s\n", info_enabled ? "yes" : "no"); + printf("ERROR enabled: %s\n", error_enabled ? "yes" : "no"); +} + +/** + * @brief Demonstrates using multiple loggers. + */ +void demo_multiple_loggers(otel_logger_provider_t* provider) { + printf("\n=== Multiple Loggers Demo ===\n"); + otel_status_t status; + + /* Get loggers for different components */ + otel_logger_t* db_logger = otel_logger_provider_get_logger( + provider, + "database-client", + "2.0.0", + NULL + ); + CHECK_PTR(db_logger, "otel_logger_provider_get_logger (database)"); + + otel_logger_t* http_logger = otel_logger_provider_get_logger( + provider, + "http-server", + "1.5.0", + "https://opentelemetry.io/schemas/1.21.0" + ); + CHECK_PTR(http_logger, "otel_logger_provider_get_logger (http)"); + + otel_logger_t* cache_logger = otel_logger_provider_get_logger( + provider, + "cache-service", + NULL, /* no version */ + NULL + ); + CHECK_PTR(cache_logger, "otel_logger_provider_get_logger (cache)"); + + /* Log from each component */ + status = otel_logger_emit( + db_logger, + OTEL_SEVERITY_INFO, + "INFO", + "Database connection pool initialized", + NULL, 0 + ); + CHECK_STATUS(status, "db_logger emit"); + printf("Emitted log from database-client logger\n"); + + status = otel_logger_emit( + http_logger, + OTEL_SEVERITY_INFO, + "INFO", + "HTTP server listening on port 8080", + NULL, 0 + ); + CHECK_STATUS(status, "http_logger emit"); + printf("Emitted log from http-server logger\n"); + + status = otel_logger_emit( + cache_logger, + OTEL_SEVERITY_DEBUG, + "DEBUG", + "Cache hit for key: user:123", + NULL, 0 + ); + CHECK_STATUS(status, "cache_logger emit"); + printf("Emitted log from cache-service logger\n"); +} + +/** + * @brief Main entry point. + */ +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; + + printf("OpenTelemetry Logs C API Example\n"); + printf("================================\n"); + + /* Step 1: Create a LoggerProvider */ + printf("\n--- Creating LoggerProvider ---\n"); + otel_logger_provider_t* provider = otel_logger_provider_create(); + CHECK_PTR(provider, "otel_logger_provider_create"); + printf("LoggerProvider created successfully\n"); + + /* Step 2: Create a stdout exporter for debugging */ + printf("\n--- Creating stdout LogRecordExporter ---\n"); + otel_log_record_exporter_t* exporter = otel_log_record_exporter_stdout_create(); + CHECK_PTR(exporter, "otel_log_record_exporter_stdout_create"); + printf("Stdout LogRecordExporter created successfully\n"); + + /* Step 3: Create a SimpleLogRecordProcessor */ + printf("\n--- Creating SimpleLogRecordProcessor ---\n"); + otel_log_record_processor_t* processor = otel_simple_log_record_processor_create(exporter); + CHECK_PTR(processor, "otel_simple_log_record_processor_create"); + printf("SimpleLogRecordProcessor created successfully\n"); + + /* Step 4: Add the processor to the provider */ + printf("\n--- Adding processor to provider ---\n"); + otel_status_t status = otel_logger_provider_add_log_record_processor(provider, processor); + CHECK_STATUS(status, "otel_logger_provider_add_log_record_processor"); + printf("Processor added to provider successfully\n"); + + /* Step 5: Get a Logger */ + printf("\n--- Getting Logger ---\n"); + otel_logger_t* logger = otel_logger_provider_get_logger( + provider, + "example-app", + "1.0.0", + NULL + ); + CHECK_PTR(logger, "otel_logger_provider_get_logger"); + printf("Logger obtained successfully\n"); + + /* Run demos */ + demo_basic_logging(logger); + demo_logging_with_attributes(logger); + demo_severity_levels(logger); + demo_enabled_check(logger); + demo_multiple_loggers(provider); + + /* Step 6: Force flush to ensure all logs are exported */ + printf("\n--- Flushing logs ---\n"); + status = otel_logger_provider_force_flush(provider); + CHECK_STATUS(status, "otel_logger_provider_force_flush"); + printf("All logs flushed successfully\n"); + + /* Step 7: Shutdown */ + printf("\n--- Shutting down ---\n"); + otel_logger_provider_shutdown(provider); + printf("LoggerProvider shutdown successfully\n"); + + printf("\n================================\n"); + printf("Example completed successfully!\n"); + + return 0; +} diff --git a/examples/c/metrics.c b/examples/c/metrics.c new file mode 100644 index 0000000..6b818f3 --- /dev/null +++ b/examples/c/metrics.c @@ -0,0 +1,168 @@ +/** + * OpenTelemetry C API Example - Basic Metrics + * + * This example demonstrates how to use the OpenTelemetry C API + * to create and record metrics. + * + * To compile: + * zig build + * # Then compile this C file and link with the generated library: + * cc -I include examples/c/basic_metrics.c -L zig-out/lib -lopentelemetry-sdk -o basic_metrics + * + * To run: + * ./basic_metrics + */ + +#include "opentelemetry.h" +#include +#include + +int main(void) { + printf("OpenTelemetry C API Example\n"); + printf("===========================\n\n"); + + // Create a meter provider + otel_meter_provider_t* provider = otel_meter_provider_create(); + if (!provider) { + fprintf(stderr, "Failed to create meter provider\n"); + return 1; + } + printf("✓ Created MeterProvider\n"); + + // Create a stdout exporter for debugging + otel_metric_exporter_t* exporter = otel_metric_exporter_stdout_create(); + if (!exporter) { + fprintf(stderr, "Failed to create exporter\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Created stdout MetricExporter\n"); + + // Create a metric reader + otel_metric_reader_t* reader = otel_metric_reader_create(exporter); + if (!reader) { + fprintf(stderr, "Failed to create metric reader\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Created MetricReader\n"); + + // Add the reader to the provider + otel_status_t status = otel_meter_provider_add_reader(provider, reader); + if (status != OTEL_STATUS_OK) { + fprintf(stderr, "Failed to add reader to provider: %d\n", status); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Added reader to provider\n"); + + // Get a meter + otel_meter_t* meter = otel_meter_provider_get_meter( + provider, + "example-service", + "1.0.0", + NULL + ); + if (!meter) { + fprintf(stderr, "Failed to get meter\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Got Meter 'example-service'\n"); + + // Create a counter + otel_counter_u64_t* request_counter = otel_meter_create_counter_u64( + meter, + "http_requests_total", + "Total number of HTTP requests", + "1" + ); + if (!request_counter) { + fprintf(stderr, "Failed to create counter\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Created Counter 'http_requests_total'\n"); + + // Create a histogram + otel_histogram_f64_t* latency_histogram = otel_meter_create_histogram_f64( + meter, + "http_request_duration_seconds", + "HTTP request latency in seconds", + "s" + ); + if (!latency_histogram) { + fprintf(stderr, "Failed to create histogram\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Created Histogram 'http_request_duration_seconds'\n"); + + // Create a gauge + otel_gauge_f64_t* cpu_gauge = otel_meter_create_gauge_f64( + meter, + "system_cpu_usage", + "Current CPU usage", + "percent" + ); + if (!cpu_gauge) { + fprintf(stderr, "Failed to create gauge\n"); + otel_meter_provider_shutdown(provider); + return 1; + } + printf("✓ Created Gauge 'system_cpu_usage'\n"); + + printf("\n--- Recording metrics ---\n\n"); + + // Record some counter values without attributes + printf("Recording counter increments...\n"); + otel_counter_add_u64(request_counter, 1, NULL, 0); + otel_counter_add_u64(request_counter, 5, NULL, 0); + otel_counter_add_u64(request_counter, 3, NULL, 0); + + // Record counter with attributes + otel_attribute_t attrs[] = { + { + .key = "method", + .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + .value = { .string_value = "GET" } + }, + { + .key = "status_code", + .value_type = OTEL_ATTRIBUTE_TYPE_INT, + .value = { .int_value = 200 } + } + }; + otel_counter_add_u64(request_counter, 10, attrs, 2); + printf(" ✓ Recorded counter with attributes\n"); + + // Record histogram values + printf("Recording histogram values...\n"); + otel_histogram_record_f64(latency_histogram, 0.025, NULL, 0); + otel_histogram_record_f64(latency_histogram, 0.150, NULL, 0); + otel_histogram_record_f64(latency_histogram, 0.042, NULL, 0); + otel_histogram_record_f64(latency_histogram, 1.234, NULL, 0); + printf(" ✓ Recorded 4 latency samples\n"); + + // Record gauge value + printf("Recording gauge value...\n"); + otel_gauge_record_f64(cpu_gauge, 45.7, NULL, 0); + printf(" ✓ Recorded CPU usage: 45.7%%\n"); + + // Collect and export metrics + printf("\n--- Collecting metrics ---\n\n"); + status = otel_metric_reader_collect(reader); + if (status == OTEL_STATUS_OK) { + printf("✓ Metrics collected and exported successfully\n"); + } else { + printf("⚠ Failed to collect metrics: %d\n", status); + } + + // Cleanup + printf("\n--- Shutdown ---\n\n"); + otel_meter_provider_shutdown(provider); + printf("✓ MeterProvider shutdown complete\n"); + + printf("\nDone!\n"); + return 0; +} diff --git a/examples/c/trace.c b/examples/c/trace.c new file mode 100644 index 0000000..97331cf --- /dev/null +++ b/examples/c/trace.c @@ -0,0 +1,301 @@ +/** + * OpenTelemetry C API Example - Basic Tracing + * + * This example demonstrates how to use the OpenTelemetry C API + * to create and manage distributed traces. + * + * To compile: + * zig build + * # Then compile this C file and link with the generated library: + * cc -I include examples/c/basic_trace.c -L zig-out/lib -lopentelemetry-sdk -o basic_trace + * + * To run: + * ./basic_trace + */ + +#include "opentelemetry.h" +#include +#include +#include + +/* Simulate some work */ +void simulate_work(const char* description) { + printf(" Working on: %s\n", description); + /* In a real application, this would do actual work */ +} + +/* Simulate a database query */ +void simulate_db_query(otel_tracer_t* tracer, const char* query) { + /* Create a client span for the database call */ + otel_span_start_options_t opts = { + .kind = OTEL_SPAN_KIND_CLIENT, + .attributes = NULL, + .attr_count = 0, + .start_timestamp_ns = 0 + }; + + otel_span_t* span = otel_tracer_start_span(tracer, "db.query", &opts); + if (!span) { + fprintf(stderr, "Failed to create database span\n"); + return; + } + + /* Set database-related attributes */ + otel_span_set_attribute_string(span, "db.system", "postgresql"); + otel_span_set_attribute_string(span, "db.statement", query); + otel_span_set_attribute_string(span, "db.name", "users_db"); + + /* Simulate the query execution */ + simulate_work("executing database query"); + + /* Add an event for query completion */ + otel_span_add_event(span, "query.executed", NULL, 0); + + /* Set success status */ + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + + /* End the span */ + otel_span_end(span); +} + +/* Simulate an HTTP request handler */ +void handle_request(otel_tracer_t* tracer, const char* method, const char* path) { + /* Create a server span for the incoming request */ + otel_span_start_options_t opts = { + .kind = OTEL_SPAN_KIND_SERVER, + .attributes = NULL, + .attr_count = 0, + .start_timestamp_ns = 0 + }; + + otel_span_t* span = otel_tracer_start_span(tracer, "http.request", &opts); + if (!span) { + fprintf(stderr, "Failed to create request span\n"); + return; + } + + /* Set HTTP-related attributes */ + otel_span_set_attribute_string(span, "http.method", method); + otel_span_set_attribute_string(span, "http.target", path); + otel_span_set_attribute_string(span, "http.scheme", "https"); + otel_span_set_attribute_int(span, "http.status_code", 200); + + /* Get and print trace context */ + char trace_id[33]; + char span_id[17]; + if (otel_span_get_trace_id_hex(span, trace_id, sizeof(trace_id)) == OTEL_STATUS_OK) { + printf(" Trace ID: %s\n", trace_id); + } + if (otel_span_get_span_id_hex(span, span_id, sizeof(span_id)) == OTEL_STATUS_OK) { + printf(" Span ID: %s\n", span_id); + } + + /* Add an event for request received */ + otel_span_add_event(span, "request.received", NULL, 0); + + /* Simulate request processing */ + simulate_work("parsing request"); + + /* Make a nested database call */ + simulate_db_query(tracer, "SELECT * FROM users WHERE id = 42"); + + /* Simulate more work */ + simulate_work("formatting response"); + + /* Add an event for response sent */ + otel_span_add_event(span, "response.sent", NULL, 0); + + /* Set success status */ + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + + /* End the span */ + otel_span_end(span); +} + +/* Simulate an operation that fails */ +void failing_operation(otel_tracer_t* tracer) { + otel_span_t* span = otel_tracer_start_span(tracer, "failing.operation", NULL); + if (!span) { + fprintf(stderr, "Failed to create span\n"); + return; + } + + /* Simulate some work */ + simulate_work("attempting risky operation"); + + /* Record an exception */ + otel_span_record_exception( + span, + "RuntimeError", + "Something went wrong!", + "at failing_operation (basic_trace.c:110)\nat main (basic_trace.c:180)" + ); + + /* Set error status */ + otel_span_set_status(span, OTEL_SPAN_STATUS_ERROR, "Operation failed due to RuntimeError"); + + /* End the span */ + otel_span_end(span); +} + +int main(void) { + printf("OpenTelemetry C API Tracing Example\n"); + printf("====================================\n\n"); + + /* Create a tracer provider */ + otel_tracer_provider_t* provider = otel_tracer_provider_create(); + if (!provider) { + fprintf(stderr, "Failed to create tracer provider\n"); + return 1; + } + printf("✓ Created TracerProvider\n"); + + /* Create a stdout exporter for debugging */ + otel_span_exporter_t* exporter = otel_span_exporter_stdout_create(); + if (!exporter) { + fprintf(stderr, "Failed to create exporter\n"); + otel_tracer_provider_shutdown(provider); + return 1; + } + printf("✓ Created stdout SpanExporter\n"); + + /* Create a simple span processor */ + otel_span_processor_t* processor = otel_simple_span_processor_create(exporter); + if (!processor) { + fprintf(stderr, "Failed to create processor\n"); + otel_tracer_provider_shutdown(provider); + return 1; + } + printf("✓ Created SimpleSpanProcessor\n"); + + /* Add the processor to the provider */ + otel_status_t status = otel_tracer_provider_add_span_processor(provider, processor); + if (status != OTEL_STATUS_OK) { + fprintf(stderr, "Failed to add processor: %d\n", status); + otel_tracer_provider_shutdown(provider); + return 1; + } + printf("✓ Added processor to provider\n"); + + /* Get a tracer */ + otel_tracer_t* tracer = otel_tracer_provider_get_tracer( + provider, + "example-service", + "1.0.0", + NULL + ); + if (!tracer) { + fprintf(stderr, "Failed to get tracer\n"); + otel_tracer_provider_shutdown(provider); + return 1; + } + printf("✓ Got Tracer 'example-service'\n"); + + /* Check if tracer is enabled */ + if (otel_tracer_is_enabled(tracer)) { + printf("✓ Tracer is enabled\n"); + } + + printf("\n--- Creating traces ---\n\n"); + + /* Example 1: Simple span */ + printf("Example 1: Simple span\n"); + { + otel_span_t* span = otel_tracer_start_span(tracer, "simple-operation", NULL); + if (span) { + otel_span_set_attribute_string(span, "operation.type", "simple"); + otel_span_set_attribute_int(span, "operation.count", 1); + simulate_work("simple task"); + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + otel_span_end(span); + printf(" ✓ Simple span completed\n"); + } + } + + printf("\nExample 2: HTTP request with nested database call\n"); + handle_request(tracer, "GET", "/api/users/42"); + printf(" ✓ HTTP request span completed\n"); + + printf("\nExample 3: Span with custom kind\n"); + { + otel_span_start_options_t opts = { + .kind = OTEL_SPAN_KIND_PRODUCER, + .attributes = NULL, + .attr_count = 0, + .start_timestamp_ns = 0 + }; + otel_span_t* span = otel_tracer_start_span(tracer, "message.publish", &opts); + if (span) { + otel_span_set_attribute_string(span, "messaging.system", "kafka"); + otel_span_set_attribute_string(span, "messaging.destination", "orders"); + simulate_work("publishing message"); + otel_span_add_event(span, "message.sent", NULL, 0); + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + otel_span_end(span); + printf(" ✓ Producer span completed\n"); + } + } + + printf("\nExample 4: Span with event attributes\n"); + { + otel_span_t* span = otel_tracer_start_span(tracer, "process-order", NULL); + if (span) { + otel_span_set_attribute_string(span, "order.id", "ORD-12345"); + + /* Add event with attributes */ + otel_attribute_t event_attrs[] = { + { + .key = "item.count", + .value_type = OTEL_ATTRIBUTE_TYPE_INT, + .value = { .int_value = 3 } + }, + { + .key = "total.amount", + .value_type = OTEL_ATTRIBUTE_TYPE_DOUBLE, + .value = { .double_value = 99.99 } + } + }; + otel_span_add_event(span, "order.validated", event_attrs, 2); + + simulate_work("processing order"); + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + otel_span_end(span); + printf(" ✓ Order processing span completed\n"); + } + } + + printf("\nExample 5: Failing operation with exception\n"); + failing_operation(tracer); + printf(" ✓ Failing operation span completed (with error)\n"); + + printf("\nExample 6: Updating span name\n"); + { + otel_span_t* span = otel_tracer_start_span(tracer, "generic-operation", NULL); + if (span) { + /* Update name based on what we learn during execution */ + otel_span_update_name(span, "specific-user-lookup"); + otel_span_set_attribute_int(span, "user.id", 12345); + simulate_work("looking up user"); + otel_span_set_status(span, OTEL_SPAN_STATUS_OK, NULL); + otel_span_end(span); + printf(" ✓ Span with updated name completed\n"); + } + } + + /* Force flush to ensure all spans are exported */ + printf("\n--- Flushing spans ---\n\n"); + status = otel_tracer_provider_force_flush(provider); + if (status == OTEL_STATUS_OK) { + printf("✓ Force flush completed\n"); + } else { + printf("⚠ Force flush failed: %d\n", status); + } + + /* Cleanup */ + printf("\n--- Shutdown ---\n\n"); + otel_tracer_provider_shutdown(provider); + printf("✓ TracerProvider shutdown complete\n"); + + printf("\nDone!\n"); + return 0; +} diff --git a/include/opentelemetry.h b/include/opentelemetry.h new file mode 100644 index 0000000..042b693 --- /dev/null +++ b/include/opentelemetry.h @@ -0,0 +1,1113 @@ +/** + * @file opentelemetry.h + * @brief OpenTelemetry C API Header + * + * This header provides C-compatible bindings for the OpenTelemetry Zig SDK. + * It allows C programs to instrument their applications with OpenTelemetry + * metrics, traces, and logs. + * + * ## Quick Start + * + * ```c + * #include "opentelemetry.h" + * + * int main() { + * // Create a meter provider + * otel_meter_provider_t* provider = otel_meter_provider_create(); + * if (!provider) return 1; + * + * // Create an exporter and reader + * otel_metric_exporter_t* exporter = otel_metric_exporter_stdout_create(); + * otel_metric_reader_t* reader = otel_metric_reader_create(exporter); + * otel_meter_provider_add_reader(provider, reader); + * + * // Get a meter + * otel_meter_t* meter = otel_meter_provider_get_meter( + * provider, "my-service", "1.0.0", NULL); + * + * // Create and use a counter + * otel_counter_u64_t* counter = otel_meter_create_counter_u64( + * meter, "requests", "Total requests", "1"); + * otel_counter_add_u64(counter, 1, NULL, 0); + * + * // Collect and export metrics + * otel_metric_reader_collect(reader); + * + * // Cleanup + * otel_meter_provider_shutdown(provider); + * return 0; + * } + * ``` + * + * @note Link with the compiled OpenTelemetry Zig library. + */ + +#ifndef OPENTELEMETRY_H +#define OPENTELEMETRY_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Status Codes + * ============================================================================ */ + +/** + * @brief Status codes returned by OpenTelemetry C API functions. + */ +typedef enum { + OTEL_STATUS_OK = 0, /**< Operation succeeded */ + OTEL_STATUS_ERROR_OUT_OF_MEMORY = -1, /**< Memory allocation failed */ + OTEL_STATUS_ERROR_INVALID_ARGUMENT = -2, /**< Invalid argument provided */ + OTEL_STATUS_ERROR_INVALID_STATE = -3, /**< Invalid state for operation */ + OTEL_STATUS_ERROR_ALREADY_SHUTDOWN = -4, /**< Component already shut down */ + OTEL_STATUS_ERROR_EXPORT_FAILED = -5, /**< Export operation failed */ + OTEL_STATUS_ERROR_COLLECT_FAILED = -6, /**< Collection operation failed */ + OTEL_STATUS_ERROR_UNKNOWN = -99 /**< Unknown error */ +} otel_status_t; + +/* ============================================================================ + * Opaque Handle Types + * ============================================================================ */ + +/** + * @brief Opaque handle to a MeterProvider. + * + * MeterProvider is the entry point for the Metrics API. It provides access + * to Meters which are used to create instruments. + */ +typedef struct otel_meter_provider otel_meter_provider_t; + +/** + * @brief Opaque handle to a Meter. + * + * A Meter is used to create instruments (Counter, Histogram, Gauge, etc.) + * for recording measurements. + */ +typedef struct otel_meter otel_meter_t; + +/** + * @brief Opaque handle to a Counter instrument with u64 values. + * + * A Counter is a monotonically increasing value. Use add() to increment. + * Counter uses unsigned integers since it can only increase. + */ +typedef struct otel_counter_u64 otel_counter_u64_t; + +/** + * @brief Opaque handle to an UpDownCounter instrument with i64 values. + * + * An UpDownCounter can increase or decrease. Use add() with positive + * or negative values. + */ +typedef struct otel_updown_counter_i64 otel_updown_counter_i64_t; + +/** + * @brief Opaque handle to a Histogram instrument with f64 values. + * + * A Histogram records a distribution of values. Use record() to add + * measurements. + */ +typedef struct otel_histogram_f64 otel_histogram_f64_t; + +/** + * @brief Opaque handle to a Gauge instrument with f64 values. + * + * A Gauge records point-in-time values. Use record() to set the current + * value. + */ +typedef struct otel_gauge_f64 otel_gauge_f64_t; + +/** + * @brief Opaque handle to a MetricReader. + * + * A MetricReader collects metrics from a MeterProvider and exports them + * using a MetricExporter. + */ +typedef struct otel_metric_reader otel_metric_reader_t; + +/** + * @brief Opaque handle to a MetricExporter. + * + * A MetricExporter exports metrics to a destination (stdout, OTLP, etc.). + */ +typedef struct otel_metric_exporter otel_metric_exporter_t; + +/* ============================================================================ + * Attribute Types + * ============================================================================ */ + +/** + * @brief Attribute value types. + */ +typedef enum { + OTEL_ATTRIBUTE_TYPE_BOOL = 0, /**< Boolean value */ + OTEL_ATTRIBUTE_TYPE_INT = 1, /**< 64-bit signed integer */ + OTEL_ATTRIBUTE_TYPE_DOUBLE = 2, /**< 64-bit floating point */ + OTEL_ATTRIBUTE_TYPE_STRING = 3 /**< Null-terminated string */ +} otel_attribute_value_type_t; + +/** + * @brief A key-value attribute for adding metadata to measurements. + * + * Attributes provide additional context to measurements. They are + * key-value pairs where the value can be a bool, int, double, or string. + * + * Example: + * ```c + * otel_attribute_t attrs[] = { + * {.key = "method", .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + * .value = {.string_value = "GET"}}, + * {.key = "status", .value_type = OTEL_ATTRIBUTE_TYPE_INT, + * .value = {.int_value = 200}} + * }; + * otel_counter_add_i64(counter, 1, attrs, 2); + * ``` + */ +typedef struct { + const char* key; /**< Attribute key (null-terminated) */ + otel_attribute_value_type_t value_type; /**< Type of the value */ + union { + bool bool_value; /**< Boolean value */ + int64_t int_value; /**< Integer value */ + double double_value; /**< Double value */ + const char* string_value; /**< String value (null-terminated) */ + } value; +} otel_attribute_t; + +/* ============================================================================ + * MeterProvider API + * ============================================================================ */ + +/** + * @brief Create a new MeterProvider using the default allocator. + * + * Creates a MeterProvider that manages Meters and their instruments. + * The provider must be shut down with otel_meter_provider_shutdown() + * when no longer needed. + * + * @return Pointer to the MeterProvider, or NULL on error. + * + * Example: + * ```c + * otel_meter_provider_t* provider = otel_meter_provider_create(); + * if (provider) { + * // Use the provider... + * otel_meter_provider_shutdown(provider); + * } + * ``` + */ +otel_meter_provider_t* otel_meter_provider_create(void); + +/** + * @brief Create a new MeterProvider with explicit initialization. + * + * Similar to otel_meter_provider_create() but uses a different + * internal allocator. For most use cases, prefer otel_meter_provider_create(). + * + * @return Pointer to the MeterProvider, or NULL on error. + */ +otel_meter_provider_t* otel_meter_provider_init(void); + +/** + * @brief Shutdown the MeterProvider and release all resources. + * + * This function flushes all pending metrics, shuts down all associated + * readers and exporters, and frees all memory. After calling this function, + * the provider handle becomes invalid and must not be used. + * + * @param provider The MeterProvider to shutdown. Can be NULL (no-op). + */ +void otel_meter_provider_shutdown(otel_meter_provider_t* provider); + +/** + * @brief Get a Meter from the MeterProvider. + * + * Returns a Meter for the given instrumentation scope. If a Meter with + * the same scope already exists, it is returned. Otherwise, a new Meter + * is created. + * + * @param provider The MeterProvider handle. + * @param name The name of the instrumentation scope (required, null-terminated). + * @param version Optional version string (null-terminated, can be NULL). + * @param schema_url Optional schema URL (null-terminated, can be NULL). + * @return Pointer to the Meter, or NULL on error. + * + * Example: + * ```c + * otel_meter_t* meter = otel_meter_provider_get_meter( + * provider, "my-library", "1.0.0", NULL); + * ``` + */ +otel_meter_t* otel_meter_provider_get_meter( + otel_meter_provider_t* provider, + const char* name, + const char* version, + const char* schema_url); + +/** + * @brief Add a MetricReader to the MeterProvider. + * + * Registers a MetricReader with the provider. The reader will collect + * metrics from all Meters managed by this provider. + * + * @param provider The MeterProvider handle. + * @param reader The MetricReader to add. + * @return Status code indicating success or failure. + */ +otel_status_t otel_meter_provider_add_reader( + otel_meter_provider_t* provider, + otel_metric_reader_t* reader); + +/* ============================================================================ + * Counter API (u64) + * ============================================================================ */ + +/** + * @brief Create a new Counter instrument with u64 values. + * + * A Counter is a monotonically increasing value. It uses unsigned integers + * since counters can only be incremented (never negative). + * + * @param meter The Meter handle. + * @param name Instrument name (required, null-terminated). + * @param description Optional description (null-terminated, can be NULL). + * @param unit Optional unit (null-terminated, can be NULL). Example: "1", "ms", "bytes". + * @return Pointer to the Counter, or NULL on error. + * + * Example: + * ```c + * otel_counter_u64_t* counter = otel_meter_create_counter_u64( + * meter, "http.requests", "Total HTTP requests", "1"); + * ``` + */ +otel_counter_u64_t* otel_meter_create_counter_u64( + otel_meter_t* meter, + const char* name, + const char* description, + const char* unit); + +/** + * @brief Add a value to the Counter. + * + * Increments the counter by the given value. + * + * @param counter The Counter handle. + * @param value The value to add (unsigned). + * @param attributes Array of attributes (can be NULL if attr_count is 0). + * @param attr_count Number of attributes in the array. + * @return Status code indicating success or failure. + * + * Example: + * ```c + * // Simple increment + * otel_counter_add_u64(counter, 1, NULL, 0); + * + * // With attributes + * otel_attribute_t attrs[] = { + * {.key = "method", .value_type = OTEL_ATTRIBUTE_TYPE_STRING, + * .value = {.string_value = "GET"}} + * }; + * otel_counter_add_u64(counter, 1, attrs, 1); + * ``` + */ +otel_status_t otel_counter_add_u64( + otel_counter_u64_t* counter, + uint64_t value, + const otel_attribute_t* attributes, + size_t attr_count); + +/* ============================================================================ + * UpDownCounter API (i64) + * ============================================================================ */ + +/** + * @brief Create a new UpDownCounter instrument with i64 values. + * + * An UpDownCounter can both increase and decrease. It's useful for + * values that can go up or down, like active connections or queue size. + * + * @param meter The Meter handle. + * @param name Instrument name (required, null-terminated). + * @param description Optional description (null-terminated, can be NULL). + * @param unit Optional unit (null-terminated, can be NULL). + * @return Pointer to the UpDownCounter, or NULL on error. + */ +otel_updown_counter_i64_t* otel_meter_create_updown_counter_i64( + otel_meter_t* meter, + const char* name, + const char* description, + const char* unit); + +/** + * @brief Add a value to the UpDownCounter. + * + * Adds the given value to the counter. The value can be positive + * (increment) or negative (decrement). + * + * @param counter The UpDownCounter handle. + * @param value The value to add (can be positive or negative). + * @param attributes Array of attributes (can be NULL if attr_count is 0). + * @param attr_count Number of attributes in the array. + * @return Status code indicating success or failure. + */ +otel_status_t otel_updown_counter_add_i64( + otel_updown_counter_i64_t* counter, + int64_t value, + const otel_attribute_t* attributes, + size_t attr_count); + +/* ============================================================================ + * Histogram API (f64) + * ============================================================================ */ + +/** + * @brief Create a new Histogram instrument with f64 values. + * + * A Histogram samples observations and counts them in buckets. It's + * useful for measuring durations, sizes, and other distributions. + * + * @param meter The Meter handle. + * @param name Instrument name (required, null-terminated). + * @param description Optional description (null-terminated, can be NULL). + * @param unit Optional unit (null-terminated, can be NULL). Example: "ms", "bytes". + * @return Pointer to the Histogram, or NULL on error. + */ +otel_histogram_f64_t* otel_meter_create_histogram_f64( + otel_meter_t* meter, + const char* name, + const char* description, + const char* unit); + +/** + * @brief Record a value in the Histogram. + * + * Records an observation in the histogram. The value will be counted + * in the appropriate bucket. + * + * @param histogram The Histogram handle. + * @param value The value to record. + * @param attributes Array of attributes (can be NULL if attr_count is 0). + * @param attr_count Number of attributes in the array. + * @return Status code indicating success or failure. + */ +otel_status_t otel_histogram_record_f64( + otel_histogram_f64_t* histogram, + double value, + const otel_attribute_t* attributes, + size_t attr_count); + +/* ============================================================================ + * Gauge API (f64) + * ============================================================================ */ + +/** + * @brief Create a new Gauge instrument with f64 values. + * + * A Gauge records point-in-time values. It's useful for values that + * fluctuate, like temperature, CPU usage, or memory usage. + * + * @param meter The Meter handle. + * @param name Instrument name (required, null-terminated). + * @param description Optional description (null-terminated, can be NULL). + * @param unit Optional unit (null-terminated, can be NULL). + * @return Pointer to the Gauge, or NULL on error. + */ +otel_gauge_f64_t* otel_meter_create_gauge_f64( + otel_meter_t* meter, + const char* name, + const char* description, + const char* unit); + +/** + * @brief Record a value in the Gauge. + * + * Records the current value of the gauge. + * + * @param gauge The Gauge handle. + * @param value The value to record. + * @param attributes Array of attributes (can be NULL if attr_count is 0). + * @param attr_count Number of attributes in the array. + * @return Status code indicating success or failure. + */ +otel_status_t otel_gauge_record_f64( + otel_gauge_f64_t* gauge, + double value, + const otel_attribute_t* attributes, + size_t attr_count); + +/* ============================================================================ + * MetricExporter API + * ============================================================================ */ + +/** + * @brief Create a stdout MetricExporter for debugging. + * + * Creates an exporter that writes metrics to standard output in a + * human-readable format. Useful for debugging and development. + * + * @return Pointer to the MetricExporter, or NULL on error. + */ +otel_metric_exporter_t* otel_metric_exporter_stdout_create(void); + +/** + * @brief Create an in-memory MetricExporter. + * + * Creates an exporter that stores metrics in memory. Useful for testing + * and scenarios where metrics need to be accessed programmatically. + * + * @return Pointer to the MetricExporter, or NULL on error. + */ +otel_metric_exporter_t* otel_metric_exporter_inmemory_create(void); + +/* ============================================================================ + * MetricReader API + * ============================================================================ */ + +/** + * @brief Create a MetricReader with the given exporter. + * + * A MetricReader collects metrics from a MeterProvider and exports them + * using the provided exporter. + * + * @param exporter The MetricExporter to use. + * @return Pointer to the MetricReader, or NULL on error. + */ +otel_metric_reader_t* otel_metric_reader_create(otel_metric_exporter_t* exporter); + +/** + * @brief Trigger a collection cycle on the MetricReader. + * + * Collects all metrics from the associated MeterProvider and exports + * them using the configured exporter. + * + * @param reader The MetricReader handle. + * @return Status code indicating success or failure. + */ +otel_status_t otel_metric_reader_collect(otel_metric_reader_t* reader); + +/** + * @brief Shutdown the MetricReader and release resources. + * + * Performs a final collection, shuts down the exporter, and frees + * all memory. After calling this function, the reader handle becomes + * invalid and must not be used. + * + * @param reader The MetricReader to shutdown. Can be NULL (no-op). + */ +void otel_metric_reader_shutdown(otel_metric_reader_t* reader); + +/* ============================================================================ + * Tracing API - Opaque Handle Types + * ============================================================================ */ + +/** + * @brief Opaque handle to a TracerProvider. + * + * TracerProvider is the entry point for the Tracing API. It provides access + * to Tracers which are used to create Spans. + */ +typedef struct otel_tracer_provider otel_tracer_provider_t; + +/** + * @brief Opaque handle to a Tracer. + * + * A Tracer is used to create Spans for recording distributed traces. + */ +typedef struct otel_tracer otel_tracer_t; + +/** + * @brief Opaque handle to a Span. + * + * A Span represents a single operation within a trace. Spans can be + * nested to form a trace tree. + */ +typedef struct otel_span otel_span_t; + +/** + * @brief Opaque handle to a SpanProcessor. + * + * A SpanProcessor processes spans as they are started and ended. + */ +typedef struct otel_span_processor otel_span_processor_t; + +/** + * @brief Opaque handle to a SpanExporter. + * + * A SpanExporter exports spans to a destination (stdout, OTLP, etc.). + */ +typedef struct otel_span_exporter otel_span_exporter_t; + +/* ============================================================================ + * Tracing API - Enums + * ============================================================================ */ + +/** + * @brief Span kind values. + * + * SpanKind describes the relationship between the Span, its parents, + * and its children in a Trace. + */ +typedef enum { + OTEL_SPAN_KIND_INTERNAL = 0, /**< Default. Internal operation */ + OTEL_SPAN_KIND_SERVER = 1, /**< Server-side handling of a request */ + OTEL_SPAN_KIND_CLIENT = 2, /**< Client-side request to a remote service */ + OTEL_SPAN_KIND_PRODUCER = 3, /**< Initiation of an async operation */ + OTEL_SPAN_KIND_CONSUMER = 4 /**< Processing of an async operation */ +} otel_span_kind_t; + +/** + * @brief Span status codes. + */ +typedef enum { + OTEL_SPAN_STATUS_UNSET = 0, /**< Default status */ + OTEL_SPAN_STATUS_OK = 1, /**< Operation completed successfully */ + OTEL_SPAN_STATUS_ERROR = 2 /**< Operation failed */ +} otel_span_status_code_t; + +/** + * @brief Options for starting a span. + */ +typedef struct { + otel_span_kind_t kind; /**< Span kind (default: INTERNAL) */ + const otel_attribute_t* attributes; /**< Initial attributes (can be NULL) */ + size_t attr_count; /**< Number of attributes */ + uint64_t start_timestamp_ns; /**< Start time in nanoseconds (0 = now) */ +} otel_span_start_options_t; + +/* ============================================================================ + * TracerProvider API + * ============================================================================ */ + +/** + * @brief Create a new TracerProvider. + * + * Creates a TracerProvider that manages Tracers and their Spans. + * The provider must be shut down with otel_tracer_provider_shutdown() + * when no longer needed. + * + * @return Pointer to the TracerProvider, or NULL on error. + * + * Example: + * ```c + * otel_tracer_provider_t* provider = otel_tracer_provider_create(); + * if (provider) { + * // Use the provider... + * otel_tracer_provider_shutdown(provider); + * } + * ``` + */ +otel_tracer_provider_t* otel_tracer_provider_create(void); + +/** + * @brief Shutdown the TracerProvider and release all resources. + * + * This function flushes all pending spans, shuts down all associated + * processors and exporters, and frees all memory. After calling this + * function, the provider handle becomes invalid and must not be used. + * + * @param provider The TracerProvider to shutdown. Can be NULL (no-op). + */ +void otel_tracer_provider_shutdown(otel_tracer_provider_t* provider); + +/** + * @brief Get a Tracer from the TracerProvider. + * + * Returns a Tracer for the given instrumentation scope. If a Tracer with + * the same scope already exists, it is returned. Otherwise, a new Tracer + * is created. + * + * @param provider The TracerProvider handle. + * @param name The name of the instrumentation scope (required, null-terminated). + * @param version Optional version string (null-terminated, can be NULL). + * @param schema_url Optional schema URL (null-terminated, can be NULL). + * @return Pointer to the Tracer, or NULL on error. + * + * Example: + * ```c + * otel_tracer_t* tracer = otel_tracer_provider_get_tracer( + * provider, "my-library", "1.0.0", NULL); + * ``` + */ +otel_tracer_t* otel_tracer_provider_get_tracer( + otel_tracer_provider_t* provider, + const char* name, + const char* version, + const char* schema_url); + +/** + * @brief Add a SpanProcessor to the TracerProvider. + * + * Registers a SpanProcessor with the provider. The processor will be + * called for all spans created by tracers from this provider. + * + * @param provider The TracerProvider handle. + * @param processor The SpanProcessor to add. + * @return Status code indicating success or failure. + */ +otel_status_t otel_tracer_provider_add_span_processor( + otel_tracer_provider_t* provider, + otel_span_processor_t* processor); + +/** + * @brief Force flush all span processors. + * + * @param provider The TracerProvider handle. + * @return Status code indicating success or failure. + */ +otel_status_t otel_tracer_provider_force_flush(otel_tracer_provider_t* provider); + +/* ============================================================================ + * Tracer API + * ============================================================================ */ + +/** + * @brief Start a new Span. + * + * Creates a new span with the given name. The span must be ended with + * otel_span_end() when the operation completes. + * + * @param tracer The Tracer handle. + * @param name The span name (required, null-terminated). + * @param options Optional span start options (can be NULL for defaults). + * @return Pointer to the Span, or NULL on error. + * + * Example: + * ```c + * otel_span_t* span = otel_tracer_start_span(tracer, "my-operation", NULL); + * // ... do work ... + * otel_span_end(span); + * ``` + */ +otel_span_t* otel_tracer_start_span( + otel_tracer_t* tracer, + const char* name, + const otel_span_start_options_t* options); + +/** + * @brief Check if the tracer is enabled. + * + * @param tracer The Tracer handle. + * @return true if the tracer is enabled, false otherwise. + */ +bool otel_tracer_is_enabled(otel_tracer_t* tracer); + +/* ============================================================================ + * Span API + * ============================================================================ */ + +/** + * @brief End a Span. + * + * Ends the span and exports it via the configured processors. + * After calling this function, the span handle becomes invalid. + * + * @param span The Span to end. Can be NULL (no-op). + */ +void otel_span_end(otel_span_t* span); + +/** + * @brief End a Span with a specific timestamp. + * + * @param span The Span to end. + * @param timestamp_ns End timestamp in nanoseconds since epoch. + */ +void otel_span_end_with_timestamp(otel_span_t* span, uint64_t timestamp_ns); + +/** + * @brief Set a string attribute on the Span. + * + * @param span The Span handle. + * @param key Attribute key (null-terminated). + * @param value Attribute value (null-terminated). + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_set_attribute_string( + otel_span_t* span, + const char* key, + const char* value); + +/** + * @brief Set an integer attribute on the Span. + * + * @param span The Span handle. + * @param key Attribute key (null-terminated). + * @param value Attribute value. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_set_attribute_int( + otel_span_t* span, + const char* key, + int64_t value); + +/** + * @brief Set a double attribute on the Span. + * + * @param span The Span handle. + * @param key Attribute key (null-terminated). + * @param value Attribute value. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_set_attribute_double( + otel_span_t* span, + const char* key, + double value); + +/** + * @brief Set a boolean attribute on the Span. + * + * @param span The Span handle. + * @param key Attribute key (null-terminated). + * @param value Attribute value. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_set_attribute_bool( + otel_span_t* span, + const char* key, + bool value); + +/** + * @brief Add an event to the Span. + * + * Events represent something that happened during a Span's lifetime. + * + * @param span The Span handle. + * @param name Event name (null-terminated). + * @param attributes Array of attributes (can be NULL). + * @param attr_count Number of attributes. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_add_event( + otel_span_t* span, + const char* name, + const otel_attribute_t* attributes, + size_t attr_count); + +/** + * @brief Add an event with a specific timestamp. + * + * @param span The Span handle. + * @param name Event name (null-terminated). + * @param timestamp_ns Event timestamp in nanoseconds since epoch. + * @param attributes Array of attributes (can be NULL). + * @param attr_count Number of attributes. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_add_event_with_timestamp( + otel_span_t* span, + const char* name, + uint64_t timestamp_ns, + const otel_attribute_t* attributes, + size_t attr_count); + +/** + * @brief Set the status of the Span. + * + * @param span The Span handle. + * @param code Status code. + * @param description Optional description (null-terminated, can be NULL). + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_set_status( + otel_span_t* span, + otel_span_status_code_t code, + const char* description); + +/** + * @brief Update the name of the Span. + * + * @param span The Span handle. + * @param name New span name (null-terminated). + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_update_name(otel_span_t* span, const char* name); + +/** + * @brief Record an exception on the Span. + * + * Records an exception as an event with standard exception attributes. + * + * @param span The Span handle. + * @param exception_type Exception type (null-terminated). + * @param message Exception message (null-terminated). + * @param stacktrace Optional stack trace (null-terminated, can be NULL). + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_record_exception( + otel_span_t* span, + const char* exception_type, + const char* message, + const char* stacktrace); + +/** + * @brief Check if the Span is recording. + * + * @param span The Span handle. + * @return true if the span is recording, false otherwise. + */ +bool otel_span_is_recording(otel_span_t* span); + +/** + * @brief Get the trace ID as a hex string. + * + * @param span The Span handle. + * @param buffer Buffer to write the hex string (must be at least 33 bytes). + * @param buffer_size Size of the buffer. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_get_trace_id_hex( + otel_span_t* span, + char* buffer, + size_t buffer_size); + +/** + * @brief Get the span ID as a hex string. + * + * @param span The Span handle. + * @param buffer Buffer to write the hex string (must be at least 17 bytes). + * @param buffer_size Size of the buffer. + * @return Status code indicating success or failure. + */ +otel_status_t otel_span_get_span_id_hex( + otel_span_t* span, + char* buffer, + size_t buffer_size); + +/* ============================================================================ + * SpanExporter API + * ============================================================================ */ + +/** + * @brief Create a stdout SpanExporter for debugging. + * + * Creates an exporter that writes spans to standard output in a + * human-readable JSON format. Useful for debugging and development. + * + * @return Pointer to the SpanExporter, or NULL on error. + */ +otel_span_exporter_t* otel_span_exporter_stdout_create(void); + +/* ============================================================================ + * SpanProcessor API + * ============================================================================ */ + +/** + * @brief Create a SimpleSpanProcessor. + * + * A SimpleSpanProcessor exports spans immediately when they end. + * This is useful for debugging but may not be suitable for production + * due to the synchronous export on the critical path. + * + * @param exporter The SpanExporter to use. + * @return Pointer to the SpanProcessor, or NULL on error. + */ +otel_span_processor_t* otel_simple_span_processor_create( + otel_span_exporter_t* exporter); + +/* ============================================================================ + * LOGS API + * ============================================================================ */ + +/* ============================================================================ + * Logs Opaque Handle Types + * ============================================================================ */ + +/** + * @brief Opaque handle to a LoggerProvider. + * + * A LoggerProvider is the entry point for the Logs SDK. It provides + * Loggers and manages log record processors. + */ +typedef struct otel_logger_provider_t otel_logger_provider_t; + +/** + * @brief Opaque handle to a Logger. + * + * A Logger emits log records to the configured log record processors. + */ +typedef struct otel_logger_t otel_logger_t; + +/** + * @brief Opaque handle to a LogRecordProcessor. + * + * A LogRecordProcessor receives log records from Loggers and forwards + * them to LogRecordExporters. + */ +typedef struct otel_log_record_processor_t otel_log_record_processor_t; + +/** + * @brief Opaque handle to a LogRecordExporter. + * + * A LogRecordExporter exports log records to a backend. + */ +typedef struct otel_log_record_exporter_t otel_log_record_exporter_t; + +/* ============================================================================ + * Severity Levels + * ============================================================================ */ + +/** + * @brief Severity number values for logs (OpenTelemetry specification). + */ +typedef enum { + OTEL_SEVERITY_UNSPECIFIED = 0, + OTEL_SEVERITY_TRACE = 1, + OTEL_SEVERITY_TRACE2 = 2, + OTEL_SEVERITY_TRACE3 = 3, + OTEL_SEVERITY_TRACE4 = 4, + OTEL_SEVERITY_DEBUG = 5, + OTEL_SEVERITY_DEBUG2 = 6, + OTEL_SEVERITY_DEBUG3 = 7, + OTEL_SEVERITY_DEBUG4 = 8, + OTEL_SEVERITY_INFO = 9, + OTEL_SEVERITY_INFO2 = 10, + OTEL_SEVERITY_INFO3 = 11, + OTEL_SEVERITY_INFO4 = 12, + OTEL_SEVERITY_WARN = 13, + OTEL_SEVERITY_WARN2 = 14, + OTEL_SEVERITY_WARN3 = 15, + OTEL_SEVERITY_WARN4 = 16, + OTEL_SEVERITY_ERROR = 17, + OTEL_SEVERITY_ERROR2 = 18, + OTEL_SEVERITY_ERROR3 = 19, + OTEL_SEVERITY_ERROR4 = 20, + OTEL_SEVERITY_FATAL = 21, + OTEL_SEVERITY_FATAL2 = 22, + OTEL_SEVERITY_FATAL3 = 23, + OTEL_SEVERITY_FATAL4 = 24 +} otel_severity_number_t; + +/* ============================================================================ + * LoggerProvider API + * ============================================================================ */ + +/** + * @brief Create a new LoggerProvider. + * + * Creates a new LoggerProvider with default configuration. + * + * @return Pointer to the LoggerProvider, or NULL on error. + */ +otel_logger_provider_t* otel_logger_provider_create(void); + +/** + * @brief Shutdown the LoggerProvider and release all resources. + * + * Flushes any pending log records and releases all associated resources. + * After calling this function, the provider handle becomes invalid. + * + * @param provider The LoggerProvider to shutdown. Can be NULL (no-op). + */ +void otel_logger_provider_shutdown(otel_logger_provider_t* provider); + +/** + * @brief Get a Logger from the LoggerProvider. + * + * @param provider The LoggerProvider handle. + * @param name The name of the logger (null-terminated). + * @param version Optional version string (null-terminated, can be NULL). + * @param schema_url Optional schema URL (null-terminated, can be NULL). + * @return Pointer to the Logger, or NULL on error. + */ +otel_logger_t* otel_logger_provider_get_logger( + otel_logger_provider_t* provider, + const char* name, + const char* version, + const char* schema_url); + +/** + * @brief Add a LogRecordProcessor to the LoggerProvider. + * + * @param provider The LoggerProvider handle. + * @param processor The LogRecordProcessor to add. + * @return Status code indicating success or failure. + */ +otel_status_t otel_logger_provider_add_log_record_processor( + otel_logger_provider_t* provider, + otel_log_record_processor_t* processor); + +/** + * @brief Force flush all log record processors. + * + * Forces immediate export of all log records that have not yet been exported. + * + * @param provider The LoggerProvider handle. + * @return Status code indicating success or failure. + */ +otel_status_t otel_logger_provider_force_flush(otel_logger_provider_t* provider); + +/* ============================================================================ + * Logger API + * ============================================================================ */ + +/** + * @brief Emit a log record. + * + * @param logger The Logger handle. + * @param severity_number Severity level (use otel_severity_number_t values). + * @param severity_text Severity text (e.g., "INFO", "ERROR", null-terminated, can be NULL). + * @param body Log message body (null-terminated, can be NULL). + * @param attributes Array of attributes (can be NULL). + * @param attr_count Number of attributes. + * @return Status code indicating success or failure. + */ +otel_status_t otel_logger_emit( + otel_logger_t* logger, + int severity_number, + const char* severity_text, + const char* body, + const otel_attribute_t* attributes, + size_t attr_count); + +/** + * @brief Check if logging is enabled for the given severity. + * + * This method is useful for avoiding expensive operations when logging is disabled. + * + * @param logger The Logger handle. + * @param severity_number Severity level to check. + * @return true if logging is enabled, false otherwise. + */ +bool otel_logger_is_enabled(otel_logger_t* logger, int severity_number); + +/* ============================================================================ + * LogRecordExporter API + * ============================================================================ */ + +/** + * @brief Create a stdout LogRecordExporter for debugging. + * + * Creates an exporter that writes log records to standard output. + * Useful for debugging and development. + * + * @return Pointer to the LogRecordExporter, or NULL on error. + */ +otel_log_record_exporter_t* otel_log_record_exporter_stdout_create(void); + +/* ============================================================================ + * LogRecordProcessor API + * ============================================================================ */ + +/** + * @brief Create a SimpleLogRecordProcessor. + * + * A SimpleLogRecordProcessor exports log records immediately when they are emitted. + * This is useful for debugging but may not be suitable for production + * due to the synchronous export on the critical path. + * + * @param exporter The LogRecordExporter to use. + * @return Pointer to the LogRecordProcessor, or NULL on error. + */ +otel_log_record_processor_t* otel_simple_log_record_processor_create( + otel_log_record_exporter_t* exporter); + +#ifdef __cplusplus +} +#endif + +#endif /* OPENTELEMETRY_H */ diff --git a/src/api/metrics/instrument.zig b/src/api/metrics/instrument.zig index 94f7e4c..163ff7c 100644 --- a/src/api/metrics/instrument.zig +++ b/src/api/metrics/instrument.zig @@ -276,6 +276,22 @@ pub fn Counter(comptime T: type) type { try self.data_points.append(self.allocator, dp); } + /// Add the given delta to the counter with a pre-built attribute slice. + /// This is primarily used for C interop where attributes are passed as a slice. + pub fn addWithSlice(self: *Self, delta: T, attributes: ?[]Attribute) !void { + self.lock.lock(); + defer self.lock.unlock(); + + const dp = DataPoint(T){ + .value = delta, + .attributes = if (attributes) |attrs| + try self.allocator.dupe(Attribute, attrs) + else + null, + }; + try self.data_points.append(self.allocator, dp); + } + fn measurementsData(self: *Self, allocator: std.mem.Allocator) !MeasurementsData { self.lock.lock(); defer self.lock.unlock(); @@ -343,6 +359,22 @@ pub fn Histogram(comptime T: type) type { try self.data_points.append(self.allocator, dp); } + /// Record a value with a pre-built attribute slice. + /// This is primarily used for C interop where attributes are passed as a slice. + pub fn recordWithSlice(self: *Self, value: T, attributes: ?[]Attribute) !void { + self.lock.lock(); + defer self.lock.unlock(); + + const dp = DataPoint(T){ + .value = value, + .attributes = if (attributes) |attrs| + try self.allocator.dupe(Attribute, attrs) + else + null, + }; + try self.data_points.append(self.allocator, dp); + } + fn measurementsData(self: *Self, allocator: std.mem.Allocator) !MeasurementsData { self.lock.lock(); defer self.lock.unlock(); @@ -420,6 +452,22 @@ pub fn Gauge(comptime T: type) type { try self.data_points.append(self.allocator, dp); } + /// Record a value with a pre-built attribute slice. + /// This is primarily used for C interop where attributes are passed as a slice. + pub fn recordWithSlice(self: *Self, value: T, attributes: ?[]Attribute) !void { + self.lock.lock(); + defer self.lock.unlock(); + + const dp = DataPoint(T){ + .value = value, + .attributes = if (attributes) |attrs| + try self.allocator.dupe(Attribute, attrs) + else + null, + }; + try self.data_points.append(self.allocator, dp); + } + fn measurementsData(self: *Self, allocator: std.mem.Allocator) !MeasurementsData { self.lock.lock(); defer self.lock.unlock(); diff --git a/src/api/metrics/meter.zig b/src/api/metrics/meter.zig index 2e1324d..44f4d06 100644 --- a/src/api/metrics/meter.zig +++ b/src/api/metrics/meter.zig @@ -169,7 +169,7 @@ pub const MeterProvider = struct { /// Meter is a named instance that is used to record measurements. /// See https://opentelemetry.io/docs/specs/otel/metrics/api/#meter -const Meter = struct { +pub const Meter = struct { scope: InstrumentationScope, instruments: std.StringHashMapUnmanaged(*Instrument), allocator: std.mem.Allocator, diff --git a/src/c.zig b/src/c.zig new file mode 100644 index 0000000..d98cc93 --- /dev/null +++ b/src/c.zig @@ -0,0 +1,105 @@ +//! OpenTelemetry SDK C bindings. +//! +//! This module provides C-compatible wrappers for the Zig OpenTelemetry SDK, +//! allowing C programs to use OpenTelemetry instrumentation. +//! +//! ## Overview +//! +//! The C API exposes opaque handles for SDK objects and provides functions +//! to create, manipulate, and destroy them. All functions follow a consistent +//! naming convention: +//! +//! - `otel__create` - Create a new instance +//! - `otel__` - Perform an action +//! - `otel__shutdown` - Cleanup and destroy +//! +//! ## Signals +//! +//! Currently supported signals: +//! - Metrics (see `c/metrics.zig`) +//! - Traces (see `c/trace.zig`) +//! - Logs (see `c/logs.zig`) +//! +//! ## Memory Management +//! +//! The C API manages memory internally using page allocators. Users must +//! call the appropriate shutdown/destroy functions to release resources. +//! +//! ## Thread Safety +//! +//! The underlying Zig SDK is thread-safe where specified. The C bindings +//! preserve these guarantees. + +// ============================================================================ +// Signal Modules +// ============================================================================ + +/// Metrics signal C bindings. +/// Provides MeterProvider, Meter, Counter, Histogram, Gauge, and related types. +pub const metrics = @import("c/metrics.zig"); + +/// Tracing signal C bindings. +/// Provides TracerProvider, Tracer, Span, SpanProcessor, SpanExporter, and related types. +pub const trace = @import("c/trace.zig"); + +/// Logs signal C bindings. +/// Provides LoggerProvider, Logger, LogRecordProcessor, LogRecordExporter, and related types. +pub const logs = @import("c/logs.zig"); + +// Force the modules to be analyzed at comptime so exports are registered +comptime { + _ = metrics; + _ = trace; + _ = logs; +} + +// ============================================================================ +// API Bindings +// ============================================================================ + +// Baggage - TODO: Create proper C wrappers for baggage types +// The baggage API returns Zig types that need C-compatible wrappers +// const baggage = @import("api/baggage.zig"); +// comptime { +// @export(&baggage.getCurrentBaggage, .{ .name = "otel_baggage_get_current" }); +// } + +// ============================================================================ +// Re-export types for convenience +// ============================================================================ + +// Re-export common types that C users might need +pub const OtelStatus = metrics.OtelStatus; +pub const OtelAttribute = metrics.OtelAttribute; +pub const OtelAttributeValueType = metrics.OtelAttributeValueType; + +// Opaque handle types +pub const OtelMeterProvider = metrics.OtelMeterProvider; +pub const OtelMeter = metrics.OtelMeter; +pub const OtelCounterU64 = metrics.OtelCounterU64; +pub const OtelUpDownCounterI64 = metrics.OtelUpDownCounterI64; +pub const OtelHistogramF64 = metrics.OtelHistogramF64; +pub const OtelGaugeF64 = metrics.OtelGaugeF64; +pub const OtelMetricReader = metrics.OtelMetricReader; +pub const OtelMetricExporter = metrics.OtelMetricExporter; + +// Trace opaque handle types +pub const OtelTracerProvider = trace.OtelTracerProvider; +pub const OtelTracer = trace.OtelTracer; +pub const OtelSpan = trace.OtelSpan; +pub const OtelSpanProcessor = trace.OtelSpanProcessor; +pub const OtelSpanExporter = trace.OtelSpanExporter; + +// Trace enum types +pub const OtelSpanKind = trace.OtelSpanKind; +pub const OtelSpanStatusCode = trace.OtelSpanStatusCode; +pub const OtelSpanStartOptions = trace.OtelSpanStartOptions; + +// ============================================================================ +// Tests +// ============================================================================ + +test { + _ = metrics; + _ = trace; +} diff --git a/src/c/logs.zig b/src/c/logs.zig new file mode 100644 index 0000000..c106193 --- /dev/null +++ b/src/c/logs.zig @@ -0,0 +1,461 @@ +//! OpenTelemetry Logs SDK C bindings. +//! +//! This module provides C-compatible wrappers for the Zig Logs SDK, +//! allowing C programs to use OpenTelemetry logging instrumentation. +//! +//! ## Usage from C +//! +//! ```c +//! #include "opentelemetry.h" +//! +//! // Create a logger provider +//! otel_logger_provider_t* provider = otel_logger_provider_create(); +//! +//! // Add a log record processor with stdout exporter +//! otel_log_record_exporter_t* exporter = otel_log_record_exporter_stdout_create(); +//! otel_log_record_processor_t* processor = otel_simple_log_record_processor_create(exporter); +//! otel_logger_provider_add_log_record_processor(provider, processor); +//! +//! // Get a logger +//! otel_logger_t* logger = otel_logger_provider_get_logger(provider, "my-library", "1.0.0", NULL); +//! +//! // Emit log records +//! otel_logger_emit(logger, OTEL_SEVERITY_INFO, "INFO", "Hello from C!", NULL, 0); +//! +//! // Cleanup +//! otel_logger_provider_shutdown(provider); +//! ``` + +const std = @import("std"); +const LoggerProvider = @import("../api/logs/logger_provider.zig").LoggerProvider; +const Logger = @import("../api/logs/logger_provider.zig").Logger; +const ReadWriteLogRecord = @import("../api/logs/logger_provider.zig").ReadWriteLogRecord; +const ReadableLogRecord = @import("../api/logs/logger_provider.zig").ReadableLogRecord; +const LogRecordProcessor = @import("../sdk/logs/log_record_processor.zig").LogRecordProcessor; +const SimpleLogRecordProcessor = @import("../sdk/logs/log_record_processor.zig").SimpleLogRecordProcessor; +const LogRecordExporter = @import("../sdk/logs/log_record_exporter.zig").LogRecordExporter; +const StdoutExporter = @import("../sdk/logs/exporters/generic.zig").StdoutExporter; +const Attribute = @import("../attributes.zig").Attribute; +const InstrumentationScope = @import("../scope.zig").InstrumentationScope; + +// ============================================================================ +// Error Codes (shared with metrics/traces) +// ============================================================================ + +/// Error codes returned by C API functions. +pub const OtelStatus = enum(c_int) { + ok = 0, + error_out_of_memory = -1, + error_invalid_argument = -2, + error_invalid_state = -3, + error_already_shutdown = -4, + error_export_failed = -5, + error_unknown = -99, +}; + +// ============================================================================ +// Opaque Handle Types +// ============================================================================ + +/// Opaque handle to a LoggerProvider. +pub const OtelLoggerProvider = opaque {}; + +/// Opaque handle to a Logger. +pub const OtelLogger = opaque {}; + +/// Opaque handle to a LogRecordProcessor. +pub const OtelLogRecordProcessor = opaque {}; + +/// Opaque handle to a LogRecordExporter. +pub const OtelLogRecordExporter = opaque {}; + +// ============================================================================ +// Severity Levels +// ============================================================================ + +/// Severity number values for logs (OpenTelemetry specification). +pub const OtelSeverityNumber = enum(c_int) { + unspecified = 0, + trace = 1, + trace2 = 2, + trace3 = 3, + trace4 = 4, + debug = 5, + debug2 = 6, + debug3 = 7, + debug4 = 8, + info = 9, + info2 = 10, + info3 = 11, + info4 = 12, + warn = 13, + warn2 = 14, + warn3 = 15, + warn4 = 16, + @"error" = 17, + error2 = 18, + error3 = 19, + error4 = 20, + fatal = 21, + fatal2 = 22, + fatal3 = 23, + fatal4 = 24, +}; + +// ============================================================================ +// Attribute Types (shared with metrics/traces) +// ============================================================================ + +/// Attribute value types for C API. +pub const OtelAttributeValueType = enum(c_int) { + bool = 0, + int = 1, + double = 2, + string = 3, +}; + +/// A key-value attribute pair for C API. +pub const OtelAttribute = extern struct { + key: [*:0]const u8, + value_type: OtelAttributeValueType, + value: extern union { + bool_value: bool, + int_value: i64, + double_value: f64, + string_value: [*:0]const u8, + }, +}; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/// Get the global allocator used for C bindings. +fn getCAllocator() std.mem.Allocator { + return std.heap.c_allocator; +} + +/// Convert C attribute array to Zig attribute slice. +fn convertAttributes( + allocator: std.mem.Allocator, + c_attrs: [*c]const OtelAttribute, + count: usize, +) !?[]Attribute { + if (count == 0 or c_attrs == null) return null; + + var attrs = try allocator.alloc(Attribute, count); + errdefer allocator.free(attrs); + + for (0..count) |i| { + const c_attr = c_attrs[i]; + const key = std.mem.span(c_attr.key); + + attrs[i] = .{ + .key = key, + .value = switch (c_attr.value_type) { + .bool => .{ .bool = c_attr.value.bool_value }, + .int => .{ .int = c_attr.value.int_value }, + .double => .{ .double = c_attr.value.double_value }, + .string => .{ .string = std.mem.span(c_attr.value.string_value) }, + }, + }; + } + + return attrs; +} + +// ============================================================================ +// LoggerProvider API +// ============================================================================ + +/// Create a new LoggerProvider. +/// +/// Returns: Pointer to the LoggerProvider, or null on error. +pub fn loggerProviderCreate() callconv(.c) ?*OtelLoggerProvider { + const allocator = getCAllocator(); + const provider = LoggerProvider.init(allocator, null) catch return null; + return @ptrCast(provider); +} + +/// Shutdown the LoggerProvider and release all resources. +/// +/// After calling this function, the provider handle becomes invalid. +pub fn loggerProviderShutdown(provider: ?*OtelLoggerProvider) callconv(.c) void { + if (provider) |p| { + const lp: *LoggerProvider = @ptrCast(@alignCast(p)); + lp.shutdown() catch {}; + lp.deinit(); + } +} + +/// Get a Logger from the LoggerProvider. +/// +/// Parameters: +/// - provider: The LoggerProvider handle +/// - name: The name of the logger (null-terminated string) +/// - version: Optional version string (null-terminated, can be null) +/// - schema_url: Optional schema URL (null-terminated, can be null) +/// +/// Returns: Pointer to the Logger, or null on error. +pub fn loggerProviderGetLogger( + provider: ?*OtelLoggerProvider, + name: [*:0]const u8, + version: ?[*:0]const u8, + schema_url: ?[*:0]const u8, +) callconv(.c) ?*OtelLogger { + const p = provider orelse return null; + const lp: *LoggerProvider = @ptrCast(@alignCast(p)); + + const scope = InstrumentationScope{ + .name = std.mem.span(name), + .version = if (version) |v| std.mem.span(v) else null, + .schema_url = if (schema_url) |s| std.mem.span(s) else null, + }; + + const logger = lp.getLogger(scope) catch return null; + return @ptrCast(logger); +} + +/// Add a LogRecordProcessor to the LoggerProvider. +/// +/// Returns: Status code indicating success or failure. +pub fn loggerProviderAddLogRecordProcessor( + provider: ?*OtelLoggerProvider, + processor: ?*OtelLogRecordProcessor, +) callconv(.c) OtelStatus { + const p = provider orelse return .error_invalid_argument; + const proc = processor orelse return .error_invalid_argument; + + const lp: *LoggerProvider = @ptrCast(@alignCast(p)); + const lrp: *LogRecordProcessor = @ptrCast(@alignCast(proc)); + + lp.addLogRecordProcessor(lrp.*) catch |err| { + return switch (err) { + error.OutOfMemory => .error_out_of_memory, + error.LoggerProviderShutdown => .error_already_shutdown, + }; + }; + + return .ok; +} + +/// Force flush all log record processors. +/// +/// Returns: Status code indicating success or failure. +pub fn loggerProviderForceFlush(provider: ?*OtelLoggerProvider) callconv(.c) OtelStatus { + const p = provider orelse return .error_invalid_argument; + const lp: *LoggerProvider = @ptrCast(@alignCast(p)); + + lp.forceFlush() catch |err| { + return switch (err) { + error.LoggerProviderShutdown => .error_already_shutdown, + else => .error_export_failed, + }; + }; + + return .ok; +} + +// ============================================================================ +// Logger API +// ============================================================================ + +/// Emit a log record. +/// +/// Parameters: +/// - logger: The Logger handle +/// - severity_number: Severity level (use OtelSeverityNumber values) +/// - severity_text: Severity text (e.g., "INFO", "ERROR", null-terminated, can be null) +/// - body: Log message body (null-terminated, can be null) +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +/// +/// Returns: Status code indicating success or failure. +pub fn loggerEmit( + logger: ?*OtelLogger, + severity_number: c_int, + severity_text: ?[*:0]const u8, + body: ?[*:0]const u8, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const l = logger orelse return .error_invalid_argument; + const zigLogger: *Logger = @ptrCast(@alignCast(l)); + + const allocator = getCAllocator(); + + // Convert attributes + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + // Emit the log record + zigLogger.emit( + if (severity_number > 0) @intCast(severity_number) else null, + if (severity_text) |st| std.mem.span(st) else null, + if (body) |b| std.mem.span(b) else null, + attrs, + ); + + return .ok; +} + +/// Check if logging is enabled for the given severity. +/// +/// This method is useful for avoiding expensive operations when logging is disabled. +/// +/// Returns: true if logging is enabled, false otherwise. +pub fn loggerIsEnabled( + logger: ?*OtelLogger, + severity_number: c_int, +) callconv(.c) bool { + const l = logger orelse return false; + const zigLogger: *Logger = @ptrCast(@alignCast(l)); + + // Use the enabled method with just severity + const context = @import("../api/context/context.zig").Context.init(); + return zigLogger.enabled(.{ + .context = context, + .severity = if (severity_number > 0) @intCast(severity_number) else null, + }); +} + +// ============================================================================ +// LogRecordExporter API +// ============================================================================ + +/// Internal wrapper for stdout exporter that holds the exporter together. +const StdoutLogExporterWrapper = struct { + exporter: ?StdoutExporter = null, + log_record_exporter: ?LogRecordExporter = null, + + fn init(self: *StdoutLogExporterWrapper) void { + // Initialize the exporter with stdout + self.exporter = StdoutExporter.init(std.fs.File.stdout().deprecatedWriter()); + // Now create the log record exporter interface + self.log_record_exporter = self.exporter.?.asLogRecordExporter(); + } +}; + +/// Create a stdout LogRecordExporter for debugging. +/// +/// Returns: Pointer to the LogRecordExporter, or null on error. +pub fn logRecordExporterStdoutCreate() callconv(.c) ?*OtelLogRecordExporter { + const allocator = getCAllocator(); + + // Allocate the wrapper on the heap so everything persists + const wrapper = allocator.create(StdoutLogExporterWrapper) catch return null; + wrapper.* = .{}; // Initialize with defaults + wrapper.init(); // Then initialize the exporter + + if (wrapper.log_record_exporter) |*lre| { + return @ptrCast(lre); + } + return null; +} + +// ============================================================================ +// LogRecordProcessor API +// ============================================================================ + +/// Create a SimpleLogRecordProcessor that exports logs immediately. +/// +/// Parameters: +/// - exporter: The LogRecordExporter handle +/// +/// Returns: Pointer to the LogRecordProcessor, or null on error. +pub fn simpleLogRecordProcessorCreate(exporter: ?*OtelLogRecordExporter) callconv(.c) ?*OtelLogRecordProcessor { + const e = exporter orelse return null; + const allocator = getCAllocator(); + + const exp: *LogRecordExporter = @ptrCast(@alignCast(e)); + + const storage = allocator.create(SimpleLogRecordProcessor) catch return null; + storage.* = SimpleLogRecordProcessor.init(allocator, exp.*); + + const processor_ptr = allocator.create(LogRecordProcessor) catch { + allocator.destroy(storage); + return null; + }; + processor_ptr.* = storage.asLogRecordProcessor(); + + return @ptrCast(processor_ptr); +} + +// ============================================================================ +// C Export Declarations +// ============================================================================ + +comptime { + // LoggerProvider exports + @export(&loggerProviderCreate, .{ .name = "otel_logger_provider_create" }); + @export(&loggerProviderShutdown, .{ .name = "otel_logger_provider_shutdown" }); + @export(&loggerProviderGetLogger, .{ .name = "otel_logger_provider_get_logger" }); + @export(&loggerProviderAddLogRecordProcessor, .{ .name = "otel_logger_provider_add_log_record_processor" }); + @export(&loggerProviderForceFlush, .{ .name = "otel_logger_provider_force_flush" }); + + // Logger exports + @export(&loggerEmit, .{ .name = "otel_logger_emit" }); + @export(&loggerIsEnabled, .{ .name = "otel_logger_is_enabled" }); + + // LogRecordExporter exports + @export(&logRecordExporterStdoutCreate, .{ .name = "otel_log_record_exporter_stdout_create" }); + + // LogRecordProcessor exports + @export(&simpleLogRecordProcessorCreate, .{ .name = "otel_simple_log_record_processor_create" }); +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "logs C API - create logger provider" { + const provider = loggerProviderCreate(); + try std.testing.expect(provider != null); + defer loggerProviderShutdown(provider); +} + +test "logs C API - get logger" { + const provider = loggerProviderCreate(); + try std.testing.expect(provider != null); + defer loggerProviderShutdown(provider); + + const logger = loggerProviderGetLogger(provider, "test-logger", "1.0.0", null); + try std.testing.expect(logger != null); +} + +test "logs C API - emit log record" { + const provider = loggerProviderCreate(); + try std.testing.expect(provider != null); + defer loggerProviderShutdown(provider); + + const logger = loggerProviderGetLogger(provider, "test-logger", null, null); + try std.testing.expect(logger != null); + + // Emit a log record (no processor, so it won't go anywhere, but shouldn't crash) + const status = loggerEmit(logger, 9, "INFO", "Test message", null, 0); + try std.testing.expectEqual(OtelStatus.ok, status); +} + +test "logs C API - full pipeline with processor" { + const provider = loggerProviderCreate(); + try std.testing.expect(provider != null); + defer loggerProviderShutdown(provider); + + // Create exporter and processor + const exporter = logRecordExporterStdoutCreate(); + try std.testing.expect(exporter != null); + + const processor = simpleLogRecordProcessorCreate(exporter); + try std.testing.expect(processor != null); + + // Add processor to provider + const add_status = loggerProviderAddLogRecordProcessor(provider, processor); + try std.testing.expectEqual(OtelStatus.ok, add_status); + + // Get logger and emit + const logger = loggerProviderGetLogger(provider, "test-logger", null, null); + try std.testing.expect(logger != null); + + const emit_status = loggerEmit(logger, 9, "INFO", "Test message from C API", null, 0); + try std.testing.expectEqual(OtelStatus.ok, emit_status); +} diff --git a/src/c/metrics.zig b/src/c/metrics.zig new file mode 100644 index 0000000..249d3d4 --- /dev/null +++ b/src/c/metrics.zig @@ -0,0 +1,630 @@ +//! OpenTelemetry Metrics SDK C bindings. +//! +//! This module provides C-compatible wrappers for the Zig Metrics SDK, +//! allowing C programs to use OpenTelemetry metrics instrumentation. +//! +//! ## Usage from C +//! +//! ```c +//! #include "opentelemetry.h" +//! +//! // Create a meter provider +//! otel_meter_provider_t* provider = otel_meter_provider_create(); +//! +//! // Get a meter +//! otel_meter_t* meter = otel_meter_provider_get_meter(provider, "my-library", "1.0.0", NULL); +//! +//! // Create a counter +//! otel_counter_t* counter = otel_meter_create_counter_i64(meter, "my_counter", "A sample counter", "1"); +//! +//! // Record a value +//! otel_counter_add_i64(counter, 1, NULL, 0); +//! +//! // Cleanup +//! otel_meter_provider_shutdown(provider); +//! ``` + +const std = @import("std"); +const MeterProvider = @import("../api/metrics/meter.zig").MeterProvider; +const Meter = @import("../api/metrics/meter.zig").Meter; +const InstrumentOptions = @import("../api/metrics/instrument.zig").InstrumentOptions; +const Counter = @import("../api/metrics/instrument.zig").Counter; +const Histogram = @import("../api/metrics/instrument.zig").Histogram; +const Gauge = @import("../api/metrics/instrument.zig").Gauge; +const Attribute = @import("../attributes.zig").Attribute; +const MetricReader = @import("../sdk/metrics/reader.zig").MetricReader; +const MetricExporter = @import("../sdk/metrics/exporter.zig").MetricExporter; +const InMemoryExporter = @import("../sdk/metrics/exporters/in_memory.zig").InMemoryExporter; +const StdoutExporter = @import("../sdk/metrics/exporters/stdout.zig").StdoutExporter; + +// ============================================================================ +// Error Codes +// ============================================================================ + +/// Error codes returned by C API functions. +pub const OtelStatus = enum(c_int) { + ok = 0, + error_out_of_memory = -1, + error_invalid_argument = -2, + error_invalid_state = -3, + error_already_shutdown = -4, + error_export_failed = -5, + error_collect_failed = -6, + error_unknown = -99, +}; + +// ============================================================================ +// Opaque Handle Types +// ============================================================================ + +/// Opaque handle to a MeterProvider. +pub const OtelMeterProvider = opaque {}; + +/// Opaque handle to a Meter. +pub const OtelMeter = opaque {}; + +/// Opaque handle to a Counter instrument (u64 values). +/// Monotonic counters only support unsigned types. +pub const OtelCounterU64 = opaque {}; + +/// Opaque handle to an UpDownCounter instrument (i64 values). +pub const OtelUpDownCounterI64 = opaque {}; + +/// Opaque handle to a Histogram instrument (f64 values). +pub const OtelHistogramF64 = opaque {}; + +/// Opaque handle to a Gauge instrument (f64 values). +pub const OtelGaugeF64 = opaque {}; + +/// Opaque handle to a MetricReader. +pub const OtelMetricReader = opaque {}; + +/// Opaque handle to a MetricExporter. +pub const OtelMetricExporter = opaque {}; + +// ============================================================================ +// Attribute Types +// ============================================================================ + +/// Attribute value types for C API. +pub const OtelAttributeValueType = enum(c_int) { + bool = 0, + int = 1, + double = 2, + string = 3, +}; + +/// A key-value attribute pair for C API. +pub const OtelAttribute = extern struct { + key: [*:0]const u8, + value_type: OtelAttributeValueType, + value: extern union { + bool_value: bool, + int_value: i64, + double_value: f64, + string_value: [*:0]const u8, + }, +}; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/// Get the global allocator used for C bindings. +fn getCAllocator() std.mem.Allocator { + // Use page allocator for C bindings - it's simple and doesn't require cleanup + return std.heap.c_allocator; +} + +/// Convert C attribute array to Zig attribute slice for instrument recording. +fn convertAttributes( + allocator: std.mem.Allocator, + c_attrs: [*c]const OtelAttribute, + count: usize, +) !?[]Attribute { + if (count == 0 or c_attrs == null) return null; + + var attrs = try allocator.alloc(Attribute, count); + errdefer allocator.free(attrs); + + for (0..count) |i| { + const c_attr = c_attrs[i]; + const key = std.mem.span(c_attr.key); + + attrs[i] = .{ + .key = key, + .value = switch (c_attr.value_type) { + .bool => .{ .bool = c_attr.value.bool_value }, + .int => .{ .int = c_attr.value.int_value }, + .double => .{ .double = c_attr.value.double_value }, + .string => .{ .string = std.mem.span(c_attr.value.string_value) }, + }, + }; + } + + return attrs; +} + +// ============================================================================ +// MeterProvider API +// ============================================================================ + +/// Create a new MeterProvider using the default allocator. +/// +/// Returns: Pointer to the MeterProvider, or null on error. +pub fn meterProviderCreate() callconv(.c) ?*OtelMeterProvider { + const provider = MeterProvider.default() catch return null; + return @ptrCast(provider); +} + +/// Create a new MeterProvider with a custom allocator (for advanced use). +/// +/// Returns: Pointer to the MeterProvider, or null on error. +pub fn meterProviderInit() callconv(.c) ?*OtelMeterProvider { + const allocator = getCAllocator(); + const provider = MeterProvider.init(allocator) catch return null; + return @ptrCast(provider); +} + +/// Shutdown the MeterProvider and release all resources. +/// +/// After calling this function, the provider handle becomes invalid. +pub fn meterProviderShutdown(provider: ?*OtelMeterProvider) callconv(.c) void { + if (provider) |p| { + const mp: *MeterProvider = @ptrCast(@alignCast(p)); + mp.shutdown(); + } +} + +/// Get a Meter from the MeterProvider. +/// +/// Parameters: +/// - provider: The MeterProvider handle +/// - name: The name of the meter (null-terminated string) +/// - version: Optional version string (null-terminated, can be null) +/// - schema_url: Optional schema URL (null-terminated, can be null) +/// +/// Returns: Pointer to the Meter, or null on error. +pub fn meterProviderGetMeter( + provider: ?*OtelMeterProvider, + name: [*:0]const u8, + version: ?[*:0]const u8, + schema_url: ?[*:0]const u8, +) callconv(.c) ?*OtelMeter { + const p = provider orelse return null; + const mp: *MeterProvider = @ptrCast(@alignCast(p)); + + const scope = @import("../scope.zig").InstrumentationScope{ + .name = std.mem.span(name), + .version = if (version) |v| std.mem.span(v) else null, + .schema_url = if (schema_url) |s| std.mem.span(s) else null, + }; + + const meter = mp.getMeter(scope) catch return null; + return @ptrCast(meter); +} + +/// Add a MetricReader to the MeterProvider. +/// +/// Returns: Status code indicating success or failure. +pub fn meterProviderAddReader( + provider: ?*OtelMeterProvider, + reader: ?*OtelMetricReader, +) callconv(.c) OtelStatus { + const p = provider orelse return .error_invalid_argument; + const r = reader orelse return .error_invalid_argument; + + const mp: *MeterProvider = @ptrCast(@alignCast(p)); + const mr: *MetricReader = @ptrCast(@alignCast(r)); + + mp.addReader(mr) catch |err| { + return switch (err) { + error.OutOfMemory => .error_out_of_memory, + else => .error_invalid_state, + }; + }; + + return .ok; +} + +// ============================================================================ +// Counter API (u64) - Monotonic counters use unsigned types +// ============================================================================ + +/// Create a new Counter instrument with u64 values. +/// Monotonic counters can only be incremented by non-negative values. +/// +/// Parameters: +/// - meter: The Meter handle +/// - name: Instrument name (null-terminated) +/// - description: Optional description (null-terminated, can be null) +/// - unit: Optional unit (null-terminated, can be null) +/// +/// Returns: Pointer to the Counter, or null on error. +pub fn meterCreateCounterU64( + meter: ?*OtelMeter, + name: [*:0]const u8, + description: ?[*:0]const u8, + unit: ?[*:0]const u8, +) callconv(.c) ?*OtelCounterU64 { + const m = meter orelse return null; + const zigMeter: *Meter = @ptrCast(@alignCast(m)); + + const opts = InstrumentOptions{ + .name = std.mem.span(name), + .description = if (description) |d| std.mem.span(d) else null, + .unit = if (unit) |u| std.mem.span(u) else null, + }; + + const counter = zigMeter.createCounter(u64, opts) catch return null; + return @ptrCast(counter); +} + +/// Add a value to the Counter. +/// +/// Parameters: +/// - counter: The Counter handle +/// - value: The value to add (must be non-negative) +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +/// +/// Returns: Status code indicating success or failure. +pub fn counterAddU64( + counter: ?*OtelCounterU64, + value: u64, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const c = counter orelse return .error_invalid_argument; + const zigCounter: *Counter(u64) = @ptrCast(@alignCast(c)); + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + zigCounter.addWithSlice(value, attrs) catch return .error_out_of_memory; + return .ok; +} + +// ============================================================================ +// UpDownCounter API (i64) +// ============================================================================ + +/// Create a new UpDownCounter instrument with i64 values. +/// +/// Parameters: +/// - meter: The Meter handle +/// - name: Instrument name (null-terminated) +/// - description: Optional description (null-terminated, can be null) +/// - unit: Optional unit (null-terminated, can be null) +/// +/// Returns: Pointer to the UpDownCounter, or null on error. +pub fn meterCreateUpDownCounterI64( + meter: ?*OtelMeter, + name: [*:0]const u8, + description: ?[*:0]const u8, + unit: ?[*:0]const u8, +) callconv(.c) ?*OtelUpDownCounterI64 { + const m = meter orelse return null; + const zigMeter: *Meter = @ptrCast(@alignCast(m)); + + const opts = InstrumentOptions{ + .name = std.mem.span(name), + .description = if (description) |d| std.mem.span(d) else null, + .unit = if (unit) |u| std.mem.span(u) else null, + }; + + const counter = zigMeter.createUpDownCounter(i64, opts) catch return null; + return @ptrCast(counter); +} + +/// Add a value to the UpDownCounter (can be negative). +/// +/// Parameters: +/// - counter: The UpDownCounter handle +/// - value: The value to add (can be positive or negative) +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +/// +/// Returns: Status code indicating success or failure. +pub fn upDownCounterAddI64( + counter: ?*OtelUpDownCounterI64, + value: i64, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const c = counter orelse return .error_invalid_argument; + const zigCounter: *Counter(i64) = @ptrCast(@alignCast(c)); + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + zigCounter.addWithSlice(value, attrs) catch return .error_out_of_memory; + return .ok; +} + +// ============================================================================ +// Histogram API (f64) +// ============================================================================ + +/// Create a new Histogram instrument with f64 values. +/// +/// Parameters: +/// - meter: The Meter handle +/// - name: Instrument name (null-terminated) +/// - description: Optional description (null-terminated, can be null) +/// - unit: Optional unit (null-terminated, can be null) +/// +/// Returns: Pointer to the Histogram, or null on error. +pub fn meterCreateHistogramF64( + meter: ?*OtelMeter, + name: [*:0]const u8, + description: ?[*:0]const u8, + unit: ?[*:0]const u8, +) callconv(.c) ?*OtelHistogramF64 { + const m = meter orelse return null; + const zigMeter: *Meter = @ptrCast(@alignCast(m)); + + const opts = InstrumentOptions{ + .name = std.mem.span(name), + .description = if (description) |d| std.mem.span(d) else null, + .unit = if (unit) |u| std.mem.span(u) else null, + }; + + const histogram = zigMeter.createHistogram(f64, opts) catch return null; + return @ptrCast(histogram); +} + +/// Record a value in the Histogram. +/// +/// Parameters: +/// - histogram: The Histogram handle +/// - value: The value to record +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +/// +/// Returns: Status code indicating success or failure. +pub fn histogramRecordF64( + histogram: ?*OtelHistogramF64, + value: f64, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const h = histogram orelse return .error_invalid_argument; + const zigHistogram: *Histogram(f64) = @ptrCast(@alignCast(h)); + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + zigHistogram.recordWithSlice(value, attrs) catch return .error_out_of_memory; + return .ok; +} + +// ============================================================================ +// Gauge API (f64) +// ============================================================================ + +/// Create a new Gauge instrument with f64 values. +/// +/// Parameters: +/// - meter: The Meter handle +/// - name: Instrument name (null-terminated) +/// - description: Optional description (null-terminated, can be null) +/// - unit: Optional unit (null-terminated, can be null) +/// +/// Returns: Pointer to the Gauge, or null on error. +pub fn meterCreateGaugeF64( + meter: ?*OtelMeter, + name: [*:0]const u8, + description: ?[*:0]const u8, + unit: ?[*:0]const u8, +) callconv(.c) ?*OtelGaugeF64 { + const m = meter orelse return null; + const zigMeter: *Meter = @ptrCast(@alignCast(m)); + + const opts = InstrumentOptions{ + .name = std.mem.span(name), + .description = if (description) |d| std.mem.span(d) else null, + .unit = if (unit) |u| std.mem.span(u) else null, + }; + + const gauge = zigMeter.createGauge(f64, opts) catch return null; + return @ptrCast(gauge); +} + +/// Record a value in the Gauge. +/// +/// Parameters: +/// - gauge: The Gauge handle +/// - value: The value to record +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +/// +/// Returns: Status code indicating success or failure. +pub fn gaugeRecordF64( + gauge: ?*OtelGaugeF64, + value: f64, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const g = gauge orelse return .error_invalid_argument; + const zigGauge: *Gauge(f64) = @ptrCast(@alignCast(g)); + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + zigGauge.recordWithSlice(value, attrs) catch return .error_out_of_memory; + return .ok; +} + +// ============================================================================ +// MetricExporter API +// ============================================================================ + +/// Create a new stdout MetricExporter for debugging. +/// +/// Returns: Pointer to the MetricExporter, or null on error. +pub fn metricExporterStdoutCreate() callconv(.c) ?*OtelMetricExporter { + const allocator = getCAllocator(); + const result = MetricExporter.Stdout(allocator, null, null) catch return null; + return @ptrCast(result.exporter); +} + +/// Create a new in-memory MetricExporter. +/// +/// Returns: Pointer to the MetricExporter, or null on error. +pub fn metricExporterInMemoryCreate() callconv(.c) ?*OtelMetricExporter { + const allocator = getCAllocator(); + const result = MetricExporter.InMemory(allocator, null, null) catch return null; + return @ptrCast(result.exporter); +} + +// ============================================================================ +// MetricReader API +// ============================================================================ + +/// Create a new MetricReader with the given exporter. +/// +/// Parameters: +/// - exporter: The MetricExporter handle +/// +/// Returns: Pointer to the MetricReader, or null on error. +pub fn metricReaderCreate(exporter: ?*OtelMetricExporter) callconv(.c) ?*OtelMetricReader { + const e = exporter orelse return null; + const allocator = getCAllocator(); + const me: *MetricExporter = @ptrCast(@alignCast(e)); + + const reader = MetricReader.init(allocator, me) catch return null; + return @ptrCast(reader); +} + +/// Trigger a collection cycle on the MetricReader. +/// +/// Returns: Status code indicating success or failure. +pub fn metricReaderCollect(reader: ?*OtelMetricReader) callconv(.c) OtelStatus { + const r = reader orelse return .error_invalid_argument; + const mr: *MetricReader = @ptrCast(@alignCast(r)); + + mr.collect() catch |err| { + return switch (err) { + error.CollectFailedOnMissingMeterProvider => .error_invalid_state, + error.ExportFailed => .error_export_failed, + error.OutOfMemory => .error_out_of_memory, + else => .error_collect_failed, + }; + }; + + return .ok; +} + +/// Shutdown the MetricReader and release resources. +/// +/// After calling this function, the reader handle becomes invalid. +pub fn metricReaderShutdown(reader: ?*OtelMetricReader) callconv(.c) void { + if (reader) |r| { + const mr: *MetricReader = @ptrCast(@alignCast(r)); + mr.shutdown(); + } +} + +// ============================================================================ +// C Export Declarations +// ============================================================================ + +comptime { + // MeterProvider exports + @export(&meterProviderCreate, .{ .name = "otel_meter_provider_create" }); + @export(&meterProviderInit, .{ .name = "otel_meter_provider_init" }); + @export(&meterProviderShutdown, .{ .name = "otel_meter_provider_shutdown" }); + @export(&meterProviderGetMeter, .{ .name = "otel_meter_provider_get_meter" }); + @export(&meterProviderAddReader, .{ .name = "otel_meter_provider_add_reader" }); + + // Counter exports (u64 - monotonic counters use unsigned types) + @export(&meterCreateCounterU64, .{ .name = "otel_meter_create_counter_u64" }); + @export(&counterAddU64, .{ .name = "otel_counter_add_u64" }); + + // UpDownCounter exports (i64 - can go up or down) + @export(&meterCreateUpDownCounterI64, .{ .name = "otel_meter_create_updown_counter_i64" }); + @export(&upDownCounterAddI64, .{ .name = "otel_updown_counter_add_i64" }); + + // Histogram exports + @export(&meterCreateHistogramF64, .{ .name = "otel_meter_create_histogram_f64" }); + @export(&histogramRecordF64, .{ .name = "otel_histogram_record_f64" }); + + // Gauge exports + @export(&meterCreateGaugeF64, .{ .name = "otel_meter_create_gauge_f64" }); + @export(&gaugeRecordF64, .{ .name = "otel_gauge_record_f64" }); + + // MetricExporter exports + @export(&metricExporterStdoutCreate, .{ .name = "otel_metric_exporter_stdout_create" }); + @export(&metricExporterInMemoryCreate, .{ .name = "otel_metric_exporter_inmemory_create" }); + + // MetricReader exports + @export(&metricReaderCreate, .{ .name = "otel_metric_reader_create" }); + @export(&metricReaderCollect, .{ .name = "otel_metric_reader_collect" }); + @export(&metricReaderShutdown, .{ .name = "otel_metric_reader_shutdown" }); +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "metrics C API - create and use counter" { + const provider = meterProviderCreate(); + try std.testing.expect(provider != null); + defer meterProviderShutdown(provider); + + const meter = meterProviderGetMeter(provider, "test-meter", "1.0.0", null); + try std.testing.expect(meter != null); + + const counter = meterCreateCounterU64(meter, "test_counter", "A test counter", "1"); + try std.testing.expect(counter != null); + + const status = counterAddU64(counter, 42, null, 0); + try std.testing.expectEqual(OtelStatus.ok, status); +} + +test "metrics C API - create histogram and record" { + const provider = meterProviderCreate(); + try std.testing.expect(provider != null); + defer meterProviderShutdown(provider); + + const meter = meterProviderGetMeter(provider, "test-meter", null, null); + try std.testing.expect(meter != null); + + const histogram = meterCreateHistogramF64(meter, "request_duration", "Duration of requests", "ms"); + try std.testing.expect(histogram != null); + + const status = histogramRecordF64(histogram, 123.45, null, 0); + try std.testing.expectEqual(OtelStatus.ok, status); +} + +test "metrics C API - full pipeline with reader" { + const provider = meterProviderCreate(); + try std.testing.expect(provider != null); + defer meterProviderShutdown(provider); + + const exporter = metricExporterInMemoryCreate(); + try std.testing.expect(exporter != null); + + const reader = metricReaderCreate(exporter); + try std.testing.expect(reader != null); + + const add_status = meterProviderAddReader(provider, reader); + try std.testing.expectEqual(OtelStatus.ok, add_status); + + const meter = meterProviderGetMeter(provider, "test-meter", null, null); + try std.testing.expect(meter != null); + + const counter = meterCreateCounterU64(meter, "requests", null, null); + try std.testing.expect(counter != null); + + _ = counterAddU64(counter, 10, null, 0); + _ = counterAddU64(counter, 20, null, 0); + + const collect_status = metricReaderCollect(reader); + try std.testing.expectEqual(OtelStatus.ok, collect_status); +} diff --git a/src/c/trace.zig b/src/c/trace.zig new file mode 100644 index 0000000..9a04358 --- /dev/null +++ b/src/c/trace.zig @@ -0,0 +1,807 @@ +//! OpenTelemetry Tracing SDK C bindings. +//! +//! This module provides C-compatible wrappers for the Zig Tracing SDK, +//! allowing C programs to use OpenTelemetry tracing instrumentation. +//! +//! ## Usage from C +//! +//! ```c +//! #include "opentelemetry.h" +//! +//! // Create a tracer provider +//! otel_tracer_provider_t* provider = otel_tracer_provider_create(); +//! +//! // Add a span processor with stdout exporter +//! otel_span_exporter_t* exporter = otel_span_exporter_stdout_create(); +//! otel_span_processor_t* processor = otel_simple_span_processor_create(exporter); +//! otel_tracer_provider_add_span_processor(provider, processor); +//! +//! // Get a tracer +//! otel_tracer_t* tracer = otel_tracer_provider_get_tracer(provider, "my-library", "1.0.0", NULL); +//! +//! // Start a span +//! otel_span_t* span = otel_tracer_start_span(tracer, "my-operation", NULL); +//! +//! // Add attributes and events +//! otel_span_set_attribute_string(span, "key", "value"); +//! otel_span_add_event(span, "something happened", NULL, 0); +//! +//! // End the span +//! otel_span_end(span); +//! +//! // Cleanup +//! otel_tracer_provider_shutdown(provider); +//! ``` + +const std = @import("std"); +const TracerProvider = @import("../sdk/trace/provider.zig").TracerProvider; +const Tracer = @import("../sdk/trace/provider.zig").Tracer; +const TracerImpl = @import("../api/trace/tracer.zig").TracerImpl; +const trace_api = @import("../api/trace.zig"); +const Span = trace_api.Span; +const SpanKind = trace_api.SpanKind; +const Status = trace_api.Status; +const SpanContext = trace_api.SpanContext; +const TraceState = trace_api.TraceState; +const TraceFlags = trace_api.TraceFlags; +const TraceID = trace_api.TraceID; +const SpanID = trace_api.SpanID; +const Code = trace_api.Code; +const Attribute = @import("../attributes.zig").Attribute; +const AttributeValue = @import("../attributes.zig").AttributeValue; +const SpanProcessor = @import("../sdk/trace/span_processor.zig").SpanProcessor; +const SimpleProcessor = @import("../sdk/trace/span_processor.zig").SimpleProcessor; +const SpanExporter = @import("../sdk/trace/span_exporter.zig").SpanExporter; +const StdOutExporter = @import("../sdk/trace/exporters/generic.zig").StdoutExporter; +const DeprecatedStdoutExporter = @import("../sdk/trace/exporters/generic.zig").DeprecatedStdoutExporter; +const IDGenerator = @import("../sdk/trace/id_generator.zig").IDGenerator; +const RandomIDGenerator = @import("../sdk/trace/id_generator.zig").RandomIDGenerator; +const InstrumentationScope = @import("../scope.zig").InstrumentationScope; + +// ============================================================================ +// Error Codes (shared with metrics) +// ============================================================================ + +/// Error codes returned by C API functions. +pub const OtelStatus = enum(c_int) { + ok = 0, + error_out_of_memory = -1, + error_invalid_argument = -2, + error_invalid_state = -3, + error_already_shutdown = -4, + error_export_failed = -5, + error_span_not_recording = -6, + error_unknown = -99, +}; + +// ============================================================================ +// Opaque Handle Types +// ============================================================================ + +/// Opaque handle to a TracerProvider. +pub const OtelTracerProvider = opaque {}; + +/// Opaque handle to a Tracer. +pub const OtelTracer = opaque {}; + +/// Opaque handle to a Span. +pub const OtelSpan = opaque {}; + +/// Opaque handle to a SpanProcessor. +pub const OtelSpanProcessor = opaque {}; + +/// Opaque handle to a SpanExporter. +pub const OtelSpanExporter = opaque {}; + +// ============================================================================ +// Span Kind Enum +// ============================================================================ + +/// Span kind values for C API. +pub const OtelSpanKind = enum(c_int) { + internal = 0, + server = 1, + client = 2, + producer = 3, + consumer = 4, + + fn toZig(self: OtelSpanKind) SpanKind { + return switch (self) { + .internal => .Internal, + .server => .Server, + .client => .Client, + .producer => .Producer, + .consumer => .Consumer, + }; + } +}; + +// ============================================================================ +// Span Status Code Enum +// ============================================================================ + +/// Span status code values for C API. +pub const OtelSpanStatusCode = enum(c_int) { + unset = 0, + ok = 1, + @"error" = 2, + + fn toZig(self: OtelSpanStatusCode) Code { + return switch (self) { + .unset => .Unset, + .ok => .Ok, + .@"error" => .Error, + }; + } +}; + +// ============================================================================ +// Attribute Types (shared with metrics) +// ============================================================================ + +/// Attribute value types for C API. +pub const OtelAttributeValueType = enum(c_int) { + bool = 0, + int = 1, + double = 2, + string = 3, +}; + +/// A key-value attribute pair for C API. +pub const OtelAttribute = extern struct { + key: [*:0]const u8, + value_type: OtelAttributeValueType, + value: extern union { + bool_value: bool, + int_value: i64, + double_value: f64, + string_value: [*:0]const u8, + }, +}; + +// ============================================================================ +// Span Start Options +// ============================================================================ + +/// Options for starting a span. +pub const OtelSpanStartOptions = extern struct { + /// Span kind (default: internal) + kind: OtelSpanKind = .internal, + /// Attributes to set on the span (can be null) + attributes: [*c]const OtelAttribute = null, + /// Number of attributes + attr_count: usize = 0, + /// Start timestamp in nanoseconds (0 means use current time) + start_timestamp_ns: u64 = 0, +}; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/// Get the global allocator used for C bindings. +fn getCAllocator() std.mem.Allocator { + return std.heap.c_allocator; +} + +/// Convert C attribute array to Zig attribute slice. +fn convertAttributes( + allocator: std.mem.Allocator, + c_attrs: [*c]const OtelAttribute, + count: usize, +) !?[]Attribute { + if (count == 0 or c_attrs == null) return null; + + var attrs = try allocator.alloc(Attribute, count); + errdefer allocator.free(attrs); + + for (0..count) |i| { + const c_attr = c_attrs[i]; + const key = std.mem.span(c_attr.key); + + attrs[i] = .{ + .key = key, + .value = switch (c_attr.value_type) { + .bool => .{ .bool = c_attr.value.bool_value }, + .int => .{ .int = c_attr.value.int_value }, + .double => .{ .double = c_attr.value.double_value }, + .string => .{ .string = std.mem.span(c_attr.value.string_value) }, + }, + }; + } + + return attrs; +} + +/// Internal storage for a span and its associated data. +const SpanWrapper = struct { + span: Span, + tracer: *TracerImpl, + allocator: std.mem.Allocator, + + fn deinit(self: *SpanWrapper) void { + self.span.deinit(); + self.allocator.destroy(self); + } +}; + +// ============================================================================ +// TracerProvider API +// ============================================================================ + +/// Create a new TracerProvider with default random ID generator. +/// +/// Returns: Pointer to the TracerProvider, or null on error. +pub fn tracerProviderCreate() callconv(.c) ?*OtelTracerProvider { + const allocator = getCAllocator(); + + // Allocate the PRNG on the heap so it persists + const prng_ptr = allocator.create(std.Random.DefaultPrng) catch return null; + prng_ptr.* = std.Random.DefaultPrng.init(@intCast(std.time.nanoTimestamp())); + + // Create a random ID generator with the heap-allocated PRNG + const random_generator = RandomIDGenerator.init(prng_ptr.random()); + const id_generator = IDGenerator{ .Random = random_generator }; + + const provider = TracerProvider.init(allocator, id_generator) catch { + allocator.destroy(prng_ptr); + return null; + }; + return @ptrCast(provider); +} + +/// Shutdown the TracerProvider and release all resources. +/// +/// After calling this function, the provider handle becomes invalid. +pub fn tracerProviderShutdown(provider: ?*OtelTracerProvider) callconv(.c) void { + if (provider) |p| { + const tp: *TracerProvider = @ptrCast(@alignCast(p)); + tp.shutdown(); + } +} + +/// Get a Tracer from the TracerProvider. +/// +/// Parameters: +/// - provider: The TracerProvider handle +/// - name: The name of the tracer (null-terminated string) +/// - version: Optional version string (null-terminated, can be null) +/// - schema_url: Optional schema URL (null-terminated, can be null) +/// +/// Returns: Pointer to the Tracer, or null on error. +pub fn tracerProviderGetTracer( + provider: ?*OtelTracerProvider, + name: [*:0]const u8, + version: ?[*:0]const u8, + schema_url: ?[*:0]const u8, +) callconv(.c) ?*OtelTracer { + const p = provider orelse return null; + const tp: *TracerProvider = @ptrCast(@alignCast(p)); + + const scope = InstrumentationScope{ + .name = std.mem.span(name), + .version = if (version) |v| std.mem.span(v) else null, + .schema_url = if (schema_url) |s| std.mem.span(s) else null, + }; + + const tracer = tp.getTracer(scope) catch return null; + return @ptrCast(tracer); +} + +/// Add a SpanProcessor to the TracerProvider. +/// +/// Returns: Status code indicating success or failure. +pub fn tracerProviderAddSpanProcessor( + provider: ?*OtelTracerProvider, + processor: ?*OtelSpanProcessor, +) callconv(.c) OtelStatus { + const p = provider orelse return .error_invalid_argument; + const proc = processor orelse return .error_invalid_argument; + + const tp: *TracerProvider = @ptrCast(@alignCast(p)); + const sp: *SpanProcessor = @ptrCast(@alignCast(proc)); + + tp.addSpanProcessor(sp.*) catch |err| { + return switch (err) { + error.OutOfMemory => .error_out_of_memory, + error.TracerProviderShutdown => .error_already_shutdown, + }; + }; + + return .ok; +} + +/// Force flush all span processors. +/// +/// Returns: Status code indicating success or failure. +pub fn tracerProviderForceFlush(provider: ?*OtelTracerProvider) callconv(.c) OtelStatus { + const p = provider orelse return .error_invalid_argument; + const tp: *TracerProvider = @ptrCast(@alignCast(p)); + + tp.forceFlush() catch |err| { + return switch (err) { + error.TracerProviderShutdown => .error_already_shutdown, + else => .error_export_failed, + }; + }; + + return .ok; +} + +// ============================================================================ +// Tracer API +// ============================================================================ + +/// Start a new span. +/// +/// Parameters: +/// - tracer: The Tracer handle +/// - name: The name of the span (null-terminated) +/// - options: Optional span start options (can be null for defaults) +/// +/// Returns: Pointer to the Span, or null on error. +pub fn tracerStartSpan( + tracer: ?*OtelTracer, + name: [*:0]const u8, + options: ?*const OtelSpanStartOptions, +) callconv(.c) ?*OtelSpan { + const t = tracer orelse return null; + const tr: *TracerImpl = @ptrCast(@alignCast(t)); + + const allocator = getCAllocator(); + + // Convert options + var start_opts = TracerImpl.StartOptions{}; + + if (options) |opts| { + start_opts.kind = opts.kind.toZig(); + + if (opts.start_timestamp_ns != 0) { + start_opts.start_timestamp = opts.start_timestamp_ns; + } + + // Convert attributes + if (opts.attr_count > 0 and opts.attributes != null) { + start_opts.attributes = convertAttributes(allocator, opts.attributes, opts.attr_count) catch return null; + } + } + + defer if (start_opts.attributes) |attrs| allocator.free(attrs); + + // Create span wrapper + const wrapper = allocator.create(SpanWrapper) catch return null; + + wrapper.* = SpanWrapper{ + .span = tr.startSpan(allocator, std.mem.span(name), start_opts) catch { + allocator.destroy(wrapper); + return null; + }, + .tracer = tr, + .allocator = allocator, + }; + + return @ptrCast(wrapper); +} + +/// Check if the tracer is enabled. +/// +/// Returns: true if the tracer is enabled, false otherwise. +pub fn tracerIsEnabled(tracer: ?*OtelTracer) callconv(.c) bool { + const t = tracer orelse return false; + const tr: *TracerImpl = @ptrCast(@alignCast(t)); + return tr.isEnabled(); +} + +// ============================================================================ +// Span API +// ============================================================================ + +/// End a span. +/// +/// After calling this function, the span handle becomes invalid. +pub fn spanEnd(span: ?*OtelSpan) callconv(.c) void { + if (span) |s| { + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + wrapper.tracer.endSpan(&wrapper.span); + wrapper.deinit(); + } +} + +/// End a span with a specific timestamp. +/// +/// After calling this function, the span handle becomes invalid. +pub fn spanEndWithTimestamp(span: ?*OtelSpan, timestamp_ns: u64) callconv(.c) void { + if (span) |s| { + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + wrapper.span.end(timestamp_ns); + wrapper.deinit(); + } +} + +/// Set a string attribute on the span. +pub fn spanSetAttributeString( + span: ?*OtelSpan, + key: [*:0]const u8, + value: [*:0]const u8, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + wrapper.span.setAttribute(std.mem.span(key), .{ .string = std.mem.span(value) }) catch return .error_out_of_memory; + return .ok; +} + +/// Set an integer attribute on the span. +pub fn spanSetAttributeInt( + span: ?*OtelSpan, + key: [*:0]const u8, + value: i64, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + wrapper.span.setAttribute(std.mem.span(key), .{ .int = value }) catch return .error_out_of_memory; + return .ok; +} + +/// Set a double attribute on the span. +pub fn spanSetAttributeDouble( + span: ?*OtelSpan, + key: [*:0]const u8, + value: f64, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + wrapper.span.setAttribute(std.mem.span(key), .{ .double = value }) catch return .error_out_of_memory; + return .ok; +} + +/// Set a boolean attribute on the span. +pub fn spanSetAttributeBool( + span: ?*OtelSpan, + key: [*:0]const u8, + value: bool, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + wrapper.span.setAttribute(std.mem.span(key), .{ .bool = value }) catch return .error_out_of_memory; + return .ok; +} + +/// Add an event to the span. +/// +/// Parameters: +/// - span: The Span handle +/// - name: Event name (null-terminated) +/// - attributes: Array of attributes (can be null) +/// - attr_count: Number of attributes +pub fn spanAddEvent( + span: ?*OtelSpan, + name: [*:0]const u8, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + wrapper.span.addEvent(std.mem.span(name), null, attrs) catch return .error_out_of_memory; + return .ok; +} + +/// Add an event with a specific timestamp. +pub fn spanAddEventWithTimestamp( + span: ?*OtelSpan, + name: [*:0]const u8, + timestamp_ns: u64, + attributes: [*c]const OtelAttribute, + attr_count: usize, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + const allocator = getCAllocator(); + const attrs = convertAttributes(allocator, attributes, attr_count) catch return .error_out_of_memory; + defer if (attrs) |a| allocator.free(a); + + wrapper.span.addEvent(std.mem.span(name), timestamp_ns, attrs) catch return .error_out_of_memory; + return .ok; +} + +/// Set the status of the span. +pub fn spanSetStatus( + span: ?*OtelSpan, + code: OtelSpanStatusCode, + description: ?[*:0]const u8, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + const desc = if (description) |d| std.mem.span(d) else ""; + wrapper.span.setStatus(Status{ + .code = code.toZig(), + .description = desc, + }); + return .ok; +} + +/// Update the name of the span. +pub fn spanUpdateName( + span: ?*OtelSpan, + name: [*:0]const u8, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + wrapper.span.updateName(std.mem.span(name)); + return .ok; +} + +/// Record an exception on the span. +pub fn spanRecordException( + span: ?*OtelSpan, + exception_type: [*:0]const u8, + message: [*:0]const u8, + stacktrace: ?[*:0]const u8, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (!wrapper.span.isRecording()) return .error_span_not_recording; + + const st = if (stacktrace) |st| std.mem.span(st) else null; + wrapper.span.recordException( + std.mem.span(exception_type), + std.mem.span(message), + st, + null, + ) catch return .error_out_of_memory; + return .ok; +} + +/// Check if the span is recording. +pub fn spanIsRecording(span: ?*OtelSpan) callconv(.c) bool { + const s = span orelse return false; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + return wrapper.span.isRecording(); +} + +/// Get the trace ID as a hex string. +/// The buffer must be at least 33 bytes (32 hex chars + null terminator). +pub fn spanGetTraceIdHex( + span: ?*OtelSpan, + buffer: [*]u8, + buffer_size: usize, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (buffer_size < 33) return .error_invalid_argument; + + var hex_buf: [32]u8 = undefined; + const hex = wrapper.span.span_context.trace_id.toHex(&hex_buf); + @memcpy(buffer[0..32], hex); + buffer[32] = 0; + return .ok; +} + +/// Get the span ID as a hex string. +/// The buffer must be at least 17 bytes (16 hex chars + null terminator). +pub fn spanGetSpanIdHex( + span: ?*OtelSpan, + buffer: [*]u8, + buffer_size: usize, +) callconv(.c) OtelStatus { + const s = span orelse return .error_invalid_argument; + const wrapper: *SpanWrapper = @ptrCast(@alignCast(s)); + + if (buffer_size < 17) return .error_invalid_argument; + + var hex_buf: [16]u8 = undefined; + const hex = wrapper.span.span_context.span_id.toHex(&hex_buf); + @memcpy(buffer[0..16], hex); + buffer[16] = 0; + return .ok; +} + +// ============================================================================ +// SpanExporter API +// ============================================================================ + +/// Create a stdout SpanExporter for debugging. +/// +/// Returns: Pointer to the SpanExporter, or null on error. +pub fn spanExporterStdoutCreate() callconv(.c) ?*OtelSpanExporter { + const allocator = getCAllocator(); + + // Allocate the exporter on the heap + const exporter_ptr = allocator.create(DeprecatedStdoutExporter) catch return null; + exporter_ptr.* = DeprecatedStdoutExporter.init(std.fs.File.stdout().deprecatedWriter()); + + // Allocate the SpanExporter interface on the heap + const span_exporter_ptr = allocator.create(SpanExporter) catch { + allocator.destroy(exporter_ptr); + return null; + }; + span_exporter_ptr.* = exporter_ptr.asSpanExporter(); + + return @ptrCast(span_exporter_ptr); +} + +// ============================================================================ +// SpanProcessor API +// ============================================================================ + +/// Create a SimpleProcessor that exports spans immediately. +/// +/// Parameters: +/// - exporter: The SpanExporter handle +/// +/// Returns: Pointer to the SpanProcessor, or null on error. +pub fn simpleSpanProcessorCreate(exporter: ?*OtelSpanExporter) callconv(.c) ?*OtelSpanProcessor { + const e = exporter orelse return null; + const allocator = getCAllocator(); + + const exp: *SpanExporter = @ptrCast(@alignCast(e)); + + const storage = allocator.create(SimpleProcessor) catch return null; + storage.* = SimpleProcessor.init(allocator, exp.*); + + const processor_ptr = allocator.create(SpanProcessor) catch { + allocator.destroy(storage); + return null; + }; + processor_ptr.* = storage.asSpanProcessor(); + + return @ptrCast(processor_ptr); +} + +// ============================================================================ +// C Export Declarations +// ============================================================================ + +comptime { + // TracerProvider exports + @export(&tracerProviderCreate, .{ .name = "otel_tracer_provider_create" }); + @export(&tracerProviderShutdown, .{ .name = "otel_tracer_provider_shutdown" }); + @export(&tracerProviderGetTracer, .{ .name = "otel_tracer_provider_get_tracer" }); + @export(&tracerProviderAddSpanProcessor, .{ .name = "otel_tracer_provider_add_span_processor" }); + @export(&tracerProviderForceFlush, .{ .name = "otel_tracer_provider_force_flush" }); + + // Tracer exports + @export(&tracerStartSpan, .{ .name = "otel_tracer_start_span" }); + @export(&tracerIsEnabled, .{ .name = "otel_tracer_is_enabled" }); + + // Span exports + @export(&spanEnd, .{ .name = "otel_span_end" }); + @export(&spanEndWithTimestamp, .{ .name = "otel_span_end_with_timestamp" }); + @export(&spanSetAttributeString, .{ .name = "otel_span_set_attribute_string" }); + @export(&spanSetAttributeInt, .{ .name = "otel_span_set_attribute_int" }); + @export(&spanSetAttributeDouble, .{ .name = "otel_span_set_attribute_double" }); + @export(&spanSetAttributeBool, .{ .name = "otel_span_set_attribute_bool" }); + @export(&spanAddEvent, .{ .name = "otel_span_add_event" }); + @export(&spanAddEventWithTimestamp, .{ .name = "otel_span_add_event_with_timestamp" }); + @export(&spanSetStatus, .{ .name = "otel_span_set_status" }); + @export(&spanUpdateName, .{ .name = "otel_span_update_name" }); + @export(&spanRecordException, .{ .name = "otel_span_record_exception" }); + @export(&spanIsRecording, .{ .name = "otel_span_is_recording" }); + @export(&spanGetTraceIdHex, .{ .name = "otel_span_get_trace_id_hex" }); + @export(&spanGetSpanIdHex, .{ .name = "otel_span_get_span_id_hex" }); + + // SpanExporter exports + @export(&spanExporterStdoutCreate, .{ .name = "otel_span_exporter_stdout_create" }); + + // SpanProcessor exports + @export(&simpleSpanProcessorCreate, .{ .name = "otel_simple_span_processor_create" }); +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "trace C API - create tracer provider" { + const provider = tracerProviderCreate(); + try std.testing.expect(provider != null); + defer tracerProviderShutdown(provider); +} + +test "trace C API - get tracer" { + const provider = tracerProviderCreate(); + try std.testing.expect(provider != null); + defer tracerProviderShutdown(provider); + + const tracer = tracerProviderGetTracer(provider, "test-tracer", "1.0.0", null); + try std.testing.expect(tracer != null); + + try std.testing.expect(tracerIsEnabled(tracer)); +} + +test "trace C API - start and end span" { + const provider = tracerProviderCreate(); + try std.testing.expect(provider != null); + defer tracerProviderShutdown(provider); + + const tracer = tracerProviderGetTracer(provider, "test-tracer", null, null); + try std.testing.expect(tracer != null); + + const span = tracerStartSpan(tracer, "test-span", null); + try std.testing.expect(span != null); + + try std.testing.expect(spanIsRecording(span)); + + // Set some attributes + try std.testing.expectEqual(OtelStatus.ok, spanSetAttributeString(span, "key1", "value1")); + try std.testing.expectEqual(OtelStatus.ok, spanSetAttributeInt(span, "key2", 42)); + + // Add an event + try std.testing.expectEqual(OtelStatus.ok, spanAddEvent(span, "test-event", null, 0)); + + // Set status + try std.testing.expectEqual(OtelStatus.ok, spanSetStatus(span, .ok, null)); + + // End the span + spanEnd(span); +} + +test "trace C API - span with options" { + const provider = tracerProviderCreate(); + try std.testing.expect(provider != null); + defer tracerProviderShutdown(provider); + + const tracer = tracerProviderGetTracer(provider, "test-tracer", null, null); + try std.testing.expect(tracer != null); + + var options = OtelSpanStartOptions{ + .kind = .server, + }; + + const span = tracerStartSpan(tracer, "server-span", &options); + try std.testing.expect(span != null); + defer spanEnd(span); + + try std.testing.expect(spanIsRecording(span)); +} + +test "trace C API - get trace and span IDs" { + const provider = tracerProviderCreate(); + try std.testing.expect(provider != null); + defer tracerProviderShutdown(provider); + + const tracer = tracerProviderGetTracer(provider, "test-tracer", null, null); + try std.testing.expect(tracer != null); + + const span = tracerStartSpan(tracer, "test-span", null); + try std.testing.expect(span != null); + defer spanEnd(span); + + var trace_id_buf: [33]u8 = undefined; + var span_id_buf: [17]u8 = undefined; + + try std.testing.expectEqual(OtelStatus.ok, spanGetTraceIdHex(span, &trace_id_buf, 33)); + try std.testing.expectEqual(OtelStatus.ok, spanGetSpanIdHex(span, &span_id_buf, 17)); + + // Verify the IDs are valid hex strings (not all zeros typically) + try std.testing.expect(trace_id_buf[0] != 0); + try std.testing.expect(span_id_buf[0] != 0); +} diff --git a/src/sdk/trace/exporters/generic.zig b/src/sdk/trace/exporters/generic.zig index aa25d4c..b28599d 100644 --- a/src/sdk/trace/exporters/generic.zig +++ b/src/sdk/trace/exporters/generic.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const builtin = @import("builtin"); const trace = @import("../../../api/trace.zig"); const SpanExporter = @import("../span_exporter.zig").SpanExporter; @@ -35,8 +34,6 @@ const SerializableSpan = struct { } }; -var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - /// GenericWriterExporter is the generic SpanExporter that outputs spans to the given writer. fn GenericWriterExporter( comptime Writer: type, @@ -46,11 +43,6 @@ fn GenericWriterExporter( const Self = @This(); - const allocator = switch (builtin.mode) { - .Debug => debug_allocator.allocator(), - else => std.heap.smp_allocator, - }; - pub fn init(writer: Writer) Self { return Self{ .writer = writer, @@ -62,14 +54,14 @@ fn GenericWriterExporter( // Convert spans to serializable format var serializable_spans = std.ArrayList(SerializableSpan){}; - defer serializable_spans.deinit(allocator); + defer serializable_spans.deinit(std.heap.page_allocator); for (spans) |span| { - try serializable_spans.append(allocator, SerializableSpan.fromSpan(span)); + try serializable_spans.append(std.heap.page_allocator, SerializableSpan.fromSpan(span)); } // Handle both File.Writer (which has .interface) and direct Io.Writer types - var writer_interface = if (@hasField(Writer, "interface")) + const writer_interface = if (@hasField(Writer, "interface")) &self.writer.interface else &self.writer; @@ -95,6 +87,14 @@ fn GenericWriterExporter( /// ref: https://opentelemetry.io/docs/specs/otel/trace/sdk_exporters/stdout/ pub const StdoutExporter = GenericWriterExporter(std.fs.File.Writer); +/// DeprecatedStdoutExporter uses the simpler GenericWriter that doesn't require a buffer. +/// This is useful for C bindings where buffer management is more complex. +pub const DeprecatedStdoutExporter = GenericWriterExporter(std.io.GenericWriter( + std.fs.File, + std.fs.File.WriteError, + std.fs.File.write, +)); + /// InmemoryExporter exports spans to in-memory buffer. /// it is designed for testing GenericWriterExporter. pub const InMemoryExporter = GenericWriterExporter(std.ArrayList(u8).Writer);