otelzap :opentelemetry-go-extra/otelzap at main · uptrace/opentelemetry-go-extra (github.com)
go get github.com/uptrace/opentelemetry-go-extra/otelzap
声明 Tracer ,一个应用创建一个 Tracer (需要从venus中来传来的trace_id构造
)
var consumerTracer = otel.Tracer("profile-consumer",
trace.WithInstrumentationAttributes(attribute.String("profile.work", "consumer")))
处理异常,日志记录 & Span 记录错误 & 设置状态
func handlerErr(span trace.Span, ctx context.Context, msg string, err error) {
// otelzap 集成zap
otelzap.L().Ctx(ctx).Error(msg, zap.Error(err))
span.RecordError(err)
span.SetStatus(codes.Error, msg)
}
在每一个Handler中使用同一个 Tracer 创建Span
func (profileCtx *ProfileContext) UnpackKafkaMessage(ctx context.Context) (needBreak bool, tpsStatus string, contextErr error) {
var span trace.Span
// 根Span,类型为内部
profileCtx.Ctx, span = consumerTracer.Start(profileCtx.Ctx, "UnpackKafkaMessage",
trace.WithNewRoot(), trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()
if contextErr = json.Unmarshal(profileCtx.msg.Value, &profileCtx.Event); contextErr != nil {
profileCtx.Status = state.StatusUnmarshalError
handlerErr(span, profileCtx.Ctx, "unmarshal error", contextErr)
return
}
// 设置属性
if span.IsRecording() {
span.SetAttributes(
attribute.String("event.id", profileCtx.Event.ID),
)
}
log.Logger.Info("[UnpackKafkaItem] unpack kafka item success", zap.Any("event", profileCtx.Event))
return
}
根据组员 张锐添 提供的可用性方案和代码实现
metric PeriodicReader
的上报间隔MeterProvider
Int64ObservableGauge
在每个测量收集周期,异步记录一次 StatusAlive
的 瞬时值func InitAvailabilityObserver() func() {
ctx := context.Background()
res, err := createResource(ctx)
handleErr(err, "failed to create resource")
otelAgentAddr := config.Profile.GetString("otelclient.otelExporterOtlpEndpoint")
metricExp := createMetricExp(err, ctx, otelAgentAddr)
handleErr(err, "Failed to create the otelclient metric exporter")
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(
metricExp,
sdkmetric.WithInterval(10*time.Second), // 10s上报一次
sdkmetric.WithTimeout(2*time.Second), // 设置超时
),
),
)
const (
StatusAlive = iota // 可用状态
)
_, err = mp.Meter("profile-availability-meter").Int64ObservableGauge(
"profile/alive_observer",
// 每个测量值收集周期,异步记录一次瞬时 int64 测量值
metric.WithInt64Callback(func(ctx context.Context, observer metric.Int64Observer) error {
observer.Observe(
StatusAlive,
// metric.WithAttributes(attribute.String("", "")),
)
return nil
}),
)
handleErr(err, "Failed to create the ObservableGauge")
log.Logger.Info("init availabilityObserver success")
return func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := mp.Shutdown(ctx); err != nil {
otel.Handle(err)
}
}
}
在 SigNoz Web 的 Dashboards 配置 ClickHouse Query
SELECT
toStartOfMinute(toTimeZone(toDateTime(timestamp_ms/1000), 'Asia/Shanghai')) AS minute,
count(*)/6 AS result
FROM signoz_metrics.samples_v2
WHERE metric_name='profile_alive_observer' AND timestamp_ms >= 1000*toUnixTimestamp(toStartOfMinute(toTimeZone(now(), 'Asia/Shanghai') - INTERVAL 1 DAY))
GROUP BY minute
ORDER BY minute ASC
请求 Venus 的 Post 接口,Profile 消费了两次,由于上报的json数据的data字段是数组(包含了多条数据)
使用 Int64Counter
记录消费总次数时(同步且递增),在服务重新启动后,测量值会重新从0开始
traefik 反向代理
http:
routers:
frontend-router:
rule: "PathPrefix(`/frontend`)"
entrypoints:
- web-entrypoint
service: frontend-service
services:
frontend-service:
loadBalancer:
servers:
- url: http://profile-frontend:3301