github.com/goravel/framework@v1.13.9/log/logrus_writer.go (about)

     1  package log
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/rotisserie/eris"
     9  	"github.com/sirupsen/logrus"
    10  
    11  	"github.com/goravel/framework/contracts/config"
    12  	"github.com/goravel/framework/contracts/http"
    13  	"github.com/goravel/framework/contracts/log"
    14  	"github.com/goravel/framework/log/formatter"
    15  	"github.com/goravel/framework/log/logger"
    16  )
    17  
    18  type Writer struct {
    19  	code string
    20  
    21  	// context
    22  	context map[string]any
    23  	domain  string
    24  
    25  	hint     string
    26  	instance *logrus.Entry
    27  	message  string
    28  	owner    any
    29  
    30  	// http
    31  	request  http.ContextRequest
    32  	response http.ContextResponse
    33  
    34  	// stacktrace
    35  	stackEnabled bool
    36  	stacktrace   map[string]any
    37  
    38  	tags  []string
    39  	trace string
    40  
    41  	// user
    42  	user any
    43  }
    44  
    45  func NewWriter(instance *logrus.Entry) log.Writer {
    46  	return &Writer{
    47  		code: "",
    48  
    49  		// context
    50  		context: map[string]any{},
    51  		domain:  "",
    52  
    53  		hint:     "",
    54  		instance: instance,
    55  		message:  "",
    56  		owner:    nil,
    57  
    58  		// http
    59  		request:  nil,
    60  		response: nil,
    61  
    62  		// stacktrace
    63  		stackEnabled: false,
    64  		stacktrace:   nil,
    65  
    66  		tags:  []string{},
    67  		trace: "",
    68  
    69  		// user
    70  		user: nil,
    71  	}
    72  }
    73  
    74  func (r *Writer) Debug(args ...any) {
    75  	r.instance.WithField("root", r.toMap()).Debug(args...)
    76  }
    77  
    78  func (r *Writer) Debugf(format string, args ...any) {
    79  	r.instance.WithField("root", r.toMap()).Debugf(format, args...)
    80  }
    81  
    82  func (r *Writer) Info(args ...any) {
    83  	r.instance.WithField("root", r.toMap()).Info(args...)
    84  }
    85  
    86  func (r *Writer) Infof(format string, args ...any) {
    87  	r.instance.WithField("root", r.toMap()).Infof(format, args...)
    88  }
    89  
    90  func (r *Writer) Warning(args ...any) {
    91  	r.instance.WithField("root", r.toMap()).Warning(args...)
    92  }
    93  
    94  func (r *Writer) Warningf(format string, args ...any) {
    95  	r.instance.WithField("root", r.toMap()).Warningf(format, args...)
    96  }
    97  
    98  func (r *Writer) Error(args ...any) {
    99  	r.withStackTrace(fmt.Sprint(args...))
   100  	r.instance.WithField("root", r.toMap()).Error(args...)
   101  }
   102  
   103  func (r *Writer) Errorf(format string, args ...any) {
   104  	r.withStackTrace(fmt.Sprintf(format, args...))
   105  	r.instance.WithField("root", r.toMap()).Errorf(format, args...)
   106  }
   107  
   108  func (r *Writer) Fatal(args ...any) {
   109  	r.withStackTrace(fmt.Sprint(args...))
   110  	r.instance.WithField("root", r.toMap()).Fatal(args...)
   111  }
   112  
   113  func (r *Writer) Fatalf(format string, args ...any) {
   114  	r.withStackTrace(fmt.Sprintf(format, args...))
   115  	r.instance.WithField("root", r.toMap()).Fatalf(format, args...)
   116  }
   117  
   118  func (r *Writer) Panic(args ...any) {
   119  	r.withStackTrace(fmt.Sprint(args...))
   120  	r.instance.WithField("root", r.toMap()).Panic(args...)
   121  }
   122  
   123  func (r *Writer) Panicf(format string, args ...any) {
   124  	r.withStackTrace(fmt.Sprintf(format, args...))
   125  	r.instance.WithField("root", r.toMap()).Panicf(format, args...)
   126  }
   127  
   128  // Code set a code or slug that describes the error.
   129  // Error messages are intended to be read by humans, but such code is expected to
   130  // be read by machines and even transported over different services.
   131  func (r *Writer) Code(code string) log.Writer {
   132  	r.code = code
   133  	return r
   134  }
   135  
   136  // Hint set a hint for faster debugging.
   137  func (r *Writer) Hint(hint string) log.Writer {
   138  	r.hint = hint
   139  
   140  	return r
   141  }
   142  
   143  // In sets the feature category or domain in which the log entry is relevant.
   144  func (r *Writer) In(domain string) log.Writer {
   145  	r.domain = domain
   146  
   147  	return r
   148  }
   149  
   150  // Owner set the name/email of the colleague/team responsible for handling this error.
   151  // Useful for alerting purpose.
   152  func (r *Writer) Owner(owner any) log.Writer {
   153  	r.owner = owner
   154  
   155  	return r
   156  }
   157  
   158  // Request supplies a http.Request.
   159  func (r *Writer) Request(req http.ContextRequest) log.Writer {
   160  	r.request = req
   161  
   162  	return r
   163  }
   164  
   165  // Response supplies a http.Response.
   166  func (r *Writer) Response(res http.ContextResponse) log.Writer {
   167  	r.response = res
   168  
   169  	return r
   170  }
   171  
   172  // Tags add multiple tags, describing the feature returning an error.
   173  func (r *Writer) Tags(tags ...string) log.Writer {
   174  	r.tags = append(r.tags, tags...)
   175  
   176  	return r
   177  }
   178  
   179  // User sets the user associated with the log entry.
   180  func (r *Writer) User(user any) log.Writer {
   181  	r.user = user
   182  	return r
   183  }
   184  
   185  // With adds key-value pairs to the context of the log entry
   186  func (r *Writer) With(data map[string]any) log.Writer {
   187  	for k, v := range data {
   188  		r.context[k] = v
   189  	}
   190  
   191  	return r
   192  }
   193  
   194  func (r *Writer) withStackTrace(message string) {
   195  	erisNew := eris.New(message)
   196  	r.message = erisNew.Error()
   197  	format := eris.NewDefaultJSONFormat(eris.FormatOptions{
   198  		InvertOutput: true,
   199  		WithTrace:    true,
   200  		InvertTrace:  true,
   201  	})
   202  	r.stacktrace = eris.ToCustomJSON(erisNew, format)
   203  	r.stackEnabled = true
   204  }
   205  
   206  // ToMap returns a map representation of the error.
   207  func (r *Writer) toMap() map[string]any {
   208  	payload := map[string]any{}
   209  
   210  	if message := r.message; message != "" {
   211  		payload["message"] = message
   212  	}
   213  
   214  	if code := r.code; code != "" {
   215  		payload["code"] = code
   216  	}
   217  
   218  	if domain := r.domain; domain != "" {
   219  		payload["domain"] = domain
   220  	}
   221  
   222  	if tags := r.tags; len(tags) > 0 {
   223  		payload["tags"] = tags
   224  	}
   225  
   226  	if context := r.context; len(context) > 0 {
   227  		payload["context"] = context
   228  	}
   229  
   230  	if trace := r.trace; trace != "" {
   231  		payload["trace"] = trace
   232  	}
   233  
   234  	if hint := r.hint; hint != "" {
   235  		payload["hint"] = hint
   236  	}
   237  
   238  	if owner := r.owner; owner != nil {
   239  		payload["owner"] = owner
   240  	}
   241  
   242  	if r.user != nil {
   243  		payload["user"] = r.user
   244  	}
   245  
   246  	if req := r.request; req != nil {
   247  		payload["request"] = map[string]any{
   248  			"method": req.Method(),
   249  			"uri":    req.FullUrl(),
   250  			"header": req.Headers(),
   251  			"body":   req.All(),
   252  		}
   253  	}
   254  
   255  	if res := r.response; res != nil {
   256  		payload["response"] = map[string]any{
   257  			"status": res.Origin().Status(),
   258  			"header": res.Origin().Header(),
   259  			"body":   res.Origin().Body(),
   260  			"size":   res.Origin().Size(),
   261  		}
   262  	}
   263  
   264  	if stacktrace := r.stacktrace; stacktrace != nil || r.stackEnabled {
   265  		payload["stacktrace"] = stacktrace
   266  	}
   267  
   268  	return payload
   269  }
   270  
   271  func registerHook(config config.Config, instance *logrus.Logger, channel string) error {
   272  	channelPath := "logging.channels." + channel
   273  	driver := config.GetString(channelPath + ".driver")
   274  
   275  	var hook logrus.Hook
   276  	var err error
   277  	switch driver {
   278  	case log.StackDriver:
   279  		for _, stackChannel := range config.Get(channelPath + ".channels").([]string) {
   280  			if stackChannel == channel {
   281  				return errors.New("stack drive can't include self channel")
   282  			}
   283  
   284  			if err := registerHook(config, instance, stackChannel); err != nil {
   285  				return err
   286  			}
   287  		}
   288  
   289  		return nil
   290  	case log.SingleDriver:
   291  		if !config.GetBool(channelPath + ".print") {
   292  			instance.SetOutput(io.Discard)
   293  		}
   294  
   295  		logLogger := logger.NewSingle(config)
   296  		hook, err = logLogger.Handle(channelPath)
   297  		if err != nil {
   298  			return err
   299  		}
   300  	case log.DailyDriver:
   301  		if !config.GetBool(channelPath + ".print") {
   302  			instance.SetOutput(io.Discard)
   303  		}
   304  
   305  		logLogger := logger.NewDaily(config)
   306  		hook, err = logLogger.Handle(channelPath)
   307  		if err != nil {
   308  			return err
   309  		}
   310  	case log.CustomDriver:
   311  		logLogger := config.Get(channelPath + ".via").(log.Logger)
   312  		logHook, err := logLogger.Handle(channelPath)
   313  		if err != nil {
   314  			return err
   315  		}
   316  
   317  		hook = &Hook{logHook}
   318  	default:
   319  		return errors.New("Error logging channel: " + channel)
   320  	}
   321  
   322  	instance.SetFormatter(formatter.NewGeneral(config))
   323  
   324  	instance.AddHook(hook)
   325  
   326  	return nil
   327  }
   328  
   329  type Hook struct {
   330  	instance log.Hook
   331  }
   332  
   333  func (h *Hook) Levels() []logrus.Level {
   334  	levels := h.instance.Levels()
   335  	var logrusLevels []logrus.Level
   336  	for _, item := range levels {
   337  		logrusLevels = append(logrusLevels, logrus.Level(item))
   338  	}
   339  
   340  	return logrusLevels
   341  }
   342  
   343  func (h *Hook) Fire(entry *logrus.Entry) error {
   344  	return h.instance.Fire(&Entry{
   345  		ctx:     entry.Context,
   346  		level:   log.Level(entry.Level),
   347  		time:    entry.Time,
   348  		message: entry.Message,
   349  	})
   350  }