github.com/phuslu/log@v1.0.100/journal.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package log
     5  
     6  import (
     7  	"encoding/binary"
     8  	"net"
     9  	"os"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  )
    14  
    15  // JournalWriter is an Writer that writes logs to journald.
    16  type JournalWriter struct {
    17  	// JournalSocket specifies socket name, using `/run/systemd/journal/socket` if empty.
    18  	JournalSocket string
    19  
    20  	once sync.Once
    21  	addr *net.UnixAddr
    22  	conn *net.UnixConn
    23  }
    24  
    25  // Close implements io.Closer.
    26  func (w *JournalWriter) Close() (err error) {
    27  	if w.conn != nil {
    28  		err = w.conn.Close()
    29  	}
    30  	return
    31  }
    32  
    33  // WriteEntry implements Writer.
    34  func (w *JournalWriter) WriteEntry(e *Entry) (n int, err error) {
    35  	w.once.Do(func() {
    36  		// unix addr
    37  		w.addr = &net.UnixAddr{
    38  			Net:  "unixgram",
    39  			Name: w.JournalSocket,
    40  		}
    41  		if w.addr.Name == "" {
    42  			w.addr.Name = "/run/systemd/journal/socket"
    43  		}
    44  		// unix conn
    45  		var autobind *net.UnixAddr
    46  		autobind, err = net.ResolveUnixAddr("unixgram", "")
    47  		if err != nil {
    48  			return
    49  		}
    50  		w.conn, err = net.ListenUnixgram("unixgram", autobind)
    51  	})
    52  
    53  	if err != nil {
    54  		return
    55  	}
    56  
    57  	b0 := bbpool.Get().(*bb)
    58  	b0.B = b0.B[:0]
    59  	defer bbpool.Put(b0)
    60  	b0.B = append(b0.B, e.buf...)
    61  
    62  	var args FormatterArgs
    63  	parseFormatterArgs(b0.B, &args)
    64  	if args.Time == "" {
    65  		return
    66  	}
    67  
    68  	// buffer
    69  	b := bbpool.Get().(*bb)
    70  	b.B = b.B[:0]
    71  	defer bbpool.Put(b)
    72  
    73  	print := func(upper bool, name, value string) {
    74  		if upper {
    75  			for _, c := range []byte(name) {
    76  				if 'a' <= c && c <= 'z' {
    77  					c -= 'a' - 'A'
    78  				}
    79  				b.B = append(b.B, c)
    80  			}
    81  		} else {
    82  			b.B = append(b.B, name...)
    83  		}
    84  		if strings.ContainsRune(value, '\n') {
    85  			b.B = append(b.B, '\n')
    86  			_ = binary.Write(b, binary.LittleEndian, uint64(len(value)))
    87  			b.B = append(b.B, value...)
    88  			b.B = append(b.B, '\n')
    89  		} else {
    90  			b.B = append(b.B, '=')
    91  			b.B = append(b.B, value...)
    92  			b.B = append(b.B, '\n')
    93  		}
    94  	}
    95  
    96  	// level
    97  	var priority string
    98  	switch e.Level {
    99  	case TraceLevel:
   100  		priority = "7" // Debug
   101  	case DebugLevel:
   102  		priority = "7" // Debug
   103  	case InfoLevel:
   104  		priority = "6" // Informational
   105  	case WarnLevel:
   106  		priority = "4" // Warning
   107  	case ErrorLevel:
   108  		priority = "3" // Error
   109  	case FatalLevel:
   110  		priority = "2" // Critical
   111  	case PanicLevel:
   112  		priority = "0" // Emergency
   113  	default:
   114  		priority = "5" // Notice
   115  	}
   116  	print(false, "PRIORITY", priority)
   117  
   118  	// message
   119  	print(false, "MESSAGE", args.Message)
   120  
   121  	// caller
   122  	if args.Caller != "" {
   123  		print(false, "CALLER", args.Caller)
   124  	}
   125  
   126  	// goid
   127  	if args.Goid != "" {
   128  		print(false, "GOID", args.Goid)
   129  	}
   130  
   131  	// stack
   132  	if args.Stack != "" {
   133  		print(false, "STACK", args.Stack)
   134  	}
   135  
   136  	// fields
   137  	for _, kv := range args.KeyValues {
   138  		print(true, kv.Key, kv.Value)
   139  	}
   140  
   141  	print(false, "JSON", b2s(e.buf))
   142  
   143  	// write
   144  	n, _, err = w.conn.WriteMsgUnix(b.B, nil, w.addr)
   145  	if err == nil {
   146  		return
   147  	}
   148  
   149  	opErr, ok := err.(*net.OpError)
   150  	if !ok || opErr == nil {
   151  		return
   152  	}
   153  
   154  	sysErr, ok := opErr.Err.(*os.SyscallError)
   155  	if !ok || sysErr == nil {
   156  		return
   157  	}
   158  	if sysErr.Err != syscall.EMSGSIZE && sysErr.Err != syscall.ENOBUFS {
   159  		return
   160  	}
   161  
   162  	// Large log entry, send it via tempfile and ancillary-fd.
   163  	var file *os.File
   164  	file, err = os.CreateTemp("/dev/shm/", "journal.XXXXX")
   165  	if err != nil {
   166  		return
   167  	}
   168  	err = syscall.Unlink(file.Name())
   169  	if err != nil {
   170  		return
   171  	}
   172  	defer file.Close()
   173  	n, err = file.Write(b.B)
   174  	if err != nil {
   175  		return
   176  	}
   177  	rights := syscall.UnixRights(int(file.Fd()))
   178  	_, _, err = w.conn.WriteMsgUnix([]byte{}, rights, w.addr)
   179  	if err == nil {
   180  		n = len(e.buf)
   181  	}
   182  
   183  	return
   184  }
   185  
   186  var _ Writer = (*JournalWriter)(nil)