Files
cms-server/internal/exception/builder.go
Asai Neko 2c312a545a
All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished
Adapt error code definitions for exception builder refactor
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-03-22 00:13:34 +08:00

146 lines
2.8 KiB
Go

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
}