Move Go website_docs into opentelemetry.io repo (#2872)
Co-authored-by: Severin Neumann <neumanns@cisco.com> Co-authored-by: Phillip Carter <pcarter@fastmail.com>
This commit is contained in:
parent
4c873f272a
commit
a6a6b30c76
|
@ -22,7 +22,7 @@ content/en/docs/collector @open-telemetry/docs-approvers @open-te
|
||||||
content/en/docs/demo @open-telemetry/docs-approvers @open-telemetry/demo-approvers
|
content/en/docs/demo @open-telemetry/docs-approvers @open-telemetry/demo-approvers
|
||||||
content/en/docs/instrumentation/cpp/ @open-telemetry/docs-approvers @open-telemetry/cpp-maintainers
|
content/en/docs/instrumentation/cpp/ @open-telemetry/docs-approvers @open-telemetry/cpp-maintainers
|
||||||
content/en/docs/instrumentation/erlang/ @open-telemetry/docs-approvers @open-telemetry/erlang-approvers
|
content/en/docs/instrumentation/erlang/ @open-telemetry/docs-approvers @open-telemetry/erlang-approvers
|
||||||
# go is a content module
|
content/en/docs/instrumentation/go/ @open-telemetry/docs-approvers @open-telemetry/go-instrumentation-approvers
|
||||||
content/en/docs/instrumentation/java/ @open-telemetry/docs-approvers @open-telemetry/java-maintainers @open-telemetry/java-instrumentation-maintainers
|
content/en/docs/instrumentation/java/ @open-telemetry/docs-approvers @open-telemetry/java-maintainers @open-telemetry/java-instrumentation-maintainers
|
||||||
content/en/docs/instrumentation/js/ @open-telemetry/docs-approvers @open-telemetry/javascript-approvers
|
content/en/docs/instrumentation/js/ @open-telemetry/docs-approvers @open-telemetry/javascript-approvers
|
||||||
content/en/docs/instrumentation/net/ @open-telemetry/docs-approvers @open-telemetry/dotnet-approvers @open-telemetry/dotnet-instrumentation-approvers
|
content/en/docs/instrumentation/net/ @open-telemetry/docs-approvers @open-telemetry/dotnet-approvers @open-telemetry/dotnet-instrumentation-approvers
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
path = themes/docsy
|
path = themes/docsy
|
||||||
url = https://github.com/cncf/docsy.git
|
url = https://github.com/cncf/docsy.git
|
||||||
branch = opentelemetry.io
|
branch = opentelemetry.io
|
||||||
[submodule "content-modules/opentelemetry-go"]
|
|
||||||
path = content-modules/opentelemetry-go
|
|
||||||
url = https://github.com/open-telemetry/opentelemetry-go
|
|
||||||
[submodule "content-modules/opentelemetry-specification"]
|
[submodule "content-modules/opentelemetry-specification"]
|
||||||
path = content-modules/opentelemetry-specification
|
path = content-modules/opentelemetry-specification
|
||||||
url = https://github.com/open-telemetry/opentelemetry-specification.git
|
url = https://github.com/open-telemetry/opentelemetry-specification.git
|
||||||
|
|
|
@ -18,12 +18,6 @@
|
||||||
!/content-modules/community/mission-vision-values.md
|
!/content-modules/community/mission-vision-values.md
|
||||||
!/content-modules/community/roadmap.md
|
!/content-modules/community/roadmap.md
|
||||||
|
|
||||||
!/content-modules/opentelemetry-go
|
|
||||||
/content-modules/opentelemetry-go/*
|
|
||||||
!/content-modules/opentelemetry-go/website_docs
|
|
||||||
/content-modules/opentelemetry-go/website_docs/**/*
|
|
||||||
!/content-modules/opentelemetry-go/website_docs/**/*.md
|
|
||||||
|
|
||||||
# Ignore generated resources
|
# Ignore generated resources
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 4f4815406aff7889cee8b93396ac373e9240fe55
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: Go
|
||||||
|
description: >-
|
||||||
|
<img width="35" class="img-initial" src="/img/logos/32x32/Golang_SDK.svg"
|
||||||
|
alt="Go"></img> A language-specific implementation of OpenTelemetry in Go.
|
||||||
|
aliases: [/golang, /golang/metrics, /golang/tracing]
|
||||||
|
weight: 16
|
||||||
|
---
|
||||||
|
|
||||||
|
{{% docs/instrumentation/index-intro go /%}}
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
- [Contrib repository](https://github.com/open-telemetry/opentelemetry-go-contrib)
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
title: API reference
|
||||||
|
linkTitle: API
|
||||||
|
redirect: https://pkg.go.dev/go.opentelemetry.io/otel
|
||||||
|
manualLinkTarget: _blank
|
||||||
|
_build: { render: link }
|
||||||
|
weight: 210
|
||||||
|
---
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: Examples
|
||||||
|
redirect: https://github.com/open-telemetry/opentelemetry-go/tree/main/example
|
||||||
|
manualLinkTarget: _blank
|
||||||
|
_build: { render: link }
|
||||||
|
weight: 220
|
||||||
|
---
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
title: Exporters
|
||||||
|
aliases: [/docs/instrumentation/go/exporting_data]
|
||||||
|
weight: 50
|
||||||
|
---
|
||||||
|
|
||||||
|
In order to visualize and analyze your [traces](/docs/concepts/signals/traces/)
|
||||||
|
and metrics, you will need to export them to a backend.
|
||||||
|
|
||||||
|
## OTLP endpoint
|
||||||
|
|
||||||
|
To send trace data to an OTLP endpoint (like the [collector](/docs/collector) or
|
||||||
|
Jaeger >= v1.35.0) you'll want to configure an OTLP exporter that sends to your
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
### Using HTTP
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func installExportPipeline(ctx context.Context) (func(context.Context) error, error) {
|
||||||
|
client := otlptracehttp.NewClient()
|
||||||
|
exporter, err := otlptrace.New(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating OTLP trace exporter: %w", err)
|
||||||
|
}
|
||||||
|
/* … */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To learn more on how to use the OTLP HTTP exporter, try out the
|
||||||
|
[otel-collector](https://github.com/open-telemetry/opentelemetry-go/tree/main/example/otel-collector)
|
||||||
|
|
||||||
|
### Jaeger
|
||||||
|
|
||||||
|
To try out the OTLP exporter, since v1.35.0 you can run
|
||||||
|
[Jaeger](https://www.jaegertracing.io/) as an OTLP endpoint and for trace
|
||||||
|
visualization in a docker container:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d --name jaeger \
|
||||||
|
-e COLLECTOR_OTLP_ENABLED=true \
|
||||||
|
-p 16686:16686 \
|
||||||
|
-p 4318:4318 \
|
||||||
|
jaegertracing/all-in-one:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prometheus
|
||||||
|
|
||||||
|
Prometheus export is available in the
|
||||||
|
`go.opentelemetry.io/otel/exporters/prometheus` package.
|
||||||
|
|
||||||
|
Please find more documentation on
|
||||||
|
[GitHub](https://github.com/open-telemetry/opentelemetry-go/tree/main/exporters/prometheus)
|
|
@ -0,0 +1,693 @@
|
||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
weight: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome to the OpenTelemetry for Go getting started guide! This guide will walk
|
||||||
|
you through the basic steps in installing, instrumenting with, configuring, and
|
||||||
|
exporting data from OpenTelemetry. Before you get started, be sure to have Go
|
||||||
|
1.16 or newer installed.
|
||||||
|
|
||||||
|
Understanding how a system is functioning when it is failing or having issues is
|
||||||
|
critical to resolving those issues. One strategy to understand this is with
|
||||||
|
tracing. This guide shows how the OpenTelemetry Go project can be used to trace
|
||||||
|
an example application. You will start with an application that computes
|
||||||
|
Fibonacci numbers for users, and from there you will add instrumentation to
|
||||||
|
produce tracing telemetry with OpenTelemetry Go.
|
||||||
|
|
||||||
|
For reference, a complete example of the code you will build can be found
|
||||||
|
[here](https://github.com/open-telemetry/opentelemetry-go/tree/main/example/fib).
|
||||||
|
|
||||||
|
To start building the application, make a new directory named `fib` to house our
|
||||||
|
Fibonacci project. Next, add the following to a new file named `fib.go` in that
|
||||||
|
directory.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Fibonacci returns the n-th fibonacci number.
|
||||||
|
func Fibonacci(n uint) (uint64, error) {
|
||||||
|
if n <= 1 {
|
||||||
|
return uint64(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var n2, n1 uint64 = 0, 1
|
||||||
|
for i := uint(2); i < n; i++ {
|
||||||
|
n2, n1 = n1, n1+n2
|
||||||
|
}
|
||||||
|
|
||||||
|
return n2 + n1, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With your core logic added, you can now build your application around it. Add a
|
||||||
|
new `app.go` file with the following application logic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is a Fibonacci computation application.
|
||||||
|
type App struct {
|
||||||
|
r io.Reader
|
||||||
|
l *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp returns a new App.
|
||||||
|
func NewApp(r io.Reader, l *log.Logger) *App {
|
||||||
|
return &App{r: r, l: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts polling users for Fibonacci number requests and writes results.
|
||||||
|
func (a *App) Run(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
n, err := a.Poll(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Write(ctx, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll asks a user for input and returns the request.
|
||||||
|
func (a *App) Poll(ctx context.Context) (uint, error) {
|
||||||
|
a.l.Print("What Fibonacci number would you like to know: ")
|
||||||
|
|
||||||
|
var n uint
|
||||||
|
_, err := fmt.Fscanf(a.r, "%d\n", &n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the n-th Fibonacci number back to the user.
|
||||||
|
func (a *App) Write(ctx context.Context, n uint) {
|
||||||
|
f, err := Fibonacci(n)
|
||||||
|
if err != nil {
|
||||||
|
a.l.Printf("Fibonacci(%d): %v\n", n, err)
|
||||||
|
} else {
|
||||||
|
a.l.Printf("Fibonacci(%d) = %d\n", n, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With your application fully composed, you need a `main()` function to actually
|
||||||
|
run the application. In a new `main.go` file add the following run logic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
l := log.New(os.Stdout, "", 0)
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, os.Interrupt)
|
||||||
|
|
||||||
|
errCh := make(chan error)
|
||||||
|
app := NewApp(os.Stdin, l)
|
||||||
|
go func() {
|
||||||
|
errCh <- app.Run(context.Background())
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigCh:
|
||||||
|
l.Println("\ngoodbye")
|
||||||
|
return
|
||||||
|
case err := <-errCh:
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the code complete it is almost time to run the application. Before you can
|
||||||
|
do that you need to initialize this directory as a Go module. From your
|
||||||
|
terminal, run the command `go mod init fib` in the `fib` directory. This will
|
||||||
|
create a `go.mod` file, which is used by Go to manage imports. Now you should be
|
||||||
|
able to run the application!
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go run .
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
42
|
||||||
|
Fibonacci(42) = 267914296
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
^C
|
||||||
|
goodbye
|
||||||
|
```
|
||||||
|
|
||||||
|
The application can be exited with <kbd>Ctrl+C</kbd>. You should see a similar
|
||||||
|
output as above, if not make sure to go back and fix any errors.
|
||||||
|
|
||||||
|
## Trace Instrumentation
|
||||||
|
|
||||||
|
OpenTelemetry is split into two parts: an API to instrument code with, and SDKs
|
||||||
|
that implement the API. To start integrating OpenTelemetry into any project, the
|
||||||
|
API is used to define how telemetry is generated. To generate tracing telemetry
|
||||||
|
in your application you will use the OpenTelemetry Trace API from the
|
||||||
|
[`go.opentelemetry.io/otel/trace`] package.
|
||||||
|
|
||||||
|
First, you need to install the necessary packages for the Trace API. Run the
|
||||||
|
following command in your working directory.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get go.opentelemetry.io/otel \
|
||||||
|
go.opentelemetry.io/otel/trace
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that the packages installed you can start updating your application with
|
||||||
|
imports you will use in the `app.go` file.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
With the imports added, you can start instrumenting.
|
||||||
|
|
||||||
|
The OpenTelemetry Tracing API provides a [`Tracer`] to create traces. These
|
||||||
|
[`Tracer`]s are designed to be associated with one instrumentation library. That
|
||||||
|
way telemetry they produce can be understood to come from that part of a code
|
||||||
|
base. To uniquely identify your application to the [`Tracer`] you will create a
|
||||||
|
constant with the package name in `app.go`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// name is the Tracer name used to identify this instrumentation library.
|
||||||
|
const name = "fib"
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the full-qualified package name, something that should be unique for Go
|
||||||
|
packages, is the standard way to identify a [`Tracer`]. If your example package
|
||||||
|
name differs, be sure to update the name you use here to match.
|
||||||
|
|
||||||
|
Everything should be in place now to start tracing your application. But first,
|
||||||
|
what is a trace? And, how exactly should you build them for your application?
|
||||||
|
|
||||||
|
To back up a bit, a trace is a type of telemetry that represents work being done
|
||||||
|
by a service. A trace is a record of the connection(s) between participants
|
||||||
|
processing a transaction, often through client/server requests processing and
|
||||||
|
other forms of communication.
|
||||||
|
|
||||||
|
Each part of the work that a service performs is represented in the trace by a
|
||||||
|
span. Those spans are not just an unordered collection. Like the call stack of
|
||||||
|
our application, those spans are defined with relationships to one another. The
|
||||||
|
"root" span is the only span without a parent, it represents how a service
|
||||||
|
request is started. All other spans have a parent relationship to another span
|
||||||
|
in the same trace.
|
||||||
|
|
||||||
|
If this last part about span relationships doesn't make complete sense now,
|
||||||
|
don't worry. The most important takeaway is that each part of your code, which
|
||||||
|
does some work, should be represented as a span. You will have a better
|
||||||
|
understanding of these span relationships after you instrument your code, so
|
||||||
|
let's get started.
|
||||||
|
|
||||||
|
Start by instrumenting the `Run` method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Run starts polling users for Fibonacci number requests and writes results.
|
||||||
|
func (a *App) Run(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
// Each execution of the run loop, we should get a new "root" span and context.
|
||||||
|
newCtx, span := otel.Tracer(name).Start(ctx, "Run")
|
||||||
|
|
||||||
|
n, err := a.Poll(newCtx)
|
||||||
|
if err != nil {
|
||||||
|
span.End()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Write(newCtx, n)
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above code creates a span for every iteration of the for loop. The span is
|
||||||
|
created using a [`Tracer`] from the
|
||||||
|
[global `TracerProvider`](https://pkg.go.dev/go.opentelemetry.io/otel#GetTracerProvider).
|
||||||
|
You will learn more about [`TracerProvider`]s and handle the other side of
|
||||||
|
setting up a global [`TracerProvider`] when you install an SDK in a later
|
||||||
|
section. For now, as an instrumentation author, all you need to worry about is
|
||||||
|
that you are using an appropriately named [`Tracer`] from a [`TracerProvider`]
|
||||||
|
when you write `otel.Tracer(name)`.
|
||||||
|
|
||||||
|
Next, instrument the `Poll` method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Poll asks a user for input and returns the request.
|
||||||
|
func (a *App) Poll(ctx context.Context) (uint, error) {
|
||||||
|
_, span := otel.Tracer(name).Start(ctx, "Poll")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
a.l.Print("What Fibonacci number would you like to know: ")
|
||||||
|
|
||||||
|
var n uint
|
||||||
|
_, err := fmt.Fscanf(a.r, "%d\n", &n)
|
||||||
|
|
||||||
|
// Store n as a string to not overflow an int64.
|
||||||
|
nStr := strconv.FormatUint(uint64(n), 10)
|
||||||
|
span.SetAttributes(attribute.String("request.n", nStr))
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similar to the `Run` method instrumentation, this adds a span to the method to
|
||||||
|
track the computation performed. However, it also adds an attribute to annotate
|
||||||
|
the span. This annotation is something you can add when you think a user of your
|
||||||
|
application will want to see the state or details about the run environment when
|
||||||
|
looking at telemetry.
|
||||||
|
|
||||||
|
Finally, instrument the `Write` method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Write writes the n-th Fibonacci number back to the user.
|
||||||
|
func (a *App) Write(ctx context.Context, n uint) {
|
||||||
|
var span trace.Span
|
||||||
|
ctx, span = otel.Tracer(name).Start(ctx, "Write")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
f, err := func(ctx context.Context) (uint64, error) {
|
||||||
|
_, span := otel.Tracer(name).Start(ctx, "Fibonacci")
|
||||||
|
defer span.End()
|
||||||
|
return Fibonacci(n)
|
||||||
|
}(ctx)
|
||||||
|
if err != nil {
|
||||||
|
a.l.Printf("Fibonacci(%d): %v\n", n, err)
|
||||||
|
} else {
|
||||||
|
a.l.Printf("Fibonacci(%d) = %d\n", n, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This method is instrumented with two spans. One to track the `Write` method
|
||||||
|
itself, and another to track the call to the core logic with the `Fibonacci`
|
||||||
|
function. Do you see how context is passed through the spans? Do you see how
|
||||||
|
this also defines the relationship between spans?
|
||||||
|
|
||||||
|
In OpenTelemetry Go the span relationships are defined explicitly with a
|
||||||
|
`context.Context`. When a span is created a context is returned alongside the
|
||||||
|
span. That context will contain a reference to the created span. If that context
|
||||||
|
is used when creating another span the two spans will be related. The original
|
||||||
|
span will become the new span's parent, and as a corollary, the new span is said
|
||||||
|
to be a child of the original. This hierarchy gives traces structure, structure
|
||||||
|
that helps show a computation path through a system. Based on what you
|
||||||
|
instrumented above and this understanding of span relationships you should
|
||||||
|
expect a trace for each execution of the run loop to look like this.
|
||||||
|
|
||||||
|
```
|
||||||
|
Run
|
||||||
|
├── Poll
|
||||||
|
└── Write
|
||||||
|
└── Fibonacci
|
||||||
|
```
|
||||||
|
|
||||||
|
A `Run` span will be a parent to both a `Poll` and `Write` span, and the `Write`
|
||||||
|
span will be a parent to a `Fibonacci` span.
|
||||||
|
|
||||||
|
Now how do you actually see the produced spans? To do this you will need to
|
||||||
|
configure and install an SDK.
|
||||||
|
|
||||||
|
## SDK Installation
|
||||||
|
|
||||||
|
OpenTelemetry is designed to be modular in its implementation of the
|
||||||
|
OpenTelemetry API. The OpenTelemetry Go project offers an SDK package,
|
||||||
|
[`go.opentelemetry.io/otel/sdk`], that implements this API and adheres to the
|
||||||
|
OpenTelemetry specification. To start using this SDK you will first need to
|
||||||
|
create an exporter, but before anything can happen we need to install some
|
||||||
|
packages. Run the following in the `fib` directory to install the trace STDOUT
|
||||||
|
exporter and the SDK.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get go.opentelemetry.io/otel/sdk \
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace
|
||||||
|
```
|
||||||
|
|
||||||
|
Now add the needed imports to `main.go`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Console Exporter
|
||||||
|
|
||||||
|
The SDK connects telemetry from the OpenTelemetry API to exporters. Exporters
|
||||||
|
are packages that allow telemetry data to be emitted somewhere - either to the
|
||||||
|
console (which is what we're doing here), or to a remote system or collector for
|
||||||
|
further analysis and/or enrichment. OpenTelemetry supports a variety of
|
||||||
|
exporters through its ecosystem including popular open source tools like
|
||||||
|
[Jaeger](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/jaeger),
|
||||||
|
[Zipkin](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/zipkin), and
|
||||||
|
[Prometheus](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/prometheus).
|
||||||
|
|
||||||
|
To initialize the console exporter, add the following function to the `main.go`
|
||||||
|
file:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// newExporter returns a console exporter.
|
||||||
|
func newExporter(w io.Writer) (trace.SpanExporter, error) {
|
||||||
|
return stdouttrace.New(
|
||||||
|
stdouttrace.WithWriter(w),
|
||||||
|
// Use human-readable output.
|
||||||
|
stdouttrace.WithPrettyPrint(),
|
||||||
|
// Do not print timestamps for the demo.
|
||||||
|
stdouttrace.WithoutTimestamps(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a new console exporter with basic options. You will use this
|
||||||
|
function later when you configure the SDK to send telemetry data to it, but
|
||||||
|
first you need to make sure that data is identifiable.
|
||||||
|
|
||||||
|
### Creating a Resource
|
||||||
|
|
||||||
|
Telemetry data can be crucial to solving issues with a service. The catch is,
|
||||||
|
you need a way to identify what service, or even what service instance, that
|
||||||
|
data is coming from. OpenTelemetry uses a [`Resource`] to represent the entity
|
||||||
|
producing telemetry. Add the following function to the `main.go` file to create
|
||||||
|
an appropriate [`Resource`] for the application.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// newResource returns a resource describing this application.
|
||||||
|
func newResource() *resource.Resource {
|
||||||
|
r, _ := resource.Merge(
|
||||||
|
resource.Default(),
|
||||||
|
resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceName("fib"),
|
||||||
|
semconv.ServiceVersion("v0.1.0"),
|
||||||
|
attribute.String("environment", "demo"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Any information you would like to associate with all telemetry data the SDK
|
||||||
|
handles can be added to the returned [`Resource`]. This is done by registering
|
||||||
|
the [`Resource`] with the [`TracerProvider`]. Something you can now create!
|
||||||
|
|
||||||
|
### Installing a Tracer Provider
|
||||||
|
|
||||||
|
You have your application instrumented to produce telemetry data and you have an
|
||||||
|
exporter to send that data to the console, but how are they connected? This is
|
||||||
|
where the [`TracerProvider`] is used. It is a centralized point where
|
||||||
|
instrumentation will get a [`Tracer`] from and funnels the telemetry data from
|
||||||
|
these [`Tracer`]s to export pipelines.
|
||||||
|
|
||||||
|
The pipelines that receive and ultimately transmit data to exporters are called
|
||||||
|
[`SpanProcessor`]s. A [`TracerProvider`] can be configured to have multiple span
|
||||||
|
processors, but for this example you will only need to configure only one.
|
||||||
|
Update your `main` function in `main.go` with the following.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
l := log.New(os.Stdout, "", 0)
|
||||||
|
|
||||||
|
// Write telemetry data to a file.
|
||||||
|
f, err := os.Create("traces.txt")
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
exp, err := newExporter(f)
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tp := trace.NewTracerProvider(
|
||||||
|
trace.WithBatcher(exp),
|
||||||
|
trace.WithResource(newResource()),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err := tp.Shutdown(context.Background()); err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
/* … */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There's a fair amount going on here. First you are creating a console exporter
|
||||||
|
that will export to a file. You are then registering the exporter with a new
|
||||||
|
[`TracerProvider`]. This is done with a [`BatchSpanProcessor`] when it is passed
|
||||||
|
to the [`trace.WithBatcher`] option. Batching data is a good practice and will
|
||||||
|
help not overload systems downstream. Finally, with the [`TracerProvider`]
|
||||||
|
created, you are deferring a function to flush and stop it, and registering it
|
||||||
|
as the global OpenTelemetry [`TracerProvider`].
|
||||||
|
|
||||||
|
Do you remember in the previous instrumentation section when we used the global
|
||||||
|
[`TracerProvider`] to get a [`Tracer`]? This last step, registering the
|
||||||
|
[`TracerProvider`] globally, is what will connect that instrumentation's
|
||||||
|
[`Tracer`] with this [`TracerProvider`]. This pattern, using a global
|
||||||
|
[`TracerProvider`], is convenient, but not always appropriate.
|
||||||
|
[`TracerProvider`]s can be explicitly passed to instrumentation or inferred from
|
||||||
|
a context that contains a span. For this simple example using a global provider
|
||||||
|
makes sense, but for more complex or distributed codebases these other ways of
|
||||||
|
passing [`TracerProvider`]s may make more sense.
|
||||||
|
|
||||||
|
## Putting It All Together
|
||||||
|
|
||||||
|
You should now have a working application that produces trace telemetry data!
|
||||||
|
Give it a try.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go run .
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
42
|
||||||
|
Fibonacci(42) = 267914296
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
^C
|
||||||
|
goodbye
|
||||||
|
```
|
||||||
|
|
||||||
|
A new file named `traces.txt` should be created in your working directory. All
|
||||||
|
the traces created from running your application should be in there!
|
||||||
|
|
||||||
|
## (Bonus) Errors
|
||||||
|
|
||||||
|
At this point you have a working application and it is producing tracing
|
||||||
|
telemetry data. Unfortunately, it was discovered that there is an error in the
|
||||||
|
core functionality of the `fib` module.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go run .
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
100
|
||||||
|
Fibonacci(100) = 3736710778780434371
|
||||||
|
# …
|
||||||
|
```
|
||||||
|
|
||||||
|
But the 100-th Fibonacci number is `354224848179261915075`, not
|
||||||
|
`3736710778780434371`! This application is only meant as a demo, but it
|
||||||
|
shouldn't return wrong values. Update the `Fibonacci` function to return an
|
||||||
|
error instead of computing incorrect values.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Fibonacci returns the n-th fibonacci number. An error is returned if the
|
||||||
|
// fibonacci number cannot be represented as a uint64.
|
||||||
|
func Fibonacci(n uint) (uint64, error) {
|
||||||
|
if n <= 1 {
|
||||||
|
return uint64(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 93 {
|
||||||
|
return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var n2, n1 uint64 = 0, 1
|
||||||
|
for i := uint(2); i < n; i++ {
|
||||||
|
n2, n1 = n1, n1+n2
|
||||||
|
}
|
||||||
|
|
||||||
|
return n2 + n1, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Great, you have fixed the code, but it would be ideal to include errors returned
|
||||||
|
to a user in the telemetry data. Luckily, spans can be configured to communicate
|
||||||
|
this information. Update the `Write` method in `app.go` with the following code.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Write writes the n-th Fibonacci number back to the user.
|
||||||
|
func (a *App) Write(ctx context.Context, n uint) {
|
||||||
|
var span trace.Span
|
||||||
|
ctx, span = otel.Tracer(name).Start(ctx, "Write")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
f, err := func(ctx context.Context) (uint64, error) {
|
||||||
|
_, span := otel.Tracer(name).Start(ctx, "Fibonacci")
|
||||||
|
defer span.End()
|
||||||
|
f, err := Fibonacci(n)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
}(ctx)
|
||||||
|
/* … */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With this change any error returned from the `Fibonacci` function will mark that
|
||||||
|
span as an error and record an event describing the error.
|
||||||
|
|
||||||
|
This is a great start, but it is not the only error returned in from the
|
||||||
|
application. If a user makes a request for a non unsigned integer value the
|
||||||
|
application will fail. Update the `Poll` method with a similar fix to capture
|
||||||
|
this error in the telemetry data.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Poll asks a user for input and returns the request.
|
||||||
|
func (a *App) Poll(ctx context.Context) (uint, error) {
|
||||||
|
_, span := otel.Tracer(name).Start(ctx, "Poll")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
a.l.Print("What Fibonacci number would you like to know: ")
|
||||||
|
|
||||||
|
var n uint
|
||||||
|
_, err := fmt.Fscanf(a.r, "%d\n", &n)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store n as a string to not overflow an int64.
|
||||||
|
nStr := strconv.FormatUint(uint64(n), 10)
|
||||||
|
span.SetAttributes(attribute.String("request.n", nStr))
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All that is left is updating imports for the `app.go` file to include the
|
||||||
|
[`go.opentelemetry.io/otel/codes`] package.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
With these fixes in place and the instrumentation updated, re-trigger the bug.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go run .
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
100
|
||||||
|
Fibonacci(100): unsupported fibonacci number 100: too large
|
||||||
|
What Fibonacci number would you like to know:
|
||||||
|
^C
|
||||||
|
goodbye
|
||||||
|
```
|
||||||
|
|
||||||
|
Excellent! The application no longer returns wrong values, and looking at the
|
||||||
|
telemetry data in the `traces.txt` file you should see the error captured as an
|
||||||
|
event.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"Events": [
|
||||||
|
{
|
||||||
|
"Name": "exception",
|
||||||
|
"Attributes": [
|
||||||
|
{
|
||||||
|
"Key": "exception.type",
|
||||||
|
"Value": {
|
||||||
|
"Type": "STRING",
|
||||||
|
"Value": "*errors.errorString"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "exception.message",
|
||||||
|
"Value": {
|
||||||
|
"Type": "STRING",
|
||||||
|
"Value": "unsupported fibonacci number 100: too large"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's Next
|
||||||
|
|
||||||
|
This guide has walked you through adding tracing instrumentation to an
|
||||||
|
application and using a console exporter to send telemetry data to a file. There
|
||||||
|
are many other topics to cover in OpenTelemetry, but you should be ready to
|
||||||
|
start adding OpenTelemetry Go to your projects at this point. Go instrument your
|
||||||
|
code!
|
||||||
|
|
||||||
|
For more information about instrumenting your code and things you can do with
|
||||||
|
spans, refer to the [manual instrumentation](/docs/instrumentation/go/manual/)
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
You'll also want to configure an appropriate exporter to
|
||||||
|
[export your telemetry data](/docs/instrumentation/go/exporters/) to one or more
|
||||||
|
telemetry backends.
|
||||||
|
|
||||||
|
[`go.opentelemetry.io/otel/trace`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/trace
|
||||||
|
[`go.opentelemetry.io/otel/sdk`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/sdk
|
||||||
|
[`go.opentelemetry.io/otel/codes`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/codes
|
||||||
|
[`tracer`]: https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer
|
||||||
|
[`tracerprovider`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/trace#TracerProvider
|
||||||
|
[`resource`]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/resource#Resource
|
||||||
|
[`spanprocessor`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#SpanProcessor
|
||||||
|
[`batchspanprocessor`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#NewBatchSpanProcessor
|
||||||
|
[`trace.withbatcher`]:
|
||||||
|
https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#WithBatcher
|
|
@ -0,0 +1,114 @@
|
||||||
|
---
|
||||||
|
title: Using instrumentation libraries
|
||||||
|
linkTitle: Libraries
|
||||||
|
aliases:
|
||||||
|
- /docs/instrumentation/go/using_instrumentation_libraries
|
||||||
|
- /docs/instrumentation/go/automatic_instrumentation
|
||||||
|
weight: 40
|
||||||
|
---
|
||||||
|
|
||||||
|
Go does not support truly automatic instrumentation like other languages today.
|
||||||
|
Instead, you'll need to depend on
|
||||||
|
[instrumentation libraries](/docs/specs/otel/glossary/#instrumentation-library)
|
||||||
|
that generate telemetry data for a particular instrumented library. For example,
|
||||||
|
the instrumentation library for `net/http` will automatically create spans that
|
||||||
|
track inbound and outbound requests once you configure it in your code.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Each instrumentation library is a package. In general, this means you need to
|
||||||
|
`go get` the appropriate package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get go.opentelemetry.io/contrib/instrumentation/{import-path}/otel{package-name}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then configure it in your code based on what the library requires to be
|
||||||
|
activated.
|
||||||
|
|
||||||
|
## Example with `net/http`
|
||||||
|
|
||||||
|
As an example, here's how you can set up automatic instrumentation for inbound
|
||||||
|
HTTP requests for `net/http`:
|
||||||
|
|
||||||
|
First, get the `net/http` instrumentation library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, use the library to wrap an HTTP handler in your code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Package-level tracer.
|
||||||
|
// This should be configured in your code setup instead of here.
|
||||||
|
var tracer = otel.Tracer("github.com/full/path/to/mypkg")
|
||||||
|
|
||||||
|
// sleepy mocks work that your application does.
|
||||||
|
func sleepy(ctx context.Context) {
|
||||||
|
_, span := tracer.Start(ctx, "sleep")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
sleepTime := 1 * time.Second
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
span.SetAttributes(attribute.Int("sleep.duration", int(sleepTime)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpHandler is an HTTP handler function that is going to be instrumented.
|
||||||
|
func httpHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Hello, World! I am instrumented automatically!")
|
||||||
|
ctx := r.Context()
|
||||||
|
sleepy(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Wrap your httpHandler function.
|
||||||
|
handler := http.HandlerFunc(httpHandler)
|
||||||
|
wrappedHandler := otelhttp.NewHandler(handler, "hello-instrumented")
|
||||||
|
http.Handle("/hello-instrumented", wrappedHandler)
|
||||||
|
|
||||||
|
// And start the HTTP serve.
|
||||||
|
log.Fatal(http.ListenAndServe(":3030", nil))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Assuming that you have a `Tracer` and [exporter](../exporters/) configured, this
|
||||||
|
code will:
|
||||||
|
|
||||||
|
- Start an HTTP server on port `3030`
|
||||||
|
- Automatically generate a span for each inbound HTTP request to
|
||||||
|
`/hello-instrumented`
|
||||||
|
- Create a child span of the automatically-generated one that tracks the work
|
||||||
|
done in `sleepy`
|
||||||
|
|
||||||
|
Connecting manual instrumentation you write in your app with instrumentation
|
||||||
|
generated from a library is essential to get good observability into your apps
|
||||||
|
and services.
|
||||||
|
|
||||||
|
## Available packages
|
||||||
|
|
||||||
|
A full list of instrumentation libraries available can be found in the
|
||||||
|
[OpenTelemetry registry](/ecosystem/registry/?language=go&component=instrumentation).
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
Instrumentation libraries can do things like generate telemetry data for inbound
|
||||||
|
and outbound HTTP requests, but they don't instrument your actual application.
|
||||||
|
|
||||||
|
To get richer telemetry data, use [manual instrumentation](../manual/) to enrich
|
||||||
|
your telemetry data from instrumentation libraries with instrumentation from
|
||||||
|
your running application.
|
|
@ -0,0 +1,302 @@
|
||||||
|
---
|
||||||
|
title: Manual Instrumentation
|
||||||
|
linkTitle: Manual
|
||||||
|
aliases:
|
||||||
|
- /docs/instrumentation/go/instrumentation
|
||||||
|
- /docs/instrumentation/go/manual_instrumentation
|
||||||
|
weight: 30
|
||||||
|
---
|
||||||
|
|
||||||
|
Instrumentation is the process of adding observability code to your application.
|
||||||
|
There are two general types of instrumentation - automatic, and manual - and you
|
||||||
|
should be familiar with both in order to effectively instrument your software.
|
||||||
|
|
||||||
|
## Getting a Tracer
|
||||||
|
|
||||||
|
To create spans, you'll need to acquire or initialize a tracer first.
|
||||||
|
|
||||||
|
### Initializing a new tracer
|
||||||
|
|
||||||
|
Ensure you have the right packages installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get go.opentelemetry.io/otel \
|
||||||
|
go.opentelemetry.io/otel/trace \
|
||||||
|
go.opentelemetry.io/otel/sdk \
|
||||||
|
```
|
||||||
|
|
||||||
|
Then initialize an exporter, resources, tracer provider, and finally a tracer.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tracer trace.Tracer
|
||||||
|
|
||||||
|
func newExporter(ctx context.Context) /* (someExporter.Exporter, error) */ {
|
||||||
|
// Your preferred exporter: console, jaeger, zipkin, OTLP, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider {
|
||||||
|
// Ensure default SDK resources and the required service name are set.
|
||||||
|
r, err := resource.Merge(
|
||||||
|
resource.Default(),
|
||||||
|
resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceName("ExampleService"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(exp),
|
||||||
|
sdktrace.WithResource(r),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
exp, err := newExporter(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to initialize exporter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new tracer provider with a batch span processor and the given exporter.
|
||||||
|
tp := newTraceProvider(exp)
|
||||||
|
|
||||||
|
// Handle shutdown properly so nothing leaks.
|
||||||
|
defer func() { _ = tp.Shutdown(ctx) }()
|
||||||
|
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
// Finally, set the tracer that can be used for this package.
|
||||||
|
tracer = tp.Tracer("ExampleService")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now access `tracer` to manually instrument your code.
|
||||||
|
|
||||||
|
## Creating Spans
|
||||||
|
|
||||||
|
Spans are created by tracers. If you don't have one initialized, you'll need to
|
||||||
|
do that.
|
||||||
|
|
||||||
|
To create a span with a tracer, you'll also need a handle on a `context.Context`
|
||||||
|
instance. These will typically come from things like a request object and may
|
||||||
|
already contain a parent span from an [instrumentation library][].
|
||||||
|
|
||||||
|
```go
|
||||||
|
func httpHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := tracer.Start(r.Context(), "hello-span")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
// do some work to track with hello-span
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In Go, the `context` package is used to store the active span. When you start a
|
||||||
|
span, you'll get a handle on not only the span that's created, but the modified
|
||||||
|
context that contains it.
|
||||||
|
|
||||||
|
Once a span has completed, it is immutable and can no longer be modified.
|
||||||
|
|
||||||
|
### Get the current span
|
||||||
|
|
||||||
|
To get the current span, you'll need to pull it out of a `context.Context` you
|
||||||
|
have a handle on:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// This context needs contain the active span you plan to extract.
|
||||||
|
ctx := context.TODO()
|
||||||
|
span := trace.SpanFromContext(ctx)
|
||||||
|
|
||||||
|
// Do something with the current span, optionally calling `span.End()` if you want it to end
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be helpful if you'd like to add information to the current span at a
|
||||||
|
point in time.
|
||||||
|
|
||||||
|
### Create nested spans
|
||||||
|
|
||||||
|
You can create a nested span to track work in a nested operation.
|
||||||
|
|
||||||
|
If the current `context.Context` you have a handle on already contains a span
|
||||||
|
inside of it, creating a new span makes it a nested span. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func parentFunction(ctx context.Context) {
|
||||||
|
ctx, parentSpan := tracer.Start(ctx, "parent")
|
||||||
|
defer parentSpan.End()
|
||||||
|
|
||||||
|
// call the child function and start a nested span in there
|
||||||
|
childFunction(ctx)
|
||||||
|
|
||||||
|
// do more work - when this function ends, parentSpan will complete.
|
||||||
|
}
|
||||||
|
|
||||||
|
func childFunction(ctx context.Context) {
|
||||||
|
// Create a span to track `childFunction()` - this is a nested span whose parent is `parentSpan`
|
||||||
|
ctx, childSpan := tracer.Start(ctx, "child")
|
||||||
|
defer childSpan.End()
|
||||||
|
|
||||||
|
// do work here, when this function returns, childSpan will complete.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once a span has completed, it is immutable and can no longer be modified.
|
||||||
|
|
||||||
|
### Span Attributes
|
||||||
|
|
||||||
|
Attributes are keys and values that are applied as metadata to your spans and
|
||||||
|
are useful for aggregating, filtering, and grouping traces. Attributes can be
|
||||||
|
added at span creation, or at any other time during the lifecycle of a span
|
||||||
|
before it has completed.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// setting attributes at creation...
|
||||||
|
ctx, span = tracer.Start(ctx, "attributesAtCreation", trace.WithAttributes(attribute.String("hello", "world")))
|
||||||
|
// ... and after creation
|
||||||
|
span.SetAttributes(attribute.Bool("isTrue", true), attribute.String("stringAttr", "hi!"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Attribute keys can be precomputed, as well:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var myKey = attribute.Key("myCoolAttribute")
|
||||||
|
span.SetAttributes(myKey.String("a value"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Semantic Attributes
|
||||||
|
|
||||||
|
Semantic Attributes are attributes that are defined by the [OpenTelemetry
|
||||||
|
Specification][] in order to provide a shared set of attribute keys across
|
||||||
|
multiple languages, frameworks, and runtimes for common concepts like HTTP
|
||||||
|
methods, status codes, user agents, and more. These attributes are available in
|
||||||
|
the `go.opentelemetry.io/otel/semconv/v1.12.0` package.
|
||||||
|
|
||||||
|
For details, see [Trace semantic conventions][].
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
An event is a human-readable message on a span that represents "something
|
||||||
|
happening" during it's lifetime. For example, imagine a function that requires
|
||||||
|
exclusive access to a resource that is under a mutex. An event could be created
|
||||||
|
at two points - once, when we try to gain access to the resource, and another
|
||||||
|
when we acquire the mutex.
|
||||||
|
|
||||||
|
```go
|
||||||
|
span.AddEvent("Acquiring lock")
|
||||||
|
mutex.Lock()
|
||||||
|
span.AddEvent("Got lock, doing work...")
|
||||||
|
// do stuff
|
||||||
|
span.AddEvent("Unlocking")
|
||||||
|
mutex.Unlock()
|
||||||
|
```
|
||||||
|
|
||||||
|
A useful characteristic of events is that their timestamps are displayed as
|
||||||
|
offsets from the beginning of the span, allowing you to easily see how much time
|
||||||
|
elapsed between them.
|
||||||
|
|
||||||
|
Events can also have attributes of their own -
|
||||||
|
|
||||||
|
```go
|
||||||
|
span.AddEvent("Cancelled wait due to external signal", trace.WithAttributes(attribute.Int("pid", 4328), attribute.String("signal", "SIGHUP")))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set span status
|
||||||
|
|
||||||
|
A status can be set on a span, typically used to specify that there was an error
|
||||||
|
in the operation a span is tracking - .`Error`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
// ...
|
||||||
|
)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
result, err := operationThatCouldFail()
|
||||||
|
if err != nil {
|
||||||
|
span.SetStatus(codes.Error, "operationThatCouldFail failed")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the status for all spans is `Unset`. In rare cases, you may also
|
||||||
|
wish to set the status to `Ok`. This should generally not be necessary, though.
|
||||||
|
|
||||||
|
### Record errors
|
||||||
|
|
||||||
|
If you have an operation that failed and you wish to capture the error it
|
||||||
|
produced, you can record that error.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
// ...
|
||||||
|
)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
result, err := operationThatCouldFail()
|
||||||
|
if err != nil {
|
||||||
|
span.SetStatus(codes.Error, "operationThatCouldFail failed")
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is highly recommended that you also set a span's status to `Error` when using
|
||||||
|
`RecordError`, unless you do not wish to consider the span tracking a failed
|
||||||
|
operation as an error span. The `RecordError` function does **not**
|
||||||
|
automatically set a span status when called.
|
||||||
|
|
||||||
|
## Creating Metrics
|
||||||
|
|
||||||
|
The metrics API is currently unstable, documentation TBA.
|
||||||
|
|
||||||
|
## Propagators and Context
|
||||||
|
|
||||||
|
Traces can extend beyond a single process. This requires _context propagation_,
|
||||||
|
a mechanism where identifiers for a trace are sent to remote processes.
|
||||||
|
|
||||||
|
In order to propagate trace context over the wire, a propagator must be
|
||||||
|
registered with the OpenTelemetry API.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
)
|
||||||
|
...
|
||||||
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
||||||
|
```
|
||||||
|
|
||||||
|
> OpenTelemetry also supports the B3 header format, for compatibility with
|
||||||
|
> existing tracing systems (`go.opentelemetry.io/contrib/propagators/b3`) that
|
||||||
|
> do not support the W3C TraceContext standard.
|
||||||
|
|
||||||
|
After configuring context propagation, you'll most likely want to use automatic
|
||||||
|
instrumentation to handle the behind-the-scenes work of actually managing
|
||||||
|
serializing the context.
|
||||||
|
|
||||||
|
[opentelemetry specification]: /docs/specs/otel/
|
||||||
|
[trace semantic conventions]: /docs/specs/otel/trace/semantic_conventions/
|
||||||
|
[instrumentation library]: ../libraries/
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
title: Resources
|
||||||
|
weight: 70
|
||||||
|
---
|
||||||
|
|
||||||
|
Resources are a special type of attribute that apply to all spans generated by a
|
||||||
|
process. These should be used to represent underlying metadata about a process
|
||||||
|
that's non-ephemeral — for example, the hostname of a process, or its
|
||||||
|
instance ID.
|
||||||
|
|
||||||
|
Resources should be assigned to a tracer provider at its initialization, and are
|
||||||
|
created much like attributes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
resources := resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceNameKey.String("myService"),
|
||||||
|
semconv.ServiceVersionKey.String("1.0.0"),
|
||||||
|
semconv.ServiceInstanceIDKey.String("abcdef12345"),
|
||||||
|
)
|
||||||
|
|
||||||
|
provider := sdktrace.NewTracerProvider(
|
||||||
|
...
|
||||||
|
sdktrace.WithResource(resources),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the use of the `semconv` package to provide
|
||||||
|
[conventional names](/docs/concepts/semantic-conventions/) for resource
|
||||||
|
attributes. This helps ensure that consumers of telemetry produced with these
|
||||||
|
semantic conventions can easily discover relevant attributes and understand
|
||||||
|
their meaning.
|
||||||
|
|
||||||
|
Resources can also be detected automatically through `resource.Detector`
|
||||||
|
implementations. These `Detector`s may discover information about the currently
|
||||||
|
running process, the operating system it is running on, the cloud provider
|
||||||
|
hosting that operating system instance, or any number of other resource
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
```go
|
||||||
|
resources := resource.New(context.Background(),
|
||||||
|
resource.WithFromEnv(), // pull attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables
|
||||||
|
resource.WithProcess(), // This option configures a set of Detectors that discover process information
|
||||||
|
resource.WithOS(), // This option configures a set of Detectors that discover OS information
|
||||||
|
resource.WithContainer(), // This option configures a set of Detectors that discover container information
|
||||||
|
resource.WithHost(), // This option configures a set of Detectors that discover host information
|
||||||
|
resource.WithDetectors(thirdparty.Detector{}), // Bring your own external Detector implementation
|
||||||
|
resource.WithAttributes(attribute.String("foo", "bar")), // Or specify resource attributes directly
|
||||||
|
)
|
||||||
|
```
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
title: Sampling
|
||||||
|
weight: 80
|
||||||
|
---
|
||||||
|
|
||||||
|
Sampling is a process that restricts the amount of traces that are generated by
|
||||||
|
a system. The exact sampler you should use depends on your specific needs, but
|
||||||
|
in general you should make a decision at the start of a trace, and allow the
|
||||||
|
sampling decision to propagate to other services.
|
||||||
|
|
||||||
|
A sampler needs to be set on the tracer provider when its configured, as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
provider := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`AlwaysSample` and `NeverSample` are fairly self-explanatory. Always means that
|
||||||
|
every trace will be sampled, the converse holds as true for Never. When you're
|
||||||
|
getting started, or in a development environment, you'll almost always want to
|
||||||
|
use `AlwaysSample`.
|
||||||
|
|
||||||
|
Other samplers include:
|
||||||
|
|
||||||
|
- `TraceIDRatioBased`, which will sample a fraction of traces, based on the
|
||||||
|
fraction given to the sampler. Thus, if you set this to .5, half of traces
|
||||||
|
will be sampled.
|
||||||
|
- `ParentBased`, which behaves differently based on the incoming sampling
|
||||||
|
decision. In general, this will sample spans that have parents that were
|
||||||
|
sampled, and will not sample spans whose parents were _not_ sampled.
|
||||||
|
|
||||||
|
When you're in production, you should consider using the `TraceIDRatioBased`
|
||||||
|
sampler with the `ParentBased` sampler.
|
|
@ -179,8 +179,6 @@ module:
|
||||||
mounts:
|
mounts:
|
||||||
- source: content/en
|
- source: content/en
|
||||||
target: content
|
target: content
|
||||||
- source: content-modules/opentelemetry-go/website_docs
|
|
||||||
target: content/docs/instrumentation/go
|
|
||||||
- source: tmp/otel/specification
|
- source: tmp/otel/specification
|
||||||
target: content/docs/specs/otel
|
target: content/docs/specs/otel
|
||||||
- source: tmp/otlp/docs/specification.md
|
- source: tmp/otlp/docs/specification.md
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
{{/*
|
|
||||||
TODO: keep this duplicate of `layouts/shortcodes/docs/instrumentation/index-intro.md`
|
|
||||||
only until the Go docs are updated.
|
|
||||||
*/ -}}
|
|
||||||
|
|
||||||
{{ $data := index $.Site.Data.instrumentation.languages (.Get 0) }}
|
|
||||||
{{ $name := $data.name }}
|
|
||||||
{{ $relUrl := printf "https://github.com/open-telemetry/opentelemetry-%s/releases" (.Get 0) -}}
|
|
||||||
{{ $tracesStatus := $data.status.traces | humanize }}
|
|
||||||
{{ $metricsStatus := $data.status.metrics | humanize }}
|
|
||||||
{{ $logsStatus := $data.status.logs | humanize }}
|
|
||||||
{{ if in "Stable Experimental" $tracesStatus }}
|
|
||||||
{{ $tracesStatus = printf "[%s](/docs/specs/otel/versioning-and-stability/#%s)" $tracesStatus $data.status.traces }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if in "Stable Experimental" $metricsStatus }}
|
|
||||||
{{ $metricsStatus = printf "[%s](/docs/specs/otel/versioning-and-stability/#%s)" $metricsStatus $data.status.metrics }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if in "Stable Experimental" $logsStatus }}
|
|
||||||
{{ $logsStatus = printf "[%s](/docs/specs/otel/versioning-and-stability/#%s)" $logsStatus $data.status.logs }}
|
|
||||||
{{ end }}
|
|
||||||
This is the OpenTelemetry {{ $name }} documentation. OpenTelemetry is an
|
|
||||||
observability framework -- an API, SDK, and tools that are designed to aid in
|
|
||||||
the generation and collection of application telemetry data such as metrics,
|
|
||||||
logs, and traces. This documentation is designed to help you understand how to
|
|
||||||
get started using OpenTelemetry {{ $name }}.
|
|
||||||
|
|
||||||
## Status and Releases
|
|
||||||
|
|
||||||
The current status of the major functional components for OpenTelemetry {{ $name }} is
|
|
||||||
as follows:
|
|
||||||
|
|
||||||
| Traces | Metrics | Logs |
|
|
||||||
| -------- | ------- | ------- |
|
|
||||||
| {{ $tracesStatus }} | {{ $metricsStatus }} | {{ $logsStatus }} |
|
|
||||||
|
|
||||||
For releases, including the [latest release][], see [Releases][].
|
|
||||||
{{- .Inner }}
|
|
||||||
|
|
||||||
[latest release]: {{ $relUrl }}/latest
|
|
||||||
[Releases]: {{ $relUrl }}
|
|
Loading…
Reference in New Issue