# Languages and SDKs

Instrument services in Java, .NET, Node.js, Python, Go and more with upstream OpenTelemetry agents and SDKs, on hosts, Docker, Kubernetes or AWS Lambda.

Tracix works with the upstream OpenTelemetry agents and SDKs: there is no Tracix SDK or wrapper to install. You point a standard SDK at your workspace, and the **Setup** page writes the exact commands for your platform, language and service.

In the commands on this page, replace `<workspace>` with your workspace ID: the eight characters that end your workspace's address, before `.app.tracix.io`.

## Use the Setup page

In your workspace, open **Applications › APM & Traces**, then the **Setup** tab, and answer its questions:

1. **Where does the service run?** **Host or VM**, **Docker**, **Kubernetes** or **AWS Lambda**.
2. **Language.** Java, Kotlin, .NET, Node.js, Python, Go, Ruby, PHP, C++, Rust, Swift, Erlang/Elixir, or **Other** for any SDK that reads the standard `OTEL_*` variables.
3. **How to instrument.** The recommended method is marked **Recommended**. Methods that can't serve your language stay listed with the reason.
4. Enter the **Service name**, an optional **Service group** and, on Kubernetes, the **Cluster name**, and pick an **Ingest key**.

The commands update as you go, with your endpoint filled in. Ingest keys are shown only once, when you create them: a key you create on the page is filled into the commands until you leave or reload it, and otherwise the commands show `YOUR_INGEST_KEY` for you to replace with the key you saved. The **Verify** card beside them shows when the first traces, runtime metrics and logs arrive. If you have no ingest key yet, create one first: see [OTLP endpoint and ingest keys](https://tracix.io/docs/collect/instrument/otlp).

## Choose a method

| Where the service runs | Methods                                                                                                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Host or VM, Docker     | Your language's OpenTelemetry agent or SDK in the process (recommended), or [zero-code eBPF tracing](https://tracix.io/docs/collect/instrument/ebpf) through the Tracix Collector |
| Kubernetes             | The OpenTelemetry Operator, the SDK built into the image, or zero-code eBPF tracing                                                                              |
| AWS Lambda             | The OpenTelemetry Lambda layer for Java, Kotlin, Node.js, Python and Ruby; a handler wrapper or the SDK in your init code for the others                         |

An agent or SDK inside the process gives you full distributed traces, runtime metrics and logs. eBPF tracing needs no code, image or restart change, but runs on Linux only and doesn't link requests across services by default.

On Kubernetes, the Operator is recommended for Java, Kotlin, .NET, Node.js and Python. For Go, building the SDK into the image is recommended, because the Operator injects Go as an eBPF sidecar that sends traces only. The Operator can't inject Ruby, PHP, C++, Rust, Swift or Erlang/Elixir, so build the SDK into the image for those.

## What each language collects

What the Setup page installs, and what you change in your code:

| Language       | What Setup installs             | Code changes                                | Runtime metrics                              |
| -------------- | ------------------------------- | ------------------------------------------- | -------------------------------------------- |
| Java, Kotlin   | OpenTelemetry Java agent        | None                                        | JVM heap, GC, threads, classes and CPU       |
| .NET           | .NET automatic instrumentation  | None                                        | GC, thread pool, lock contention, exceptions |
| Node.js        | Node.js auto-instrumentation    | None                                        | Event loop, V8 heap and GC                   |
| Python         | Python auto-instrumentation     | None                                        | GC, CPU, memory and threads                  |
| Go             | OpenTelemetry Go SDK            | A setup function, and wrappers for handlers | Goroutines, memory, GC and scheduler latency |
| Ruby           | OpenTelemetry Ruby SDK          | A Gemfile entry and an initializer          | Not reported yet                             |
| PHP            | PHP auto-instrumentation        | A PHP extension and Composer packages       | Not reported                                 |
| C++            | OpenTelemetry C++ SDK           | Spans in your code                          | Not reported                                 |
| Rust           | OpenTelemetry Rust SDK          | Setup and spans in your code                | Not reported                                 |
| Swift (server) | swift-otel                      | A package and a few lines at startup        | Not reported                                 |
| Erlang/Elixir  | OpenTelemetry Erlang/Elixir SDK | Dependencies and a setup call               | Not reported                                 |

What gets traced, and which logs are sent:

| Language       | Traces                                                                         | Logs                                                    |
| -------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |
| Java, Kotlin   | Spring, Servlet, JAX-RS, gRPC, JDBC, Kafka, JMS and many more; Ktor for Kotlin | Logback, Log4j and java.util.logging, with trace IDs    |
| .NET           | ASP.NET Core, HttpClient, gRPC, SqlClient, Entity Framework Core               | ILogger, with trace IDs                                 |
| Node.js        | Express, Fastify, NestJS, Koa, GraphQL, HTTP, databases and queues             | Pino and Winston, with trace IDs                        |
| Python         | Django, Flask, FastAPI, requests, httpx, SQLAlchemy, Celery                    | Standard-library logging, with trace IDs                |
| Go             | net/http, gRPC and database/sql through the contrib wrappers                   | slog, Zap and Logrus, through the OpenTelemetry bridges |
| Ruby           | Rails, Sinatra, Rack, ActiveRecord, Faraday, Redis                             | Structured stdout, collected by the Tracix Collector    |
| PHP            | Laravel, Symfony, Slim, PDO, Guzzle, PSR-18 clients                            | Monolog, with trace IDs                                 |
| C++            | Spans from the C++ API                                                         | Add a logger provider yourself                          |
| Rust           | Spans from the OpenTelemetry API and the `tracing` crate                       | `tracing` events, with trace IDs                        |
| Swift (server) | Hummingbird requests and swift-distributed-tracing spans                       | swift-log, with trace IDs                               |
| Erlang/Elixir  | Phoenix, Bandit, Cowboy, Ecto, Oban, Finch, Req                                | Not exported yet                                        |

The Ruby, Rust and Erlang/Elixir SDKs mark parts of their own support as experimental or beta upstream. Data from every method lands on the same pages: **Services**, **Traces** and each service's **Runtime** tab.

## Example: run a service on a host

Install the agent, set the exporter variables and start the service:

#### Node.js

```bash
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node

export OTEL_SERVICE_NAME="checkout-api"
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.0.0,deployment.environment.name=production"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-http-<workspace>.app.tracix.io"
export OTEL_EXPORTER_OTLP_HEADERS="x-tracix-ingest-key=<ingest key>"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"

node app.js
```

#### Python

```bash
pip install opentelemetry-distro opentelemetry-exporter-otlp opentelemetry-instrumentation-system-metrics
opentelemetry-bootstrap -a install

export OTEL_SERVICE_NAME="checkout-api"
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.0.0,deployment.environment.name=production"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-http-<workspace>.app.tracix.io"
export OTEL_EXPORTER_OTLP_HEADERS="x-tracix-ingest-key=<ingest key>"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED="true"

opentelemetry-instrument python app.py
```

The Python distro defaults to gRPC, which is why `OTEL_EXPORTER_OTLP_PROTOCOL` is set to `http/protobuf` here.

For the other languages, the **Setup** page shows the same steps with your endpoint and key filled in.

## Docker

On **Docker**, the Setup page gives you a Dockerfile that builds the agent or SDK into the image, and a `docker run` command. The endpoint and ingest key are passed at run time, so they never end up in the image:

```bash
docker run -d --name checkout-api \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-http-<workspace>.app.tracix.io" \
  -e OTEL_EXPORTER_OTLP_HEADERS="x-tracix-ingest-key=<ingest key>" \
  checkout-api:latest
```

## Kubernetes with the OpenTelemetry Operator

The [OpenTelemetry Operator](https://opentelemetry.io/docs/platforms/kubernetes/operator/) injects the agent into pods as they start, so you don't change your images. It is upstream software you run in your cluster; Tracix only gives it the endpoint and key.

**1. Install the Operator**

Once per cluster, install it with Helm. Its admission webhook needs cert-manager, or the chart's self-generated certificate. Skip this if the Operator already runs in the cluster.

```bash
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update open-telemetry
helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
  --namespace opentelemetry-operator-system --create-namespace \
  --set manager.collectorImage.repository=ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-k8s
```

**2. Store the ingest key in the application's namespace**

```bash
kubectl create secret generic tracix-apm -n <app-namespace> \
  --from-literal=otlp-headers="x-tracix-ingest-key=<ingest key>"
```

**3. Create the Instrumentation resource**

The Setup page writes a `tracix-apm.yaml` with an `Instrumentation` named `tracix-apm`. It sends traces, metrics and logs over OTLP/HTTP to your workspace, reads the header from the secret, and sets `deployment.environment.name` and `k8s.cluster.name`. Apply it in the application's namespace with `kubectl apply -n <app-namespace> -f tracix-apm.yaml`.

**4. Opt workloads in**

Annotate the namespace, or one Deployment's pod template, for your language, then restart its pods:

```bash
kubectl annotate namespace <app-namespace> \
  instrumentation.opentelemetry.io/inject-java=tracix-apm
kubectl rollout restart deployment -n <app-namespace>
```

Use `inject-dotnet`, `inject-nodejs` or `inject-python` for those languages. Alpine-based Python and .NET images need an extra pod annotation, which the Setup page shows.

Services are named after their Deployment, StatefulSet or DaemonSet. To name one differently, set the `resource.opentelemetry.io/service.name` annotation on its pod template.

The Setup page can also give you OpenTelemetry Collectors for the Operator to run, which collect container logs, kubelet metrics and cluster events with the same ingest key. To collect those with the Tracix Collector instead, [install it with Helm](https://tracix.io/docs/collect/collector/install) on a policy with the Kubernetes integration.

## AWS Lambda

For Java, Kotlin, Node.js, Python and Ruby, add the OpenTelemetry Lambda layer for your runtime and set the function's environment:

| Variable                                                                                   | Value                                                                 |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `AWS_LAMBDA_EXEC_WRAPPER`                                                                  | `/opt/otel-instrument` for Python; `/opt/otel-handler` for the others |
| `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_EXPORTER_OTLP_PROTOCOL` | As on a host                                                          |

The OpenTelemetry Lambda project publishes the layer ARNs for each region. For .NET and Go, the Setup page shows the handler wrapper to add; for the other languages, set the SDK up in the function's init code and flush it before each invocation returns. The layer adds some cold-start time.

## Environment variables

Every in-process setup on the Setup page sets these:

| Variable                                                              | Value                                                                                         |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `OTEL_SERVICE_NAME`                                                   | The service's name                                                                            |
| `OTEL_RESOURCE_ATTRIBUTES`                                            | `deployment.environment.name`, `service.version` and, optionally, `tracix.service_group.name` |
| `OTEL_EXPORTER_OTLP_ENDPOINT`                                         | `https://otlp-http-<workspace>.app.tracix.io`                                                 |
| `OTEL_EXPORTER_OTLP_HEADERS`                                          | `x-tracix-ingest-key=<ingest key>`                                                            |
| `OTEL_EXPORTER_OTLP_PROTOCOL`                                         | `http/protobuf`                                                                               |
| `OTEL_TRACES_EXPORTER`, `OTEL_METRICS_EXPORTER`, `OTEL_LOGS_EXPORTER` | `otlp`, for the SDKs that read them                                                           |

`service.version=1.0.0` in the generated commands is a placeholder: set it to the version you release. `tracix.service_group.name` puts the service in a [service group](https://tracix.io/docs/observe/apm); the key's default group applies when you leave it out.

## Next steps

- [Zero-code eBPF tracing](https://tracix.io/docs/collect/instrument/ebpf): Trace services you can't change

- [Sampling](https://tracix.io/docs/collect/instrument/sampling): Decide which traces Tracix stores

- [APM and tracing](https://tracix.io/docs/observe/apm): Explore services, traces and errors
