cd /news/developer-tools/building-an-opentelemetry-instrumentโ€ฆ ยท home โ€บ topics โ€บ developer-tools โ€บ article
[ARTICLE ยท art-102723] src=dev.to โ†— pub= topic=developer-tools verified=true sentiment=โ†‘ positive

Building an OpenTelemetry Instrumentation Wizard

An engineer has built the OpenTelemetry (OTel) Instrumentation Wizard, a Streamlit and Python tool that automatically injects production-grade OTel traces, metrics, and logs across multiple programming languages while preserving existing business logic. The wizard uses a multi-stage pipeline with language detection, AST/Regex analysis, safety validation, user review, and code injection, targeting official OTel semantic conventions across seven languages.

read16 min views2 publishedAug 19, 2026

Accelerating observability adoption by automating OpenTelemetry instrumentation across heterogeneous codebases

Years ago, I was tasked with building OpenTelemetry metric collection for a client who wanted IBM Instana's capabilities without deploying the Instana agent to their production servers. Achieving that required meticulous, time-consuming effort to instrument their Go and Python applications manually. Recently, I faced almost the exact same challenge - except this time, I had Bob as an AI collaborator. Leveraging Bob, I set out to build an industrialized, universal instrumentation solution to solve the problem at scale.

Adding telemetry to existing applications is often tedious, manual, and error-prone. To solve this, we designed the OpenTelemetry (OTel) Instrumentation Wizard - a tool built with Streamlit and Python that automatically injects production-grade OTel traces, metrics, and logs across multiple programming languages while strictly preserving existing business logic. What follows is the implementation and the components.

The wizard operates as a structured multi-stage pipeline. Source files pass from initial language detection through AST/Regex analysis, safety validation, user review, and code injection before returning fully instrumented outputs.

OTel-Wizard: injected

] markers, guaranteeing zero disruption to original runtime logic.

otel-wizard/
โ”œโ”€โ”€ app.py                     Streamlit UI (3-step wizard)
โ”œโ”€โ”€ injectors/
โ”‚   โ”œโ”€โ”€ base_injector.py       Abstract base + InjectionResult dataclass
โ”‚   โ”œโ”€โ”€ python_injector.py     AST-aware Python injector
โ”‚   โ”œโ”€โ”€ go_injector.py         Go import + initTracer() injector
โ”‚   โ”œโ”€โ”€ java_injector.py       Java try-with-resources span injector
โ”‚   โ”œโ”€โ”€ javascript_injector.py Node.js SDK setup + span injector
โ”‚   โ”œโ”€โ”€ typescript_injector.py ES import syntax + typed Tracer injector
โ”‚   โ””โ”€โ”€ c_injector.py          C header + otel_start/end_span injector
โ”œโ”€โ”€ utils/
โ”‚   โ”œโ”€โ”€ language_detector.py   Extension + heuristic language detection
โ”‚   โ””โ”€โ”€ diff_viewer.py         Unified text diff + colour-coded HTML diff
โ”œโ”€โ”€ samples/                   Six hello-world files (pre-injection)
โ”œโ”€โ”€ tests/                     pytest suite (8 test files, 128 assertions)
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ .env.example
โ””โ”€โ”€ run_tests.sh

The wizard targets official OpenTelemetry semantic conventions across 7 key languages, applying tailored injection strategies according to each language's SDK maturity:

| Language                | Traces   | Metrics  | Logs     | Injection Strategy                                  |
| ----------------------- | -------- | -------- | -------- | --------------------------------------------------- |
| Python (Generic)        | โœ… Stable | โœ… Stable | โœ… Dev    | AST-based (`ast` module)                            |
| Python - Flask          | โœ… Stable | โœ… Stable | โœ… Dev    | AST + `FlaskInstrumentor` middleware                |
| Python - FastAPI        | โœ… Stable | โœ… Stable | โœ… Dev    | AST + `FastAPIInstrumentor` + ASGI hook             |
| Python - Django         | โœ… Stable | โœ… Stable | โœ… Dev    | AST + `DjangoInstrumentor` + `Psycopg2Instrumentor` |
| JavaScript / TypeScript | โœ… Stable | โœ… Stable | โš ๏ธ Dev    | Regex + template                                    |
| Go                      | โœ… Stable | โœ… Stable | โš ๏ธ Beta   | Regex + template                                    |
| Java                    | โœ… Stable | โœ… Stable | โœ… Stable | Template overlay                                    |
| C# / .NET               | โœ… Stable | โœ… Stable | โœ… Stable | ActivitySource template                             |
| Ruby                    | โœ… Stable | โš ๏ธ Dev    | โš ๏ธ Dev    | Regex + template                                    |
| PHP                     | โœ… Stable | โœ… Stable | โœ… Stable | Regex + template                                    |

