github.com/coreos/mantle@v0.13.0/network/journal/format.go (about) 1 // Copyright 2017 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package journal 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "time" 22 "unicode" 23 "unicode/utf8" 24 ) 25 26 type Formatter interface { 27 SetTimezone(tz *time.Location) 28 WriteEntry(entry Entry) error 29 } 30 31 type shortWriter struct { 32 w io.Writer 33 tz *time.Location 34 bootid string 35 } 36 37 // ShortWriter writes journal entries in a format similar to journalctl's 38 // "short-precise" format, excluding hostname for conciseness. 39 func ShortWriter(w io.Writer) Formatter { 40 return &shortWriter{ 41 w: w, 42 tz: time.Local, 43 } 44 } 45 46 // SetTimezone updates the time location. The default is local time. 47 func (s *shortWriter) SetTimezone(tz *time.Location) { 48 s.tz = tz 49 } 50 51 func (s *shortWriter) WriteEntry(entry Entry) error { 52 realtime := entry.Realtime() 53 message, ok := entry[FIELD_MESSAGE] 54 if realtime.IsZero() || !ok { 55 // Simply skip entries that are woefully incomplete. 56 return nil 57 } 58 59 if s.isReboot(entry) { 60 io.WriteString(s.w, "-- Reboot --\n") 61 } 62 63 var buf bytes.Buffer 64 buf.WriteString(realtime.In(s.tz).Format(time.StampMicro)) 65 66 if identifier, ok := entry[FIELD_SYSLOG_IDENTIFIER]; ok { 67 buf.WriteByte(' ') 68 buf.Write(identifier) 69 } else { 70 buf.WriteString(" unknown") 71 } 72 73 if pid, ok := entry[FIELD_PID]; ok { 74 buf.WriteByte('[') 75 buf.Write(pid) 76 buf.WriteByte(']') 77 } else if pid, ok := entry[FIELD_SYSLOG_PID]; ok { 78 buf.WriteByte('[') 79 buf.Write(pid) 80 buf.WriteByte(']') 81 } 82 83 buf.WriteString(": ") 84 indent := buf.Len() 85 lines := bytes.Split(message, []byte{'\n'}) 86 writeEscaped(&buf, lines[0]) 87 for _, line := range lines[1:] { 88 buf.WriteByte('\n') 89 buf.Write(bytes.Repeat([]byte{' '}, indent)) 90 writeEscaped(&buf, line) 91 } 92 93 buf.WriteByte('\n') 94 95 _, err := buf.WriteTo(s.w) 96 return err 97 } 98 99 func (s *shortWriter) isReboot(entry Entry) bool { 100 bootid, ok := entry[FIELD_BOOT_ID] 101 if !ok || len(bootid) == 0 { 102 return false 103 } 104 105 newid := string(bootid) 106 if s.bootid == "" { 107 s.bootid = newid 108 return false 109 } else if s.bootid != newid { 110 s.bootid = newid 111 return true 112 } 113 return false 114 } 115 116 func writeEscaped(buf *bytes.Buffer, line []byte) { 117 const tab = " " // 8 spaces 118 for len(line) > 0 { 119 r, n := utf8.DecodeRune(line) 120 switch r { 121 case utf8.RuneError: 122 fmt.Fprintf(buf, "\\x%02x", line[0]) 123 case '\t': 124 buf.WriteString(tab) 125 default: 126 if unicode.IsPrint(r) { 127 buf.Write(line[:n]) 128 } else { 129 fmt.Fprintf(buf, "\\u%04x", r) 130 } 131 } 132 line = line[n:] 133 } 134 }