github.com/sujit-baniya/log@v1.0.73/journal.go (about)

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