> Signal status reflects the official [OpenTelemetry Language Status](https://opentelemetry.io/docs/languages/) as of 2025.

Python Supported Frameworks

Special Python based frameworks are also implemented in the wizard;

| Framework   | What Gets Injected                                           |
| ----------- | ------------------------------------------------------------ |
| **Generic** | SDK imports, `TracerProvider` init, span context managers around all functions |
| **Flask**   | All of the above + `FlaskInstrumentor().instrument_app(app, excluded_urls=...)` + `RequestsInstrumentor().instrument()` |
| **FastAPI** | All of the above + `FastAPIInstrumentor.instrument_app(app, excluded_urls=..., exclude_spans=["receive"])` + `HTTPXClientInstrumentor().instrument()` |
| **Django**  | All of the above + `DjangoInstrumentor().instrument(request_hook=..., is_sql_commentor_enabled=True)` + `Psycopg2Instrumentor().instrument()` - inserted **before** `get_wsgi_application()` / `get_asgi_application()` |

Framework-specific

requirements.txt

entries are also generated automatically.

Users retain full control over how telemetry is applied across their services through the wizard's configuration engine:

service.name

and service.version

resource attributes dynamically.otlp-http

, otlp-grpc

, console

, and prometheus

endpoints.The architecture behind the OpenTelemetry Instrumentation Wizard follows a clean four-phase pipeline: Detection โ†’ Analysis โ†’ Injection โ†’ Reporting.

wizard/core/models.py

)

@dataclass
class WizardConfig:
    service_name: str = "my-service"
    service_version: str = "1.0.0"
    exporter: str = "otlp-http"
    otlp_endpoint: str = "http://localhost:4318"
    signals: list = field(default_factory=lambda: ["traces", "metrics", "logs"])
    depth: str = "medium"  # shallow | medium | deep
    sampling_ratio: float = 1.0

@dataclass
class InjectionPoint:
    signal: str          # "trace" | "metric" | "log"
    kind: str            # "span" | "counter" | "provider_init" ...
    location: str        # "function:<name>" | "file_top"
    reason: str
    code_preview: str
    enabled: bool = True

wizard/core/detector.p

y)

def detect_language(filename: str, source: str = "") -> dict:
    _, ext = os.path.splitext(filename.lower())
    if ext in EXTENSION_MAP:
        lang = EXTENSION_MAP[ext]
        return {"language": "javascript" if lang == "typescript" else lang, "confidence": "high"}

    if source:
        first_line = source.splitlines()[0] if source.splitlines() else ""
        if first_line.startswith("#!"):
            for pattern, lang in SHEBANG_PATTERNS:
                if re.search(pattern, first_line, re.IGNORECASE):
                    return {"language": lang, "confidence": "high"}

- AST & Pattern Analysis ( wizard/core/analyzer.py)

def generate_plan(source: str, language: str, filename: str, config: WizardConfig) -> InstrumentationPlan:
    plan = InstrumentationPlan(language=language, config=config)

    check = check_existing_instrumentation(source, language)
    if check["already_instrumented"]:
        plan.already_instrumented = True
        return plan

    plan.dependencies = _get_dependencies(language, config.signals)
    functions = _detect_functions(source, language, config.depth)

    for line_no, func_name, kind in functions:
        if "traces" in config.signals:
            plan.injection_points.append(InjectionPoint(
                signal="trace",
                kind="span",
                location=f"function:{func_name}",
                reason=f"Wrap '{func_name}' with a trace span for distributed tracing",
                code_preview=f"span '{func_name}' (line {line_no})"
            ))
    return plan

