mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
feat(otel): add more tracing when communicating with internal tools / external APIs
This commit is contained in:
@@ -15,6 +15,10 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
@@ -96,25 +100,44 @@ func (engine *PdfCpu) Debug() map[string]any {
|
||||
|
||||
// Merge combines multiple PDFs into a single PDF.
|
||||
func (engine *PdfCpu) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Merge",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
args := make([]string, 0, 2+len(inputPaths))
|
||||
args = append(args, "merge", outputPath)
|
||||
args = append(args, inputPaths...)
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err == nil {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Split splits a given PDF file.
|
||||
func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Split",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
var args []string
|
||||
|
||||
switch mode.Mode {
|
||||
@@ -128,17 +151,26 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode goten
|
||||
}
|
||||
args = append(args, "extract", "-mode", "page", "-pages", mode.Span, inputPath, outputDirPath)
|
||||
default:
|
||||
return nil, fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||
err := fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("split PDFs with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("split PDFs with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var outputPaths []string
|
||||
@@ -155,46 +187,104 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode goten
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(digitSuffixSort(outputPaths))
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return outputPaths, nil
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *PdfCpu) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
||||
return fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Flatten",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert is not available in this implementation.
|
||||
func (engine *PdfCpu) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Convert",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadMetadata is not available in this implementation.
|
||||
func (engine *PdfCpu) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
||||
return nil, fmt.Errorf("read PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.ReadMetadata",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := fmt.Errorf("read PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// WriteMetadata is not available in this implementation.
|
||||
func (engine *PdfCpu) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
||||
return fmt.Errorf("write PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.WriteMetadata",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := fmt.Errorf("write PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// PageCount is not available in this implementation.
|
||||
func (engine *PdfCpu) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
||||
return 0, fmt.Errorf("page count with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.PageCount",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := fmt.Errorf("page count with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// ReadBookmarks reads the document outline (bookmarks) of a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.ReadBookmarks",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
tmpPath := fmt.Sprintf("%s.read.json", inputPath)
|
||||
args := []string{"bookmarks", "export", inputPath, tmpPath}
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -213,41 +303,55 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, in
|
||||
// If the file wasn't created, or it was created but is 0 bytes,
|
||||
// it means pdfcpu had no bookmarks to write.
|
||||
if os.IsNotExist(statErr) || (statErr == nil && info.Size() == 0) {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return make([]gotenberg.Bookmark, 0), nil
|
||||
}
|
||||
|
||||
// Fallback: Check the error string just in case pdfcpu failed without
|
||||
// touching the file.
|
||||
if strings.Contains(strings.ToLower(cmdErr.Error()), "no bookmarks") {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return make([]gotenberg.Bookmark, 0), nil
|
||||
}
|
||||
return nil, fmt.Errorf("read bookmarks with pdfcpu: %w", cmdErr)
|
||||
err = fmt.Errorf("read bookmarks with pdfcpu: %w", cmdErr)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If cmd succeeded, but output a 0-byte file anyway.
|
||||
if info != nil && info.Size() == 0 {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return make([]gotenberg.Bookmark, 0), nil
|
||||
}
|
||||
|
||||
// Read the file content.
|
||||
jsonBytes, err := os.ReadFile(tmpPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read temporary bookmarks JSON file: %w", err)
|
||||
err = fmt.Errorf("read temporary bookmarks JSON file: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the content is just empty whitespace.
|
||||
if len(bytes.TrimSpace(jsonBytes)) == 0 {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return make([]gotenberg.Bookmark, 0), nil
|
||||
}
|
||||
|
||||
var data pdfcpuBookmarks
|
||||
err = json.Unmarshal(jsonBytes, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal bookmarks: %w", err)
|
||||
err = fmt.Errorf("unmarshal bookmarks: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Safety check: Does the parsed JSON actually contain bookmarks?
|
||||
if len(data.Bookmarks) == 0 {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return make([]gotenberg.Bookmark, 0), nil
|
||||
}
|
||||
|
||||
@@ -264,12 +368,20 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, in
|
||||
return res
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return mapBookmarks(data.Bookmarks), nil
|
||||
}
|
||||
|
||||
// WriteBookmarks adds a document outline (bookmarks) to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.WriteBookmarks",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
if len(bookmarks) == 0 {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -292,13 +404,19 @@ func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, i
|
||||
|
||||
jsonBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal bookmarks: %w", err)
|
||||
err = fmt.Errorf("marshal bookmarks: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
tmpPath := fmt.Sprintf("%s.json", inputPath)
|
||||
err = os.WriteFile(tmpPath, jsonBytes, 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write temporary bookmarks JSON file: %w", err)
|
||||
err = fmt.Errorf("write temporary bookmarks JSON file: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -311,21 +429,35 @@ func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, i
|
||||
args := []string{"bookmarks", "import", "-replace", inputPath, tmpPath, inputPath}
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("write bookmarks with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("write bookmarks with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmbedFiles embeds files into a PDF. All files are embedded as file attachments
|
||||
// without modifying the main PDF content.
|
||||
func (engine *PdfCpu) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.EmbedFiles",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
if len(filePaths) == 0 {
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -337,21 +469,37 @@ func (engine *PdfCpu) EmbedFiles(ctx context.Context, logger *slog.Logger, fileP
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command for attaching files: %w", err)
|
||||
err = fmt.Errorf("create command for attaching files: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("attach files with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("attach files with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt adds password protection to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Encrypt",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
if userPassword == "" {
|
||||
return errors.New("user password cannot be empty")
|
||||
err := errors.New("user password cannot be empty")
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if ownerPassword == "" {
|
||||
@@ -368,29 +516,70 @@ func (engine *PdfCpu) Encrypt(ctx context.Context, logger *slog.Logger, inputPat
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt PDF with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("encrypt PDF with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watermark applies a watermark (behind page content) to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return engine.applyStampOrWatermark(ctx, logger, "watermark", inputPath, stamp)
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Watermark",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := engine.applyStampOrWatermark(ctx, logger, "watermark", inputPath, stamp)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stamp applies a stamp (on top of page content) to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return engine.applyStampOrWatermark(ctx, logger, "stamp", inputPath, stamp)
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Stamp",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := engine.applyStampOrWatermark(ctx, logger, "stamp", inputPath, stamp)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rotate rotates pages of a PDF file by the given angle using pdfcpu.
|
||||
func (engine *PdfCpu) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Rotate",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
args := []string{"rotate"}
|
||||
if pages != "" {
|
||||
args = append(args, "-pages", pages)
|
||||
@@ -399,14 +588,21 @@ func (engine *PdfCpu) Rotate(ctx context.Context, logger *slog.Logger, inputPath
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
err = fmt.Errorf("create command: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("rotate PDF with pdfcpu: %w", err)
|
||||
err = fmt.Errorf("rotate PDF with pdfcpu: %w", err)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user