github.com/oarkflow/log@v1.0.78/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)