wizard/core/injector.py

)

def inject(source: str, plan: InstrumentationPlan, filename: str = "") -> InjectionResult:
    language = plan.language

    if language == "python":
        from wizard.languages.python_injector import PythonInjector
        injector = PythonInjector(plan)
    elif language == "javascript":
        from wizard.languages.js_injector import JSInjector
        injector = JSInjector(plan)

    return injector.inject(source)

wizard/app.py

) The main entry point ties the interactive user workflow together across four clear user steps:

detect_language()

.generate_plan()

to output an itemized data frame of target span hooks and missing dependencies.inject()

upon user confirmation.generate_report()

and packs the modified source, package metadata (requirements.txt

, package.json

, etc.), and markdown report into an in-memory ZIP archive.The core wizard module includes these language-specific injector implementations:

wizard/core/injector.py

) maps source languages to these concrete injector classes:

| Language                        | Injector Class       | Key Strategy & Tracing Mechanism                             |
| ------------------------------- | -------------------- | ------------------------------------------------------------ |
| **Python**  PY                  | `PythonInjector`  PY | Uses standard `ast` parsing to inject OTel imports, provider initializers, and wrap function bodies using `with _otel_tracer.start_as_current_span(...)` context managers.  PY |
| **JavaScript / TypeScript**  PY | `JSInjector`  PY     | Prepends tracer setup headers, generates an accompanying `instrumentation.js` file, and wraps functions with `_otelTracer.startActiveSpan(...)`.  PY |
| **Go**  PY                      | `GoInjector`  PY     | Injects `go.opentelemetry.io/otel` imports, adds an `initOtelProvider` initialization function, and inserts `ctx, _otelSpan := _otelTracer.Start(...)` with `defer _otelSpan.End()`.  PY |
| **Java**  PY                    | `JavaInjector`  PY   | Appends static `OpenTelemetry` class initializers and wraps public/protected method bodies using `try (Scope _ = ...)` and `finally { _otelSpan.end(); }` blocks.  PY |
| **C# / .NET**  PY               | `DotNetInjector`  PY | Injects native `System.Diagnostics.ActivitySource` fields and wraps methods using `using var _otelActivity = _otelSource.StartActivity(...)` with `try/catch` exception recording.  PY |
| **C**  PY                       | `CInjector`  PY      | Scans line-by-line using regex for C function signatures, injects `otel_init()` inside `main()`, and inserts macro/pointer span tracking blocks at function entry points.  PY |
| **PHP**  PY                     | `PHPInjector`  PY    | Prepends a PHP OTel bootstrap block and wraps function bodies in `startSpan()`, `activate()`, and `try/catch/finally` blocks.  PY |
| **Ruby**  PY                    | `RubyInjector`  PY   | Prepends `require 'opentelemetry/sdk'`, configures the global SDK, and wraps method bodies inside `_OTEL_TRACER.in_span(...) do ... end` blocks.  PY |

// [OTel-Wizard: injected]

(or # [OTel-Wizard: injected])

for easy auditing and tracing.requirements.txt

, package.json

, go.mod

, pom.xml

, CMakeLists.txt

, Gemfile

, or CLI package commands.In the OTel Instrumentation Wizard, injectors are specialized components responsible for parsing source code in a target programming language, inserting required OpenTelemetry SDK setup/imports, and wrapping key execution blocks (such as entry points or main functions) with span tracking logic.

Base Abstraction & Lifecycle Control ( BaseInjector): Establishes a common

inject(source_code, filename)

contract and returns an InjectionResult

dataclass containing the modified source code, language metadata, and a human-readable list of changes. It provides shared helpers to enforce non-empty source checks (_require_non_empty

), detect pre-existing OTel instrumentation (_already_has_otel

), and scan for common entry points across languages (_find_main_function

). Tracer Provider Initialisation: Prepends or injects global tracer configuration, SDK setups, or SDK bootstrap calls suitable for the target runtime environment.

Span Wrapping & Lifecycle Management: Identifies entry functions (e.g., main()

, if __name__ == "__main__":

) and injects code to start spans at entry and guarantee proper span finalisation/teardown via native language constructs (such as try-with-resources

, defer

, try/finally

, or with

blocks).

Idempotency Safeguards: Checks for pre-existing OTel markers (e.g., opentelemetry

, go.opentelemetry.io/otel

) prior to modifying files, skipping injection if the source code is already instrumented.

PythonInjector

) Inserts OTel imports, sets up a TracerProvider

