package exception import ( "context" "fmt" "nixcn-cms/tracer" "runtime" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) // context passthrough type ctxKey string const ( ctxKeyService ctxKey = "exception_service" ctxKeyEndpoint ctxKey = "exception_endpoint" ) func ContextWithService(ctx context.Context, service string) context.Context { return context.WithValue(ctx, ctxKeyService, service) } func ContextWithEndpoint(ctx context.Context, endpoint string) context.Context { return context.WithValue(ctx, ctxKeyEndpoint, endpoint) } func extractFromCtx(ctx context.Context, key ctxKey, fallback string) string { if val, ok := ctx.Value(key).(string); ok && val != "" { return val } return fallback } // 13 chars len // :1=status // :3=endpoint // :3=service // :1=common/specific // :5=original type Builder struct { Status string Endpoint string Service string Type string Original string Error error Span trace.Span ErrorCode string } // functional option type Option func(*Builder) func New(opts ...Option) *Builder { b := &Builder{} for _, opt := range opts { opt(b) } return b } func WithStatus(s string) Option { return func(b *Builder) { b.Status = s } } func WithType(s string) Option { return func(b *Builder) { b.Type = s } } func WithOriginal(s string) Option { return func(b *Builder) { b.Original = s } } func WithError(e error) Option { return func(b *Builder) { b.Error = e } } func WithSpan(s trace.Span) Option { return func(b *Builder) { b.Span = s } } // exception builder func (self *Builder) build() { self.ErrorCode = fmt.Sprintf("%s%s%s%s%s", self.Status, self.Endpoint, self.Service, self.Type, self.Original, ) } func (self *Builder) Throw(ctx context.Context) *Builder { self.Service = extractFromCtx(ctx, ctxKeyService, "svc") self.Endpoint = extractFromCtx(ctx, ctxKeyEndpoint, "ep") self.build() pc, file, line, _ := runtime.Caller(1) function := runtime.FuncForPC(pc) if self.Span == nil { self.Span = trace.SpanFromContext(ctx) } if self.Original != CommonSuccess { if self.Span != nil && self.Span.IsRecording() { self.Span.SetAttributes( attribute.String("code.function", function.Name()), attribute.String("code.file", tracer.ShortPath(file)), attribute.Int("code.line", line), attribute.String("exception.code", self.ErrorCode), ) if self.Error != nil { self.Span.RecordError(self.Error) self.Span.SetStatus(codes.Error, self.Error.Error()) } else { self.Span.SetStatus(codes.Error, fmt.Sprintf("ServiceException: %s", self.ErrorCode)) } } ErrorHandler(ctx, self.Status, self.ErrorCode, self.Error) } return self } func (self *Builder) String() string { self.build() return self.ErrorCode }