| // Copyright 2018 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package log |
| |
| import ( |
| "os" |
| "time" |
| ) |
| |
| // GoogleEmitter is a wrapper that emits logs in a format compatible with |
| // package github.com/golang/glog. |
| type GoogleEmitter struct { |
| // Emitter is the underlying emitter. |
| Emitter |
| } |
| |
| // buffer is a simple inline buffer to avoid churn. The data slice is generally |
| // kept to the local byte array, and we avoid having to allocate it on the heap. |
| type buffer struct { |
| local [256]byte |
| data []byte |
| } |
| |
| func (b *buffer) start() { |
| b.data = b.local[:0] |
| } |
| |
| func (b *buffer) String() string { |
| return unsafeString(b.data) |
| } |
| |
| func (b *buffer) write(c byte) { |
| b.data = append(b.data, c) |
| } |
| |
| func (b *buffer) writeAll(d []byte) { |
| b.data = append(b.data, d...) |
| } |
| |
| func (b *buffer) writeOneDigit(d byte) { |
| b.write('0' + d) |
| } |
| |
| func (b *buffer) writeTwoDigits(v int) { |
| v = v % 100 |
| b.writeOneDigit(byte(v / 10)) |
| b.writeOneDigit(byte(v % 10)) |
| } |
| |
| func (b *buffer) writeSixDigits(v int) { |
| v = v % 1000000 |
| b.writeOneDigit(byte(v / 100000)) |
| b.writeOneDigit(byte((v % 100000) / 10000)) |
| b.writeOneDigit(byte((v % 10000) / 1000)) |
| b.writeOneDigit(byte((v % 1000) / 100)) |
| b.writeOneDigit(byte((v % 100) / 10)) |
| b.writeOneDigit(byte(v % 10)) |
| } |
| |
| func calculateBytes(v int, pad int) []byte { |
| var d []byte |
| r := 1 |
| |
| for n := 10; v >= r; n = n * 10 { |
| d = append(d, '0'+byte((v%n)/r)) |
| r = n |
| } |
| |
| for i := len(d); i < pad; i++ { |
| d = append(d, ' ') |
| } |
| |
| for i := 0; i < len(d)/2; i++ { |
| d[i], d[len(d)-(i+1)] = d[len(d)-(i+1)], d[i] |
| } |
| return d |
| } |
| |
| // pid is used for the threadid component of the header. |
| // |
| // The glog package logger uses 7 spaces of padding. See |
| // glob.loggingT.formatHeader. |
| var pid = calculateBytes(os.Getpid(), 7) |
| |
| // caller is faked out as the caller. See FIXME below. |
| var caller = []byte("x:0") |
| |
| // Emit emits the message, google-style. |
| func (g GoogleEmitter) Emit(level Level, timestamp time.Time, format string, args ...interface{}) { |
| var b buffer |
| b.start() |
| |
| // Log lines have this form: |
| // Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... |
| // |
| // where the fields are defined as follows: |
| // L A single character, representing the log level (eg 'I' for INFO) |
| // mm The month (zero padded; ie May is '05') |
| // dd The day (zero padded) |
| // hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds |
| // threadid The space-padded thread ID as returned by GetTID() |
| // file The file name |
| // line The line number |
| // msg The user-supplied message |
| |
| // Log level. |
| switch level { |
| case Debug: |
| b.write('D') |
| case Info: |
| b.write('I') |
| case Warning: |
| b.write('W') |
| } |
| |
| // Timestamp. |
| _, month, day := timestamp.Date() |
| hour, minute, second := timestamp.Clock() |
| b.writeTwoDigits(int(month)) |
| b.writeTwoDigits(int(day)) |
| b.write(' ') |
| b.writeTwoDigits(int(hour)) |
| b.write(':') |
| b.writeTwoDigits(int(minute)) |
| b.write(':') |
| b.writeTwoDigits(int(second)) |
| b.write('.') |
| b.writeSixDigits(int(timestamp.Nanosecond() / 1000)) |
| b.write(' ') |
| |
| // The pid. |
| b.writeAll(pid) |
| b.write(' ') |
| |
| // FIXME(b/73383460): The caller, fabricated. This really sucks, but it |
| // is unacceptable to put runtime.Callers() in the hot path. |
| b.writeAll(caller) |
| b.write(']') |
| b.write(' ') |
| |
| // User-provided format string, copied. |
| for i := 0; i < len(format); i++ { |
| b.write(format[i]) |
| } |
| |
| // End with a newline. |
| b.write('\n') |
| |
| // Pass to the underlying routine. |
| g.Emitter.Emit(level, timestamp, b.String(), args...) |
| } |