and ConsoleSpanExporter

, and wraps the body of def main():

using Python's with tracer.start_as_current_span(...)

context manager while handling indentation.

Code Excerpt (injectors/python_injector.py

):

_OTEL_IMPORTS = """\
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
    ConsoleSpanExporter,
)
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
"""

_OTEL_PROVIDER_INIT = """\
_otel_resource = Resource(attributes={SERVICE_NAME: __name__})
_otel_provider = TracerProvider(resource=_otel_resource)
_otel_provider.add_span_processor(
    BatchSpanProcessor(ConsoleSpanExporter())
)
trace.set_tracer_provider(_otel_provider)
tracer = trace.get_tracer(__name__)
"""

GoInjector

) Injects go.opentelemetry.io/otel

packages into import (...)

blocks, appends an initTracer()

helper function, and modifies func main()

to initialize the provider with defer tp.Shutdown(ctx)

and defer span completion via defer span.End()

.

Code Excerpt (injectors/go_injector.py

):

_MAIN_PREAMBLE = """\
    // [OTel] Initialise tracer
    ctx := context.Background()
    tp, err := initTracer()
    if err != nil {
        log.Fatalf("failed to initialise tracer: %v", err)
    }
    defer func() {
        if shutdownErr := tp.Shutdown(ctx); shutdownErr != nil {
            log.Printf("tracer shutdown error: %v", shutdownErr)
        }
    }()
    tracer := otel.Tracer("main")
    ctx, span := tracer.Start(ctx, "main") // [OTel] root span
    defer span.End()
    _ = ctx // use ctx in downstream calls
"""

JavaInjector

) Inserts io.opentelemetry.api

imports after package or existing import statements and wraps public static void main(String[] args)

with a try-with-resources

block using Scope scope = span.makeCurrent()

and explicit error reporting in a catch

block.

Code Excerpt (injectors/java_injector.py

):

otel_preamble = (
    f"\n{body_indent}// [OTel] Initialise tracer\n"
    f"{body_indent}OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();\n"
    f"{body_indent}Tracer tracer = openTelemetry.getTracer(\"main\");\n"
    f"{body_indent}Span span = tracer.spanBuilder(\"main\").startSpan();\n"
    f"{body_indent}try (Scope scope = span.makeCurrent()) {{\n"
)
otel_postamble = (
    f"{body_indent}}} catch (Exception e) {{\n"
    f"{body_indent}    span.setStatus(StatusCode.ERROR, e.getMessage());\n"
    f"{body_indent}    throw e;\n"
    f"{body_indent}}} finally {{\n"
    f"{body_indent}    span.end();\n"
    f"{body_indent}}}\n"
)

JavaScriptInjector

/ TypeScriptInjector

) Prepends a Node.js NodeSDK

configuration block and tracer acquisition (_otelTracer.startSpan

). TypeScript uses ES module imports and type declarations (const _otelTracer: Tracer

), wrapping function bodies with standard try/catch/finally

blocks.

Code Excerpt (injectors/typescript_injector.py

):

_OTEL_SETUP_BLOCK = """\
// [OTel] โ”€โ”€ OpenTelemetry setup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { trace, Tracer, context, SpanStatusCode } from '@opentelemetry/api';

const _otelSdk: NodeSDK = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'otel-wizard-service',
  }),
  traceExporter: new ConsoleSpanExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
});
_otelSdk.start();
process.on('SIGTERM', (): void => { _otelSdk.shutdown(); });

const _otelTracer: Tracer = trace.getTracer('main');
// [OTel] โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
"""

