github.com/richardwilkes/toolbox@v1.121.0/errs/log.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package errs
    11  
    12  import (
    13  	"context"
    14  	"log/slog"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  // StackTraceKey is the key used for logging the stack trace.
    20  const StackTraceKey = "stack_trace"
    21  
    22  // Log an error with a stack trace.
    23  func Log(err error, args ...any) {
    24  	log(context.Background(), slog.LevelError, slog.Default(), WrapTyped(err), args...)
    25  }
    26  
    27  // LogContext logs an error with a stack trace.
    28  func LogContext(ctx context.Context, err error, args ...any) {
    29  	log(ctx, slog.LevelError, slog.Default(), WrapTyped(err), args...)
    30  }
    31  
    32  // LogTo logs an error with a stack trace.
    33  func LogTo(logger *slog.Logger, err error, args ...any) {
    34  	log(context.Background(), slog.LevelError, logger, WrapTyped(err), args...)
    35  }
    36  
    37  // LogContextTo logs an error with a stack trace.
    38  func LogContextTo(ctx context.Context, logger *slog.Logger, err error, args ...any) {
    39  	log(ctx, slog.LevelError, logger, WrapTyped(err), args...)
    40  }
    41  
    42  // LogWithLevel logs an error with a stack trace.
    43  func LogWithLevel(ctx context.Context, level slog.Level, logger *slog.Logger, err error, args ...any) {
    44  	log(ctx, level, logger, WrapTyped(err), args...)
    45  }
    46  
    47  func log(ctx context.Context, level slog.Level, logger *slog.Logger, err *Error, args ...any) {
    48  	if logger == nil {
    49  		logger = slog.Default()
    50  	}
    51  	if !logger.Enabled(ctx, level) {
    52  		return
    53  	}
    54  	r := createRecord(level, err)
    55  	r.Add(args...)
    56  	if ctx == nil {
    57  		ctx = context.Background()
    58  	}
    59  	_ = logger.Handler().Handle(ctx, r) //nolint:errcheck // Since we are in the logger, nothing we can reasonably do to log this
    60  }
    61  
    62  func createRecord(level slog.Level, err *Error) slog.Record {
    63  	var pc uintptr
    64  	var msg string
    65  	if err != nil {
    66  		msg = err.Message()
    67  		if len(err.stack) != 0 {
    68  			pc = err.stack[0]
    69  		}
    70  	}
    71  	r := slog.NewRecord(time.Now(), level, msg, pc)
    72  	if err != nil {
    73  		r.AddAttrs(slog.Any(StackTraceKey, &stackValue{err: err}))
    74  	}
    75  	return r
    76  }
    77  
    78  // LogAttrs logs an error with a stack trace.
    79  func LogAttrs(err error, attrs ...slog.Attr) {
    80  	logAttrs(context.Background(), slog.LevelError, slog.Default(), WrapTyped(err), attrs...)
    81  }
    82  
    83  // LogAttrsContext logs an error with a stack trace.
    84  func LogAttrsContext(ctx context.Context, err error, attrs ...slog.Attr) {
    85  	logAttrs(ctx, slog.LevelError, slog.Default(), WrapTyped(err), attrs...)
    86  }
    87  
    88  // LogAttrsTo logs an error with a stack trace.
    89  func LogAttrsTo(logger *slog.Logger, err error, attrs ...slog.Attr) {
    90  	logAttrs(context.Background(), slog.LevelError, logger, WrapTyped(err), attrs...)
    91  }
    92  
    93  // LogAttrsContextTo logs an error with a stack trace.
    94  func LogAttrsContextTo(ctx context.Context, logger *slog.Logger, err error, attrs ...slog.Attr) {
    95  	logAttrs(ctx, slog.LevelError, logger, WrapTyped(err), attrs...)
    96  }
    97  
    98  // LogAttrsWithLevel logs an error with a stack trace.
    99  func LogAttrsWithLevel(ctx context.Context, level slog.Level, logger *slog.Logger, err error, attrs ...slog.Attr) {
   100  	logAttrs(ctx, level, logger, WrapTyped(err), attrs...)
   101  }
   102  
   103  func logAttrs(ctx context.Context, level slog.Level, logger *slog.Logger, err *Error, attrs ...slog.Attr) {
   104  	if logger == nil {
   105  		logger = slog.Default()
   106  	}
   107  	if !logger.Enabled(ctx, level) {
   108  		return
   109  	}
   110  	r := createRecord(level, err)
   111  	r.AddAttrs(attrs...)
   112  	if ctx == nil {
   113  		ctx = context.Background()
   114  	}
   115  	_ = logger.Handler().Handle(ctx, r) //nolint:errcheck // Since we are in the logger, nothing we can reasonably do to log this
   116  }
   117  
   118  type stackValue struct {
   119  	err StackError
   120  }
   121  
   122  func (v *stackValue) StackError() StackError {
   123  	return v.err
   124  }
   125  
   126  func (v *stackValue) LogValue() slog.Value {
   127  	stack := strings.Split(v.err.StackTrace(true), "\n")
   128  	for i := 0; i < len(stack); i++ {
   129  		stack[i] = strings.TrimSpace(stack[i])
   130  	}
   131  	return slog.AnyValue(stack)
   132  }