仪表化
Instrumention 是指您自己向应用程序添加可观察性代码的过程。
如果您正在 instrumenting 应用程序,您需要为您使用的语言使用 OpenTelemetry SDK。然后,您将使用 SDK 初始化 OpenTelemetry,并使用 API instrument 您的代码。这将从您的应用程序和任何也带有 instrumention 的已安装库中发出遥测数据。
如果您正在 instrumenting 库,请仅安装您语言的 OpenTelemetry API 包。您的库不会自行发出遥测数据。只有当它作为使用 OpenTelemetry SDK 的应用程序的一部分时,它才会发出遥测数据。有关 instrumenting 库的更多信息,请参阅 Libraries。
有关 OpenTelemetry API 和 SDK 的更多信息,请参阅 specification。
设置
追踪
获取 Tracer
要创建 Span,您需要先获取或初始化一个 Tracer。
确保您已安装正确的包
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/trace \
go.opentelemetry.io/otel/sdk \
然后初始化一个 exporter、resources、tracer provider,最后是 tracer。
package app
import (
"context"
"fmt"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.37.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 newTracerProvider(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 := newTracerProvider(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("example.io/package/name")
}
您现在可以访问 tracer 来手动仪表您的代码。
如果您正在添加手动 Span 并结合基于 eBPF 的 Go Zero-code 仪表(例如与 OBI),请不要设置全局 Tracer Provider。有关更多信息,请参阅 Auto SDK 文档。
创建 Span
Span 由 Tracer 创建。如果您还没有初始化 Tracer,则需要这样做。
要使用 Tracer 创建 Span,您还需要获取一个 context.Context 实例的句柄。这些通常来自请求对象等,并且可能已经包含来自 instrumentation library 的父 Span。
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
}
在 Go 中,context 包用于存储活动 Span。当您启动一个 Span 时,您将获得不仅是创建的 Span,还有包含它的修改后的 Context 的句柄。
一旦 Span 完成,它就是不可变的,无法再修改。
获取当前 Span
要获取当前 Span,您需要从您拥有的 context.Context 中提取它。
// 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
如果您想在某个时间点向当前 Span 添加信息,这会很有帮助。
创建嵌套 span
您可以创建一个嵌套 Span 来跟踪嵌套操作中的工作。
如果当前您拥有的 context.Context 实例已经包含一个 Span,那么创建一个新的 Span 将使其成为一个嵌套 Span。例如:
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.
}
一旦 Span 完成,它就是不可变的,无法再修改。
Span 属性
属性是应用于 Span 的元数据的键值对,对于聚合、过滤和分组跟踪非常有用。属性可以在 Span 创建时添加,也可以在 Span 完成之前的任何其他时间添加。
// 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!"))
属性键也可以预先计算
var myKey = attribute.Key("myCoolAttribute")
span.SetAttributes(myKey.String("a value"))
语义属性
语义属性是由 OpenTelemetry 规范 定义的属性,旨在为跨多种语言、框架和运行时提供的常见概念(如 HTTP 方法、状态码、用户代理等)提供一组共享的属性键。这些属性可在 go.opentelemetry.io/otel/semconv/v1.37.0 包中使用。
有关详细信息,请参阅 Trace semantic conventions。
事件
事件是 Span 上一个可读的消息,表示在其生命周期中“发生的某事”。例如,想象一个函数需要对互斥锁保护的资源进行独占访问。可以在两个点创建事件——一次是在我们尝试获取资源访问权限时,另一次是在我们获得互斥锁时。
span.AddEvent("Acquiring lock")
mutex.Lock()
span.AddEvent("Got lock, doing work...")
// do stuff
span.AddEvent("Unlocking")
mutex.Unlock()
事件的一个有用特性是它们的 EtimeStamps 显示为与 Span 开始时间的偏移量,让您可以轻松查看它们之间经过了多长时间。
事件也可以拥有自己的属性——
span.AddEvent("Cancelled wait due to external signal", trace.WithAttributes(attribute.Int("pid", 4328), attribute.String("signal", "SIGHUP")))
设置跨度状态
可以在 Span 上设置 Status,通常用于指定 Span 未成功完成 - Error。默认情况下,所有 spans 的状态都是 Unset,这意味着 span 完成时没有错误。Ok 状态保留给您需要明确将 span 标记为成功而不是坚持默认的 Unset(即,“无错误”)的情况。
可以在 span 完成之前的任何时间设置状态。
import (
// ...
"go.opentelemetry.io/otel/codes"
// ...
)
// ...
result, err := operationThatCouldFail()
if err != nil {
span.SetStatus(codes.Error, "operationThatCouldFail failed")
}
记录错误
如果您有一个操作失败,并且您希望捕获它产生的错误,您可以记录该错误。
import (
// ...
"go.opentelemetry.io/otel/codes"
// ...
)
// ...
result, err := operationThatCouldFail()
if err != nil {
span.SetStatus(codes.Error, "operationThatCouldFail failed")
span.RecordError(err)
}
强烈建议您在使用 RecordError 时还将 Span 的状态设置为 Error,除非您不希望将跟踪失败操作的 Span 视为错误 Span。RecordError 函数在调用时不会自动设置 Span 状态。
Propagators 和 Context
跟踪可以扩展到单个进程之外。这需要上下文传播,一种将跟踪标识符发送到远程进程的机制。
为了在网络上传播跟踪上下文,必须将 propagator 注册到 OpenTelemetry API。
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
...
otel.SetTextMapPropagator(propagation.TraceContext{})
OpenTelemetry 还支持 B3 报头格式,以便与不支持 W3C TraceContext 标准的现有跟踪系统(
go.opentelemetry.io/contrib/propagators/b3)兼容。
配置上下文传播后,您很可能希望使用自动仪表来处理实际管理上下文序列化的后台工作。
指标
要开始生成 metrics,您需要一个已初始化的 MeterProvider,它允许您创建一个 Meter。Meters 允许您创建 instrument,您可以使用它们来创建不同类型的 metrics。OpenTelemetry Go 目前支持以下 instruments:
- Counter,一个支持非负增量的同步 instrument。
- Asynchronous Counter,一个支持非负增量的异步 instrument。
- Histogram,一个支持对统计有意义的任意值的同步 instrument,例如直方图、摘要或百分位数。
- Synchronous Gauge,一个支持非加性值的同步 instrument,例如室温。
- Asynchronous Gauge,一个支持非加性值的异步 instrument,例如室温。
- UpDownCounter,一个支持增减的同步 instrument,例如活动请求的数量。
- Asynchronous UpDownCounter,一个支持增减的异步 instrument。
有关同步和异步仪器以及哪种类型最适合您的用例的更多信息,请参阅 Supplementary Guidelines (补充指南)。
如果 MeterProvider 不是由 instrumentation library 或手动创建的,OpenTelemetry Metrics API 将使用无操作(no-op)实现并无法生成数据。
您可以在这里找到更详细的包文档:
- Metrics API:
go.opentelemetry.io/otel/metric - Metrics SDK:
go.opentelemetry.io/otel/sdk/metric
初始化 Metrics
如果您正在 Instrument 一个库,请跳过此步骤。
要启用您应用中的 metrics,您需要一个已初始化的 MeterProvider,它允许您创建一个 Meter。
如果未创建 MeterProvider,OpenTelemetry 的 metrics API 将使用无操作(no-op)实现并无法生成数据。因此,您必须修改源代码以包含使用以下包的 SDK 初始化代码:
go.opentelemetry.io/otelgo.opentelemetry.io/otel/sdk/metricgo.opentelemetry.io/otel/sdk/resourcego.opentelemetry.io/otel/exporters/stdout/stdoutmetric
确保您已安装正确的 Go 模块
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/sdk/metric
然后初始化一个 resources、metrics exporter 和 metrics provider。
package main
import (
"context"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func main() {
// Create resource.
res, err := newResource()
if err != nil {
panic(err)
}
// Create a meter provider.
// You can pass this instance directly to your instrumented code if it
// accepts a MeterProvider instance.
meterProvider, err := newMeterProvider(res)
if err != nil {
panic(err)
}
// Handle shutdown properly so nothing leaks.
defer func() {
if err := meterProvider.Shutdown(context.Background()); err != nil {
log.Println(err)
}
}()
// Register as global meter provider so that it can be used via otel.Meter
// and accessed using otel.GetMeterProvider.
// Most instrumentation libraries use the global meter provider as default.
// If the global meter provider is not set then a no-op implementation
// is used, which fails to generate data.
otel.SetMeterProvider(meterProvider)
}
func newResource() (*resource.Resource, error) {
return resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("my-service"),
semconv.ServiceVersion("0.1.0"),
),
)
}
func newMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) {
metricExporter, err := stdoutmetric.New()
if err != nil {
return nil, err
}
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(metric.NewPeriodicReader(metricExporter,
// Default is 1m. Set to 3s for demonstrative purposes.
metric.WithInterval(3*time.Second))),
)
return meterProvider, nil
}
现在 MeterProvider 已配置,您可以获取一个 Meter。
获取 Meter
在您的应用程序中,任何您手动仪表代码的地方,都可以调用 otel.Meter 来获取一个 meter。例如:
import "go.opentelemetry.io/otel"
var meter = otel.Meter("example.io/package/name")
同步和异步 Instrument
OpenTelemetry instruments 要么是同步的,要么是异步的(可观测的)。
同步 instruments 在被调用时进行测量。测量过程与其他任何函数调用一样,是在程序执行期间进行的另一次调用。定期地,这些测量的聚合将由配置的 exporter 导出。由于测量与导出值是解耦的,因此一个导出周期可能包含零个或多个聚合的测量。
另一方面,异步 instruments 在 SDK 的请求时提供测量。当 SDK 导出时,会在创建 instrument 时提供的回调被调用。这个回调向 SDK 提供一个测量,该测量立即被导出。异步 instruments 上的所有测量都只在每个导出周期执行一次。
异步 instruments 在多种情况下很有用,例如:
- 当更新 counter 在计算上不便宜,而您不希望当前执行线程等待测量时。
- 观察需要以与程序执行无关的频率发生(即,当它们与请求生命周期绑定时,无法准确测量)。
- 没有已知的测量值的时间戳。
在这些情况下,直接观测累计值通常比在后处理(同步示例)中聚合一系列增量更好。
使用 Counter
Counter 可用于测量非负的、递增的值。
例如,这是报告 HTTP handler 调用次数的方法:
import (
"net/http"
"go.opentelemetry.io/otel/metric"
)
func init() {
apiCounter, err := meter.Int64Counter(
"api.counter",
metric.WithDescription("Number of API calls."),
metric.WithUnit("{call}"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
apiCounter.Add(r.Context(), 1)
// do some work in an API call
})
}
使用 UpDown Counter
UpDown Counter 可以增减,允许您观察一个向上或向下变化的累积值。
例如,这是报告某个集合中项目数量的方法:
import (
"context"
"go.opentelemetry.io/otel/metric"
)
var itemsCounter metric.Int64UpDownCounter
func init() {
var err error
itemsCounter, err = meter.Int64UpDownCounter(
"items.counter",
metric.WithDescription("Number of items."),
metric.WithUnit("{item}"),
)
if err != nil {
panic(err)
}
}
func addItem() {
// code that adds an item to the collection
itemsCounter.Add(context.Background(), 1)
}
func removeItem() {
// code that removes an item from the collection
itemsCounter.Add(context.Background(), -1)
}
使用 Gauge
Gauge 用于测量发生更改时的非加性值。
例如,这是报告 CPU 风扇当前速度的方法:
import (
"net/http"
"go.opentelemetry.io/otel/metric"
)
var (
fanSpeedSubscription chan int64
speedGauge metric.Int64Gauge
)
func init() {
var err error
speedGauge, err = meter.Int64Gauge(
"cpu.fan.speed",
metric.WithDescription("Speed of CPU fan"),
metric.WithUnit("RPM"),
)
if err != nil {
panic(err)
}
getCPUFanSpeed := func() int64 {
// Generates a random fan speed for demonstration purpose.
// In real world applications, replace this to get the actual fan speed.
return int64(1500 + rand.Intn(1000))
}
fanSpeedSubscription = make(chan int64, 1)
go func() {
defer close(fanSpeedSubscription)
for idx := 0; idx < 5; idx++ {
// Synchronous gauges are used when the measurement cycle is
// synchronous to an external change.
time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
fanSpeed := getCPUFanSpeed()
fanSpeedSubscription <- fanSpeed
}
}()
}
func recordFanSpeed() {
ctx := context.Background()
for fanSpeed := range fanSpeedSubscription {
speedGauge.Record(ctx, fanSpeed)
}
}
使用 Histogram
Histogram 用于测量随时间推移的值的分布。
例如,这是报告 HTTP handler 响应时间分布的方法:
import (
"net/http"
"time"
"go.opentelemetry.io/otel/metric"
)
func init() {
histogram, err := meter.Float64Histogram(
"task.duration",
metric.WithDescription("The duration of task execution."),
metric.WithUnit("s"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// do some work in an API call
duration := time.Since(start)
histogram.Record(r.Context(), duration.Seconds())
})
}
使用 Observable (Async) Counter
Observable Counter 可用于测量一个累加的、非负的、单调递增的值。
例如,这是报告应用程序启动以来的时间的方法:
import (
"context"
"time"
"go.opentelemetry.io/otel/metric"
)
func init() {
start := time.Now()
if _, err := meter.Float64ObservableCounter(
"uptime",
metric.WithDescription("The duration since the application started."),
metric.WithUnit("s"),
metric.WithFloat64Callback(func(_ context.Context, o metric.Float64Observer) error {
o.Observe(float64(time.Since(start).Seconds()))
return nil
}),
); err != nil {
panic(err)
}
}
使用 Observable (Async) UpDown Counter
Observable UpDown Counter 可以增减,允许您测量一个累加的、非负的、非单调递增的累积值。
例如,这是报告一些数据库 metrics 的方法:
import (
"context"
"database/sql"
"go.opentelemetry.io/otel/metric"
)
// registerDBMetrics registers asynchronous metrics for the provided db.
// Make sure to unregister metric.Registration before closing the provided db.
func registerDBMetrics(db *sql.DB, meter metric.Meter, poolName string) (metric.Registration, error) {
max, err := meter.Int64ObservableUpDownCounter(
"db.client.connections.max",
metric.WithDescription("The maximum number of open connections allowed."),
metric.WithUnit("{connection}"),
)
if err != nil {
return nil, err
}
waitTime, err := meter.Int64ObservableUpDownCounter(
"db.client.connections.wait_time",
metric.WithDescription("The time it took to obtain an open connection from the pool."),
metric.WithUnit("ms"),
)
if err != nil {
return nil, err
}
reg, err := meter.RegisterCallback(
func(_ context.Context, o metric.Observer) error {
stats := db.Stats()
o.ObserveInt64(max, int64(stats.MaxOpenConnections))
o.ObserveInt64(waitTime, int64(stats.WaitDuration))
return nil
},
max,
waitTime,
)
if err != nil {
return nil, err
}
return reg, nil
}
使用 Observable (Async) Gauge
Observable Gauge 应用于测量非累加值。
例如,这是报告应用程序使用的堆对象内存使用量的方法:
import (
"context"
"runtime"
"go.opentelemetry.io/otel/metric"
)
func init() {
if _, err := meter.Int64ObservableGauge(
"memory.heap",
metric.WithDescription(
"Memory usage of the allocated heap objects.",
),
metric.WithUnit("By"),
metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
o.Observe(int64(m.HeapAlloc))
return nil
}),
); err != nil {
panic(err)
}
}
添加属性
您可以通过使用 WithAttributeSet 或 WithAttributes 选项来添加属性。
import (
"net/http"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func init() {
apiCounter, err := meter.Int64UpDownCounter(
"api.finished.counter",
metric.WithDescription("Number of finished API calls."),
metric.WithUnit("{call}"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// do some work in an API call and set the response HTTP status code
apiCounter.Add(r.Context(), 1,
metric.WithAttributes(semconv.HTTPResponseStatusCode(statusCode)))
})
}
注册 View
View 为 SDK 用户提供了自定义 SDK 生成的指标输出的灵活性。您可以自定义要处理或忽略的指标仪器。您还可以自定义聚合以及要在指标上报告的属性。
每个 instrument 都有一个默认视图,它保留原始名称、描述和属性,并具有基于 instrument 类型的默认聚合。当注册的视图匹配 instrument 时,默认视图将被注册的视图替换。匹配 instrument 的其他注册视图是附加的,并导致 instrument 产生多个导出的 metric。
您可以使用 NewView 函数创建视图,并使用 WithView 选项进行注册。
例如,这是创建视图以将 http instrumentation library 的 v0.34.0 版本中的 latency instrument 重命名为 request.latency 的方法:
view := metric.NewView(metric.Instrument{
Name: "latency",
Scope: instrumentation.Scope{
Name: "http",
Version: "0.34.0",
},
}, metric.Stream{Name: "request.latency"})
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
例如,这是创建视图以将 http instrumentation library 的 latency instrument 作为指数直方图报告的方法:
view := metric.NewView(
metric.Instrument{
Name: "latency",
Scope: instrumentation.Scope{Name: "http"},
},
metric.Stream{
Aggregation: metric.AggregationBase2ExponentialHistogram{
MaxSize: 160,
MaxScale: 20,
},
},
)
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
SDK 在导出 metrics 之前会过滤 metrics 和 attributes。例如,您可以使用视图来减少高基数 metrics 的内存使用量,或删除可能包含敏感数据的 attributes。
这是创建视图以丢弃 http instrumentation library 的 latency instrument 的方法:
view := metric.NewView(
metric.Instrument{
Name: "latency",
Scope: instrumentation.Scope{Name: "http"},
},
metric.Stream{Aggregation: metric.AggregationDrop{}},
)
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
这是创建视图以移除 http instrumentation library 的 latency instrument 记录的 http.request.method attribute 的方法:
view := metric.NewView(
metric.Instrument{
Name: "latency",
Scope: instrumentation.Scope{Name: "http"},
},
metric.Stream{AttributeFilter: attribute.NewDenyKeysFilter("http.request.method")},
)
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
criteria 的 Name 字段支持通配符模式匹配。* 通配符被识别为匹配零个或多个字符,? 被识别为匹配恰好一个字符。例如,模式 * 匹配所有 instrument 名称。
以下示例显示了如何创建视图,为名称后缀为 .ms 的任何 instrument 设置单位为毫秒:
view := metric.NewView(
metric.Instrument{Name: "*.ms"},
metric.Stream{Unit: "ms"},
)
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
NewView 函数提供了一种便捷的创建视图的方法。如果 NewView 无法提供您所需的功能,您可以直接创建自定义 View。
例如,这是创建视图以使用正则表达式匹配,确保所有数据流名称都带有其使用的单位后缀的方法:
re := regexp.MustCompile(`[._](ms|byte)$`)
var view metric.View = func(i metric.Instrument) (metric.Stream, bool) {
// In a custom View function, you need to explicitly copy
// the name, description, and unit.
s := metric.Stream{Name: i.Name, Description: i.Description, Unit: i.Unit}
// Any instrument that does not have a unit suffix defined, but has a
// dimensional unit defined, update the name with a unit suffix.
if re.MatchString(i.Name) {
return s, false
}
switch i.Unit {
case "ms":
s.Name += ".ms"
case "By":
s.Name += ".byte"
default:
return s, false
}
return s, true
}
meterProvider := metric.NewMeterProvider(
metric.WithView(view),
)
日志
Log 与 metrics 和 traces 不同,没有面向用户的 OpenTelemetry logs API。取而代之的是,有一些工具可以将来自现有流行日志包(如 slog、logrus、zap、logr)的日志桥接到 OpenTelemetry 生态系统中。有关此设计决策的理由,请参阅 Logging specification。
下面讨论的两种典型工作流程分别满足不同的应用程序需求。
直接到 Collector
状态:实验性
在直接到 Collector 工作流程中,日志直接从应用程序通过网络协议(例如 OTLP)发送到 Collector。此工作流程设置简单,因为它不需要任何额外的日志转发组件,并允许应用程序轻松发出符合 log data model 的结构化日志。但是,应用程序将日志排队并导出到网络位置所需的开销可能不适合所有应用程序。
要使用此工作流程:
- 配置 OpenTelemetry Log SDK 以将 log records 导出到所需的目标(Collector 或其他)。
- 使用适当的 Log Bridge。
Logs SDK
Logs SDK 规定了在使用 direct-to-Collector 工作流程时日志的处理方式。在使用 log forwarding 工作流程时,不需要 log SDK。
典型的 log SDK 配置安装一个批处理 log record processor 和一个 OTLP exporter。
要启用您应用程序中的 logs,您需要一个已初始化的 LoggerProvider,它允许您使用 Log Bridge。
如果未创建 LoggerProvider,OpenTelemetry 的 logs API 将使用无操作(no-op)实现并无法生成数据。因此,您必须修改源代码以包含使用以下包的 SDK 初始化代码:
go.opentelemetry.io/otelgo.opentelemetry.io/otel/sdk/loggo.opentelemetry.io/otel/sdk/resourcego.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
确保您已安装正确的 Go 模块
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/sdk/log
然后初始化一个 logger provider。
package main
import (
"context"
"fmt"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func main() {
ctx := context.Background()
// Create resource.
res, err := newResource()
if err != nil {
panic(err)
}
// Create a logger provider.
// You can pass this instance directly when creating bridges.
loggerProvider, err := newLoggerProvider(ctx, res)
if err != nil {
panic(err)
}
// Handle shutdown properly so nothing leaks.
defer func() {
if err := loggerProvider.Shutdown(ctx); err != nil {
fmt.Println(err)
}
}()
// Register as global logger provider so that it can be accessed global.LoggerProvider.
// Most log bridges use the global logger provider as default.
// If the global logger provider is not set then a no-op implementation
// is used, which fails to generate data.
global.SetLoggerProvider(loggerProvider)
}
func newResource() (*resource.Resource, error) {
return resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("my-service"),
semconv.ServiceVersion("0.1.0"),
),
)
}
func newLoggerProvider(ctx context.Context, res *resource.Resource) (*log.LoggerProvider, error) {
exporter, err := otlploghttp.New(ctx)
if err != nil {
return nil, err
}
processor := log.NewBatchProcessor(exporter)
provider := log.NewLoggerProvider(
log.WithResource(res),
log.WithProcessor(processor),
)
return provider, nil
}
现在 LoggerProvider 已配置,您可以使用它来设置 Log Bridge。
Log Bridge
Log bridge 是一个组件,它使用 Logs Bridge API 将来自现有日志包的日志桥接到 OpenTelemetry Log SDK。
可在 OpenTelemetry registry 中找到可用的 log bridges 的完整列表。
每个 log bridge 的文档都应包含使用示例。
通过文件或 stdout
在文件或 stdout 工作流程中,日志被写入文件或标准输出。另一个组件(例如 FluentBit)负责读取/跟踪日志,将其解析为更结构化的格式,并将其转发到目标,例如 Collector。在应用程序不容忍 direct-to-Collector 额外开销的情况下,此工作流程可能更受欢迎。但是,它要求所有下游所需的日志字段都已编码到日志中,并且读取日志的组件将数据解析到 log data model 中。日志转发组件的安装和配置超出了本文档的范围。
下一步
您还需要配置适当的 exporter 以将 telemetry data 导出到一个或多个 telemetry backend。