CInjector

) Appends C OTel headers (#include "opentelemetry_c/otel_api.h"

), inserts otel_init()

and otel_start_span("main")

at the beginning of int main()

, injects otel_end_span()

prior to any return

statement inside main()

, and appends otel_shutdown()

before closing the function.

Code Excerpt (injectors/c_injector.py

):

_OTEL_MAIN_INIT = """\
    /* [OTel] Initialise tracer */
    otel_init("otel-wizard-service");
    otel_span_t *_otel_span = otel_start_span("main");
"""

_OTEL_MAIN_CLOSE = """\
    /* [OTel] End root span and shutdown */
    otel_end_span(_otel_span);
    otel_shutdown();
"""

The utils

directory provides critical supporting functionality for the OTel Instrumentation Wizard, focusing on file analysis and difference visualization:

Language Detection ( utils/language_detector.py): The

LanguageDetector

class identifies programming languages through explicit extension maps and keyword content heuristics. It offers two primary mechanisms: detect()

, which strictly validates whether a file has an active injector available (raising UnsupportedLanguageError

otherwise), and detect_any()

, a non-raising alternative designed for fluid UI workflows that gracefully flags recognized but non-instrumentable languages. Diff Generation & Visualisation ( utils/diff_viewer.py): Leverages Python's native

difflib

module to compute line-by-line differences between original and instrumented source code. It provides generate_unified_diff()

to output standard text diffs (diff -u

) and generate_html_diff()

to build sanitised, color-coded HTML views with styled line highlights (green for additions, red for deletions, and blue for hunk headers). To validate the code injection process across all supported target environments, the project includes clean, minimal "Hello World" benchmark samples free of any initial OpenTelemetry instrumentation:

Language Coverage: Standardized samples are provided for C (helloworld.c

), Go (helloworld.go

), Java (HelloWorld.java

), JavaScript (helloworld.js

), Python (helloworld.py

), and TypeScript (helloworld.ts

).

