Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "opentelemetry-proto/proto-src"]
path = opentelemetry-proto/proto-src
url = https://github.com/open-telemetry/opentelemetry-proto.git
branch = main
143 changes: 6 additions & 137 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
<p><img title="Zero OTel logo" src="images/zero-otel.png" width="320"></p>

**[Zig docs] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Installation](#installation) &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Features](#features) &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Examples](#examples) &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Modules](#modules) &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Contributing](#contributing) &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Community](#join-the-community)**

Expand All @@ -31,142 +29,13 @@ The version of the OpenTelemetry specification targeted here is **1.48.0**.
> [!IMPORTANT]
> We are currently seeking additional contributors! See [help wanted](#help-wanted) for details.

## Installation
## Modules

Run the following command to add the package to your `build.zig.zon` dependencies, replacing `<ref>` with a release version:
This repository is organized as multiple self-contained modules, each in its own
top-level directory with its own README:

```shall
zig fetch --save "git+https://github.com/open-telemetry/opentelemetry-zig#<ref>"
```

Then in your `build.zig`:

```zig
const sdk = b.dependency("opentelemetry-sdk", .{
.target = target,
.optimize = optimize,
});
```

## Specification Support State

### Signals

| Signal | Status |
|--------|--------|
| Traces | ✅ |
| Metrics | ✅ |
| Logs | ✅ |
| Profiles | ❌ |

### OTLP Protocol

| Feature | Status |
|---------|--------|
| HTTP/Protobuf | ✅ |
| HTTP/JSON | ✅ |
| gRPC | ❌ |
| Compression (gzip) | ✅ |


## Features

### `std.log` Bridge for Seamless Migration

The SDK includes a bridge that allows you to route Zig's standard `std.log` calls to OpenTelemetry without refactoring your entire codebase. This is perfect for gradual adoption of observability.

**Quick Start:**

```zig
const std = @import("std");
const sdk = @import("opentelemetry-sdk");

// Override std.log to use OpenTelemetry
pub const std_options: std.Options = .{
.logFn = sdk.logs.std_log_bridge.logFn,
};

pub fn main() !void {
var provider = try sdk.logs.LoggerProvider.init(allocator, null);
defer provider.deinit();

// Configure the bridge
try sdk.logs.std_log_bridge.configure(.{
.provider = provider,
.also_log_to_stderr = true, // Dual mode: OTel + stderr
});
defer sdk.logs.std_log_bridge.shutdown();

// Now std.log calls automatically go to OpenTelemetry!
std.log.info("Application started", .{});
}
```

**Key Features:**
- **Dual-mode logging**: Send logs to both OpenTelemetry and stderr during migration
- **Thread-safe**: Safe for concurrent use across multiple threads
- **Scope strategies**: Single scope for all logs, or separate scopes per Zig module
- **Automatic severity mapping**: Zig log levels map to OpenTelemetry severity numbers
- **Source location tracking**: Optional file/line information as attributes

See [examples/logs/std_log_basic.zig](./examples/logs/std_log_basic.zig) and [examples/logs/std_log_migration.zig](./examples/logs/std_log_migration.zig) for complete examples.

## 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`.

## Examples

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
- **[opentelemetry-sdk](./opentelemetry-sdk/README.md)** - the OpenTelemetry API and SDK: traces, metrics, logs, baggage, OTLP exporters, and C bindings. Start here for installation, features, and usage.
- **[opentelemetry-proto](./opentelemetry-proto/README.md)** - Zig protobuf bindings for the OpenTelemetry (OTLP) data model, generated from the official `.proto` definitions.

## Contributing

Expand Down
21 changes: 9 additions & 12 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,24 @@ const std = @import("std");
const zon = @import("build.zig.zon");

const helpers = @import("build/helpers.zig");
const proto_build = @import("build/proto/build.zig");
const sdk_build = @import("build/sdk/build.zig");

pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe });

