NOOP(no operations)

默认的interface可以是NOOP,以兼容(避免)或者说最大程度较少代码的侵入性

在编程中,NOOP(无操作)是一个常用的概念,它表示一个不执行任何操作的操作或函数。这在很多场景中都很有用。以下是关于 Go 语言中 NOOP 的一些要点:

  1. 接口默认实现:NOOP 可以作为一个接口的默认实现。例如,在 OpenTelemetry 中,当没有配置 TracerProvider 时,会使用一个 NOOP tracer。这样,如果用户没有提供他们自己的实现,代码仍然可以正常运行,而不会崩溃或抛出错误。

  2. 测试和调试:NOOP 也可以在测试和调试中使用。例如,你可能想要在测试中使用一个 NOOP 实现,以便你可以专注于测试其他部分的代码,而不必担心接口的实现。

  3. 可选的功能:NOOP 可以用于可选的功能。例如,你可能有一个可以记录详细日志的接口,但在某些情况下,你可能不希望记录这些日志。在这种情况下,你可以使用一个 NOOP 实现。

在 Go 语言中实现一个 NOOP 接口通常很简单。你只需定义一个结构体,并为它实现接口的所有方法,但这些方法什么都不做。例如,以下是一个 NOOP tracer 的简单实现:

type NoopTracer struct{}

func (t NoopTracer) Start(ctx context.Context, name string) (context.Context, trace.Span) {
    return ctx, trace.NoopSpan{}
    }

func (t NoopTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
    return body(ctx)
    }

在这个例子中,NoopTracer 实现了一个 tracer 接口,但它的所有方法都没有做任何事情。这就是一个 NOOP 接口的简单实现。

以OpenTelemetr为例子,yOpenTelemetry 是一个用于观察、收集和分析软件的运行时数据,如指标、日志和追踪,以支持开发、观察、诊断和修复复杂的软件系统的开源项目。

在 OpenTelemetry Go 实现中,有许多地方使用了 Noop 接口。例如,当你创建一个新的 trace,但没有配置 tracer 提供者时,OpenTelemetry 会使用一个 Noop tracer。

以下是一个简化的示例,展示了在 OpenTelemetry 中如何使用 Noop tracer:

package main

import (
	"context"
	"fmt"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
)

func main() {
	ctx := context.Background()

	// Get a tracer. Since no tracer provider is configured, this will be a Noop tracer.
	tracer := otel.Tracer("example.com/HelloWorld")

	// Start a new trace. Since the tracer is a Noop tracer, this won't actually do anything.
	ctx, span := tracer.Start(ctx, "operation")
	span.End()

	// Verify that the span is a Valid span.
	spanCtx := trace.SpanContextFromContext(c.Request.Context())
	fmt.Printf("traceID: %+v spanID %v\n", spanCtx.TraceID().String(), spanCtx.SpanID().String())
	spanCtx.IsValid()
}

在这个例子中,我们使用 otel.Tracer 获取一个 tracer,但因为我们没有配置 tracer 提供者,所以返回的是一个 Noop tracer。当我们使用这个 Noop tracer 开始一个新的 trace 时,实际上并没有做任何事情。这就是 Noop 接口在 OpenTelemetry 中的一个应用示例。

那么如何配置 tracer 提供者(provider)

import (
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
)

type OpenTelemetry struct {
	Enable                   bool   `yaml:"enable"`
	CollectorEndpointHttpUrl string `yaml:"collectorEndpointHttpUrl"`
}

// tracerProvider returns an OpenTelemetry TracerProvider configured to use
// the Jaeger exporter that will send spans to the provided url. The returned
// TracerProvider will also use a Resource configured with all the information
// about the application.
func InitNewJaegerTracerProvider(disable bool, url, serviceName, id string) (*sdktrace.TracerProvider, error) {
	if disable {
		//不初始化,使用默认的noop provider,直接返回,所有trace的操作当作无效操作,不会panic,no operation
		return nil, nil
	}
	// Create the Jaeger exporter

	// WithAgentEndpoint(WithAgentHost(host), WithAgentPort(port)),
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
	if err != nil {
		return nil, err
	}
	tp := sdktrace.NewTracerProvider(
		// Always be sure to batch in production.
		sdktrace.WithBatcher(exp),
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
		// Record information about this application in an Resource.
		sdktrace.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String(serviceName),
			// attribute.String("environment", environment),
			// attribute.String("ID", id),
		)),
	)
	// Register our TracerProvider as the global so any imported
	// instrumentation in the future will default to using it.
	otel.SetTracerProvider(tp)
	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
	return tp, nil
}

2023-10-15