Canonical Code Structure: Each file implements a simple main entry point and helper greeting function (such as greet()

/ main()

). This standardized pattern allows unit and integration tests to reliably verify header/import additions, tracer setup placements, and entry-point span wrapping across every supported language.

  // tests/samples/go/sample_app.go
  // A plain Go HTTP server WITHOUT OTel instrumentation.
  // Used to validate the wizard's Go injection.

  package main

  import (
    "encoding/json"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "time"
  )

  type Item struct {
    Name     string  `json:"name"`
    Price    float64 `json:"price"`
    Quantity int     `json:"quantity"`
  }

  type OrderRequest struct {
    UserID int    `json:"user_id"`
    Items  []Item `json:"items"`
  }

  type OrderResponse struct {
    User   string  `json:"user"`
    Total  float64 `json:"total"`
    Status string  `json:"status"`
  }

  func calculateTotal(items []Item) float64 {
    total := 0.0
    for _, item := range items {
        qty := item.Quantity
        if qty == 0 {
            qty = 1
        }
        total += item.Price * float64(qty)
    }
    return total
  }

  func fetchUser(userID int) (map[string]interface{}, error) {
    time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
    if userID <= 0 {
        return nil, fmt.Errorf("invalid userID: %d", userID)
    }
    return map[string]interface{}{
        "id":   userID,
        "name": fmt.Sprintf("User_%d", userID),
    }, nil
  }

  func processOrder(userID int, items []Item) (*OrderResponse, error) {
    user, err := fetchUser(userID)
    if err != nil {
        return nil, err
    }
    total := calculateTotal(items)
    log.Printf("Order processed for %v: $%.2f", user["name"], total)
    return &OrderResponse{
        User:   user["name"].(string),
        Total:  total,
        Status: "confirmed",
    }, nil
  }

  func orderHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    var req OrderRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    resp, err := processOrder(req.UserID, req.Items)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
  }

  func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"status":"ok"}`)
  }

  func main() {
    http.HandleFunc("/order", orderHandler)
    http.HandleFunc("/health", healthHandler)
    log.Println("Server listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
  }

Step 4: Download the modified Code

// tests/samples/go/sample_app.go
// A plain Go HTTP server WITHOUT OTel instrumentation.
// Used to validate the wizard's Go injection.

package main

import (
    "context"  // [OTel-Wizard: injected]
    "os"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel/attribute"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel/codes"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel/sdk/resource"  // [OTel-Wizard: injected]
    sdktrace "go.opentelemetry.io/otel/sdk/trace"  // [OTel-Wizard: injected]
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"  // [OTel-Wizard: injected]
    "go.opentelemetry.io/otel/trace"  // [OTel-Wizard: injected]

// initOtelProvider initialises the TracerProvider. // [OTel-Wizard: injected]
func initOtelProvider(ctx context.Context) (func(context.Context) error, error) {
    exporter, err := otlptracehttp.New(ctx,
        otlptracehttp.WithEndpoint(getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")),
        otlptracehttp.WithInsecure(),
    )
    if err != nil {
        return nil, err
    }
    res, err := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName(getEnv("OTEL_SERVICE_NAME", "my-service")),
            semconv.ServiceVersion(getEnv("OTEL_SERVICE_VERSION", "1.0.0")),
        ),
    )
    if err != nil {
        return nil, err
    }
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    return tp.Shutdown, nil
}

// getEnv returns env var value or fallback. // [OTel-Wizard: injected]
func getEnv(key, fallback string) string {
    if v := os.Getenv(key); v != "" {
        return v
    }
    return fallback
}

var _otelTracer = otel.Tracer("my-service")  // [OTel-Wizard: injected]

    "encoding/json"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "time"
)

type Item struct {
    Name     string  `json:"name"`
    Price    float64 `json:"price"`
    Quantity int     `json:"quantity"`
}

type OrderRequest struct {
    UserID int    `json:"user_id"`
    Items  []Item `json:"items"`
}

type OrderResponse struct {
    User   string  `json:"user"`
    Total  float64 `json:"total"`
    Status string  `json:"status"`
}

func calculateTotal(items []Item) float64 {
    // [OTel-Wizard: injected]
    ctx, _otelSpan := _otelTracer.Start(ctx, "calculateTotal",
        trace.WithAttributes(attribute.String("code.function", "calculateTotal")))
    defer func() {
        if r := recover(); r != nil {
            _otelSpan.SetStatus(codes.Error, "panic")
            _otelSpan.End()
            panic(r)
        }
        _otelSpan.End()
    }()  // [OTel-Wizard: injected]
    _ = ctx  // [OTel-Wizard: injected]
    total := 0.0
    for _, item := range items {
        qty := item.Quantity
        if qty == 0 {
            qty = 1
        }
        total += item.Price * float64(qty)
    }
    return total
}

func fetchUser(userID int) (map[string]interface{}, error) {
    // [OTel-Wizard: injected]
    ctx, _otelSpan := _otelTracer.Start(ctx, "fetchUser",
        trace.WithAttributes(attribute.String("code.function", "fetchUser")))
    defer func() {
        if r := recover(); r != nil {
            _otelSpan.SetStatus(codes.Error, "panic")
            _otelSpan.End()
            panic(r)
        }
        _otelSpan.End()
    }()  // [OTel-Wizard: injected]
    _ = ctx  // [OTel-Wizard: injected]
    time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
    if userID <= 0 {
        return nil, fmt.Errorf("invalid userID: %d", userID)
    }
    return map[string]interface{}{
        "id":   userID,
        "name": fmt.Sprintf("User_%d", userID),
    }, nil
}

func processOrder(userID int, items []Item) (*OrderResponse, error) {
    // [OTel-Wizard: injected]
    ctx, _otelSpan := _otelTracer.Start(ctx, "processOrder",
        trace.WithAttributes(attribute.String("code.function", "processOrder")))
    defer func() {
        if r := recover(); r != nil {
            _otelSpan.SetStatus(codes.Error, "panic")
            _otelSpan.End()
            panic(r)
        }
        _otelSpan.End()
    }()  // [OTel-Wizard: injected]
    _ = ctx  // [OTel-Wizard: injected]
    user, err := fetchUser(userID)
    if err != nil {
        return nil, err
    }
    total := calculateTotal(items)
    log.Printf("Order processed for %v: $%.2f", user["name"], total)
    return &OrderResponse{
        User:   user["name"].(string),
        Total:  total,
        Status: "confirmed",
    }, nil
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
    // [OTel-Wizard: injected]
    ctx, _otelSpan := _otelTracer.Start(ctx, "orderHandler",
        trace.WithAttributes(attribute.String("code.function", "orderHandler")))
    defer func() {
        if r := recover(); r != nil {
            _otelSpan.SetStatus(codes.Error, "panic")
            _otelSpan.End()
            panic(r)
        }
        _otelSpan.End()
    }()  // [OTel-Wizard: injected]
    _ = ctx  // [OTel-Wizard: injected]
    if r.Method != http.MethodPost {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    var req OrderRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    resp, err := processOrder(req.UserID, req.Items)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    // [OTel-Wizard: injected]
    ctx, _otelSpan := _otelTracer.Start(ctx, "healthHandler",
        trace.WithAttributes(attribute.String("code.function", "healthHandler")))
    defer func() {
        if r := recover(); r != nil {
            _otelSpan.SetStatus(codes.Error, "panic")
            _otelSpan.End()
            panic(r)
        }
        _otelSpan.End()
    }()  // [OTel-Wizard: injected]
    _ = ctx  // [OTel-Wizard: injected]
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"status":"ok"}`)
}