const benchmarks_mod = b.dependency("zbench", .{}).module("zbench");

// OpenTelemetry proto package ships protobuf as a dependency so we'll use it.
const otel_pb_dep = b.dependency("opentelemetry_proto", .{});

const otel_proto_mod = otel_pb_dep.module("opentelemetry-proto");
const protobuf_mod = otel_pb_dep.builder.dependency("protobuf", .{
const protobuf_mod = b.dependency("protobuf", .{
.optimize = optimize,
.target = target,
}).module("protobuf");

var sdk_build_mods = helpers.BuildModules.init(b.allocator);
defer sdk_build_mods.deinit();
var build_mods = helpers.BuildModules.init(b.allocator);
defer build_mods.deinit();

try sdk_build_mods.put("protobuf", protobuf_mod);
try sdk_build_mods.put("opentelemetry-proto", otel_proto_mod);
try sdk_build_mods.put("benchmark", benchmarks_mod);
try build_mods.put("protobuf", protobuf_mod);
try build_mods.put("benchmark", benchmarks_mod);

const compilation_info = helpers.CompilationInfo{
.target = target,
Expand All @@ -33,5 +28,7 @@ pub fn build(b: *std.Build) !void {
.version = zon.version,
};

try sdk_build.Setup(b, compilation_info, &sdk_build_mods);
// proto exposes the "opentelemetry-proto" module the SDK imports so it needs to be built first.
try proto_build.Setup(b, compilation_info, &build_mods);
try sdk_build.Setup(b, compilation_info, &build_mods);
}
9 changes: 6 additions & 3 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@
.url = "git+https://github.com/hendriknielaender/zBench?ref=v0.13.0#b2b89c475e3ef1bb2bd71255c80478a82d3e0ca8",
.hash = "zbench-0.13.0-YTdc7xVBAQCCMC-IdLLuotBeiNNNm8k9Pi2V4VYqrwfI",
},
.opentelemetry_proto = .{
.url = "git+https://github.com/zig-o11y/opentelemetry-proto?ref=main#c43bd4c4db96fd839c594b7b701b44866b19457e",
.hash = "opentelemetry_proto-1.10.3-S4Y0u6zjAgBXl5JQ75DnsV5L2DsJeRzbgbYLSDTkl-f9",
// Consumed by the vendored opentelemetry-proto module (see
// build/proto/build.zig) and, transitively, by the SDK.
.protobuf = .{
.url = "git+https://github.com/Arwalk/zig-protobuf?ref=v5.0.0#1f6cb06156b3ff4f52f98bc14f829c0b8393a6fc",
.hash = "protobuf-5.0.0-0e82avKUKAAVwTWJzTIEZ14Fu0zC11_lElR8tE6H__y1",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"build",
"opentelemetry-proto",
"opentelemetry-sdk",
"LICENSE",
"README.md",
Expand Down
123 changes: 123 additions & 0 deletions build/proto/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const std = @import("std");
const protobuf = @import("protobuf");
const helpers = @import("../helpers.zig");

const BuildError = helpers.BuildError;
const BuildModules = helpers.BuildModules;
const CompilationInfo = helpers.CompilationInfo;

// Path of the vendored OpenTelemetry proto source root relative to build.zig.
const proto_root = "opentelemetry-proto";

// OpenTelemetry proto definitions to generate Zig bindings from, relative to
// the proto-src submodule root. Add entries here as the API grows.
const proto_files = [_][]const u8{
// Signals
"opentelemetry/proto/common/v1/common.proto",
"opentelemetry/proto/resource/v1/resource.proto",
"opentelemetry/proto/metrics/v1/metrics.proto",
"opentelemetry/proto/trace/v1/trace.proto",
"opentelemetry/proto/logs/v1/logs.proto",
"opentelemetry/proto/profiles/v1development/profiles.proto",
// Collector types for OTLP
"opentelemetry/proto/collector/metrics/v1/metrics_service.proto",
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
"opentelemetry/proto/collector/logs/v1/logs_service.proto",
"opentelemetry/proto/collector/profiles/v1development/profiles_service.proto",
};

// Sets up the opentelemetry-proto module from the vendored generated bindings
// and registers its build steps. The created module is added to `dependencies`
// so other modules (e.g. the SDK) can import it.
pub fn Setup(
b: *std.Build,
info: CompilationInfo,
dependencies: *BuildModules,
) !void {
const protobuf_mod = dependencies.get("protobuf") orelse return BuildError.ModuleNotFound;

const proto_mod = b.addModule("opentelemetry-proto", .{
.root_source_file = b.path(proto_root ++ "/src/root.zig"),
.target = info.target,
.optimize = info.optimize,
.imports = &.{
.{ .name = "protobuf", .module = protobuf_mod },
},
});
try dependencies.put("opentelemetry-proto", proto_mod);

_ = try addTestStep(b, proto_mod);
_ = addUpdateTagStep(b);
try addGenerateStep(b, info);
}

// Registers the "proto-generate" step, regenerating the Zig bindings under
// src/ from the proto-src submodule using protoc. Run it after updating
// the submodule (see the "update-tag" step).
fn addGenerateStep(b: *std.Build, info: CompilationInfo) !void {
// protoc code generation is driven by the protobuf dependency's builder.
const protobuf_dep = b.dependency("protobuf", .{
.target = info.target,
.optimize = info.optimize,
});

var source_files: [proto_files.len]std.Build.LazyPath = undefined;
for (&source_files, proto_files) |*src, rel| {
src.* = b.path(b.pathJoin(&.{ proto_root, "proto-src", rel }));
}

const protoc_step = protobuf.RunProtocStep.create(protobuf_dep.builder, info.target, .{
.destination_directory = b.path(proto_root ++ "/src"),
.source_files = &source_files,
.include_directories = &.{
// Imports in proto files resolve against the submodule root.
b.path(proto_root ++ "/proto-src"),
},
});
protoc_step.verbose = info.optimize == .Debug;

const step = b.step("proto-generate", "Regenerate proto bindings from the proto-src submodule (requires protoc)");
step.dependOn(&protoc_step.step);
}

// Registers the "update-tag" step, moving the proto-src submodule (the official
// OpenTelemetry proto definitions) to a given tag, or the latest commit on its
// tracked branch. Regenerate the bindings from the updated submodule afterwards.
fn addUpdateTagStep(b: *std.Build) *std.Build.Step {
const tag = b.option([]const u8, "tag",
\\Tag of the OpenTelemetry proto submodule to check out.
\\If not set, the latest commit on the tracked branch (main) is used.
);

// Pull the latest commit on the submodule's tracked branch.
const update_remote = b.addSystemCommand(&.{ "git", "submodule", "update", "--remote" });

const update_step = b.step("proto-update-tag", "Update the OpenTelemetry proto submodule to -Dtag (or latest)");

if (tag) |t| {
const update_to_tag = b.addSystemCommand(&.{
"git", "submodule", "foreach",
b.fmt("git checkout {s}", .{t}),
});
update_to_tag.step.dependOn(&update_remote.step);
update_step.dependOn(&update_to_tag.step);
} else {
update_step.dependOn(&update_remote.step);
}

return update_step;
}

// Registers the "proto-test" step, building and running the proto unit tests.
fn addTestStep(b: *std.Build, proto_mod: *std.Build.Module) !*std.Build.Step {
const step = b.step("proto-test", "Run opentelemetry-proto unit tests");

const proto_tests = b.addTest(.{
.root_module = proto_mod,
.filters = b.args orelse &[0][]const u8{},
});
const run_proto_tests = b.addRunArtifact(proto_tests);
step.dependOn(&run_proto_tests.step);

return step;
}
Loading
Loading