Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.26.0
require (
github.com/cloudwego/hertz v0.10.6
github.com/glebarez/sqlite v1.11.0
github.com/oekit/logx v0.9.2
github.com/oekit/logx v0.10.0
github.com/redis/go-redis/extra/redisotel/v9 v9.23.0
github.com/redis/go-redis/v9 v9.23.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ncruces/go-strftime v1.1.0 h1:vtPmPWMkb539RGV9WpWT9EwJGvrOCKvMNI4obWL47Hk=
github.com/ncruces/go-strftime v1.1.0/go.mod h1:yVaq7iwCN09vHRrKBrrflXT0M9tTMG2KmZ9Fj7xeG/g=
github.com/oekit/logx v0.9.2 h1:8RjQPrlVU7QjQqieqGrOY2RGpJD5fPKkB/rekTg+qAo=
github.com/oekit/logx v0.9.2/go.mod h1:OC5PMAH8VCq/nerGp72T6JJwwb9xoe0hCVEJ0kkXuL8=
github.com/oekit/logx v0.10.0 h1:swoa1h9oLMdoH6LVjHaTYFIlVSxwRlfV0lBMNmKFfLI=
github.com/oekit/logx v0.10.0/go.mod h1:hGifsM8pfOb8cR4KSmJE2j2oy3Q9KLEwKcfAgj+M/yY=
github.com/redis/go-redis/extra/rediscmd/v9 v9.23.0 h1:YOQCtePRR/cBrxtZB5u2ueYei3zGJm1n0KB2tvth0k0=
github.com/redis/go-redis/extra/rediscmd/v9 v9.23.0/go.mod h1:BzBb5fEjIwijUn8VVkXfQUwFTGyk2vGIVU19zv2GhsM=
github.com/redis/go-redis/extra/redisotel/v9 v9.23.0 h1:EBQ01zUjwarZZ4IoSTQNU9EEc223eB9tpSun6n5EaUU=
Expand Down
17 changes: 11 additions & 6 deletions gormotel/gorm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"io"
"regexp"
Expand Down Expand Up @@ -267,13 +268,17 @@ func (p *otelPlugin) after() gormHookFunc {
// 驱动跳过/流结束)不算失败,保持 Unset——与 Query/Exec 路径既有口径一致;
// Row 路径的查询期错误(rowConnPool 捞取)与回调链尾的 tx.Error 共用此判定,
// 避免两处口径漂移。
//
// 哨兵判定用 errors.Is(#111):业务「未命中」是最高频的被包装哨兵——调用方
// 常以 fmt.Errorf("%w")/errors.Join 包一层再上抛,此前的 switch == 相等比较
// 对包装后的错误全部落空,未命中查询被 RecordError + SetStatus(Error),
// APM 上未命中查询全量报红,与本注释声明的「业务语义性错误不算失败」相反。
func recordSpanError(span trace.Span, err error) {
switch err {
case nil,
gorm.ErrRecordNotFound,
driver.ErrSkip,
io.EOF,
sql.ErrNoRows:
if err == nil ||
errors.Is(err, gorm.ErrRecordNotFound) ||
errors.Is(err, driver.ErrSkip) ||
errors.Is(err, io.EOF) ||
errors.Is(err, sql.ErrNoRows) {
return
}
span.RecordError(err)
Expand Down
26 changes: 19 additions & 7 deletions init.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,31 @@ func newGRPCExportingProviders(cfg Config, sampler sdktrace.Sampler, res *resour
return nil, nil, fmt.Errorf("tracing: create otlp metric exporter: %w", err)
}

mp, err := assembleMeterProvider(metricExp, tp, res)
if err != nil {
return nil, nil, err
}
return tp, mp, nil
}

// assembleMeterProvider 装配 metrics 管线尾段(#110,gRPC 与 HTTP 两条导出
// 分支单源):PeriodicReader(metricExportInterval)+ MeterProvider(挂
// resource)+ runtime.Start + 失败回滚(mp 与传入的 trace provider 一并
// Shutdown)。
//
// runtime.Start 必须显式指定 provider:无参 Start() 在调用时刻捕获
// otel.GetMeterProvider()——此刻全局还是 noop(SetMeterProvider 在 Init
// 返回后才执行),runtime 指标全部静默丢失;重复 Init 时则挂到上一轮已被
// Shutdown 的 provider 上同样丢失。
func assembleMeterProvider(metricExp metric.Exporter, tp *sdktrace.TracerProvider, res *resource.Resource) (*metric.MeterProvider, error) {
reader := metric.WithReader(metric.NewPeriodicReader(metricExp, metric.WithInterval(metricExportInterval)))
mp := metric.NewMeterProvider(reader, metric.WithResource(res))

// 必须显式指定 provider:无参 runtime.Start() 在调用时刻捕获
// otel.GetMeterProvider()——此刻全局还是 noop(SetMeterProvider 在
// Init 返回后才执行),runtime 指标全部静默丢失;重复 Init 时则挂到
// 上一轮已被 Shutdown 的 provider 上同样丢失
if err := runtime.Start(runtime.WithMeterProvider(mp)); err != nil {
_ = mp.Shutdown(context.Background())
_ = tp.Shutdown(context.Background())
return nil, nil, fmt.Errorf("tracing: start runtime metrics collector: %w", err)
return nil, fmt.Errorf("tracing: start runtime metrics collector: %w", err)
}
return tp, mp, nil
return mp, nil
}

// buildSampler 将 SampleRate 转换为采样器。
Expand Down
20 changes: 10 additions & 10 deletions otlphttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/url"
"strings"

"go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
Expand Down Expand Up @@ -59,6 +58,13 @@ func parseOTLPHTTPEndpoint(endpoint string, insecure bool) (otlpHTTPEndpoint, er
}
if !strings.Contains(raw, "://") {
// 无 scheme:host:port 形态,scheme 由 Insecure 决定。
// 带基址路径的写法(如 "collector:4318/api/otel")不收(#112):
// 整串被当 host:port 后 basePath 恒空,/api/otel 段静默丢失、导出
// 404 循环——与 validateGRPCEndpoint「不合规输入报错,不静默断供」
// 的既定哲学对齐。错误不回显端点原值(可能带凭据/令牌)。
if strings.Contains(raw, "/") {
return ep, errors.New("tracing: OTLP/HTTP endpoint contains a path but no scheme; write the full URL form (http://host:port/path or https://...)")
}
ep.hostPort = raw
return ep, nil
}
Expand Down Expand Up @@ -175,15 +181,9 @@ func newHTTPExportingProviders(cfg Config, sampler sdktrace.Sampler, res *resour
return nil, nil, fmt.Errorf("tracing: create otlp http metric exporter: %w", err)
}

reader := metric.WithReader(metric.NewPeriodicReader(metricExp, metric.WithInterval(metricExportInterval)))
mp := metric.NewMeterProvider(reader, metric.WithResource(res))

// 与 grpc 分支同一纪律:必须显式指定 provider(无参 runtime.Start() 在
// 调用时刻捕获的全局还是 noop,runtime 指标会静默丢失)。
if err := runtime.Start(runtime.WithMeterProvider(mp)); err != nil {
_ = mp.Shutdown(context.Background())
_ = tp.Shutdown(context.Background())
return nil, nil, fmt.Errorf("tracing: start runtime metrics collector: %w", err)
mp, err := assembleMeterProvider(metricExp, tp, res)
if err != nil {
return nil, nil, err
}
return tp, mp, nil
}
1 change: 1 addition & 0 deletions otlphttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestParseOTLPHTTPEndpoint(t *testing.T) {
{name: "trailing slash trimmed", endpoint: "https://host/api/otel/", wantHost: "host", wantBase: "/api/otel", wantSecure: true},
{name: "host port without scheme", endpoint: "otel-collector:4318", wantHost: "otel-collector:4318", wantBase: "", wantSecure: true},
{name: "host port without scheme insecure", endpoint: "otel-collector:4318", insecure: true, wantHost: "otel-collector:4318", wantBase: "", wantSecure: false},
{name: "path without scheme rejected (#112)", endpoint: "collector:4318/api/otel", wantErr: true},
{name: "empty", endpoint: "", wantErr: true},
{name: "unsupported scheme", endpoint: "ftp://host/x", wantErr: true},
{name: "missing host", endpoint: "https:///path", wantErr: true},
Expand Down
Loading