func main() {
    http.HandleFunc("/order", orderHandler)
    http.HandleFunc("/health", healthHandler)
    log.Println("Server listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

โ€ฆ and the codeโ€™s dependencies;

go.opentelemetry.io/otel v1.33.0
go.opentelemetry.io/otel/sdk v1.33.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0
go.opentelemetry.io/otel/sdk/metric v1.33.0

The OTel Instrumentation Wizard provides an automated, end-to-end solution for introducing OpenTelemetry tracing into multi-language source code without requiring manual boilerplate construction. By leveraging a streamlined three-step workflow (Upload โ†’ Preview Diff โ†’ Download), the application abstracts the complexity of observability integration:

Intelligent Language Detection & Flexibility: Through the LanguageDetector

module, the wizard automatically identifies file types via explicit extension matching and keyword-based content heuristics. It provides broad recognition across many ecosystem languages while enforcing clear guardrails for supported injection targets.

Robust Code Injection Engine: Built upon a extensible BaseInjector

contract, concrete injectors for Python, Go, Java, JavaScript, TypeScript, and C automatically handle import injections, tracer provider setup, and entry-point span wrapping. Native language constructs (such as try-with-resources

, defer

, with

blocks, or explicit return-wrapping) guarantee safe span lifecycle management and error propagation across diverse runtime models.

Safety, Transparency, & Idempotency: Built-in safeguards check for pre-existing OTel markers to prevent duplicate instrumentation. The interactive UI offers full transparency via unified and color-coded HTML diff viewers (utils/diff_viewer.py

), ensuring developers can inspect and accept all injected modifications with total confidence.

Empirically Validated Architecture: Accompanied by standardized, clean test samples across all target languages, the architecture ensures reproducible and dependable AST/text transformations across real-world codebases.

Ultimately, the application bridges the gap between raw source code and modern observability practices, lowering the barrier to entry for distributed tracing across heterogeneous developer stacks.

Thanks for reading ๐Ÿ”ฎ

โ”€โ”€ more in #developer-tools 4 stories ยท sorted by recency
โ”€โ”€ more on @opentelemetry 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain โ€” perfect for shipping the agent you just read about.

$git push zahid main
โ†’ Live at https://your-agent.zahid.host โœ“
Get free account โ†’ Pricing
from โ‚ฌ0/mo ยท no card required
LIVE [news/building-an-openteleโ€ฆ] indexed:0 read:16min 2026-08-19 ยท โ€”