diff --git a/pkg/log/BUILD b/pkg/log/BUILD index bf85b4494..94ac66db3 100644 --- a/pkg/log/BUILD +++ b/pkg/log/BUILD @@ -8,6 +8,7 @@ go_library( "glog.go", "glog_unsafe.go", "json.go", + "json_k8s.go", "log.go", ], importpath = "gvisor.googlesource.com/gvisor/pkg/log", diff --git a/pkg/log/json_k8s.go b/pkg/log/json_k8s.go new file mode 100644 index 000000000..9c2f8d2b7 --- /dev/null +++ b/pkg/log/json_k8s.go @@ -0,0 +1,47 @@ +// Copyright 2018 Google LLC +// +// 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 ( + "encoding/json" + "fmt" + "time" +) + +type k8sJSONLog struct { + Log string `json:"log"` + Level Level `json:"level"` + Time time.Time `json:"time"` +} + +// K8sJSONEmitter logs messages in json format that is compatible with +// Kubernetes fluent configuration. +type K8sJSONEmitter struct { + Writer +} + +// Emit implements Emitter.Emit. +func (e K8sJSONEmitter) Emit(level Level, timestamp time.Time, format string, v ...interface{}) { + j := k8sJSONLog{ + Log: fmt.Sprintf(format, v...), + Level: level, + Time: timestamp, + } + b, err := json.Marshal(j) + if err != nil { + panic(err) + } + e.Writer.Write(b) +} diff --git a/runsc/boot/compat.go b/runsc/boot/compat.go index d18c2f802..4c49e90e3 100644 --- a/runsc/boot/compat.go +++ b/runsc/boot/compat.go @@ -66,7 +66,7 @@ func newCompatEmitter(logFD int) (*compatEmitter, error) { if logFD > 0 { f := os.NewFile(uintptr(logFD), "user log file") - target := log.MultiEmitter{c.sink, log.GoogleEmitter{&log.Writer{Next: f}}} + target := log.MultiEmitter{c.sink, log.K8sJSONEmitter{log.Writer{Next: f}}} c.sink = &log.BasicLogger{Level: log.Info, Emitter: target} } return c, nil diff --git a/runsc/boot/config.go b/runsc/boot/config.go index 9ebbde424..2d89ad87e 100644 --- a/runsc/boot/config.go +++ b/runsc/boot/config.go @@ -157,12 +157,15 @@ type Config struct { // LogFilename is the filename to log to, if not empty. LogFilename string - // LogFormat is the log format, "text" or "json". + // LogFormat is the log format. LogFormat string // DebugLog is the path to log debug information to, if not empty. DebugLog string + // DebugLogFormat is the log format for debug. + DebugLogFormat string + // FileAccess indicates how the filesystem is accessed. FileAccess FileAccessType @@ -214,6 +217,7 @@ func (c *Config) ToFlags() []string { "--log=" + c.LogFilename, "--log-format=" + c.LogFormat, "--debug-log=" + c.DebugLog, + "--debug-log-format=" + c.DebugLogFormat, "--file-access=" + c.FileAccess.String(), "--overlay=" + strconv.FormatBool(c.Overlay), "--network=" + c.Network.String(), diff --git a/runsc/main.go b/runsc/main.go index 4a92db7c0..c0ee04216 100644 --- a/runsc/main.go +++ b/runsc/main.go @@ -38,17 +38,18 @@ var ( // Docker, and thus should not be changed. rootDir = flag.String("root", "", "root directory for storage of container state") logFilename = flag.String("log", "", "file path where internal debug information is written, default is stdout") - logFormat = flag.String("log-format", "text", "log format: text (default) or json") + logFormat = flag.String("log-format", "text", "log format: text (default), json, or json-k8s") debug = flag.Bool("debug", false, "enable debug logging") // These flags are unique to runsc, and are used to configure parts of the // system that are not covered by the runtime spec. // Debugging flags. - debugLog = flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.") - logPackets = flag.Bool("log-packets", false, "enable network packet logging") - logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.") - debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.") + debugLog = flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.") + logPackets = flag.Bool("log-packets", false, "enable network packet logging") + logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.") + debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.") + debugLogFormat = flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s") // Debugging flags: strace related strace = flag.Bool("strace", false, "enable strace") @@ -133,6 +134,7 @@ func main() { LogFilename: *logFilename, LogFormat: *logFormat, DebugLog: *debugLog, + DebugLogFormat: *debugLogFormat, FileAccess: fsAccess, Overlay: *overlay, Network: netType, @@ -166,15 +168,7 @@ func main() { logFile = f } - var e log.Emitter - switch *logFormat { - case "text": - e = log.GoogleEmitter{&log.Writer{Next: logFile}} - case "json": - e = log.JSONEmitter{log.Writer{Next: logFile}} - default: - cmd.Fatalf("invalid log format %q, must be 'json' or 'text'", *logFormat) - } + e := newEmitter(*logFormat, logFile) subcommand := flag.CommandLine.Arg(0) if *debugLogFD > -1 { @@ -195,13 +189,13 @@ func main() { cmd.Fatalf("error dup'ing fd %d to stderr: %v", f.Fd(), err) } - e = log.MultiEmitter{e, log.GoogleEmitter{&log.Writer{Next: f}}} + e = log.MultiEmitter{e, newEmitter(*debugLogFormat, f)} } else if *debugLog != "" { f, err := specutils.DebugLogFile(*debugLog, subcommand) if err != nil { cmd.Fatalf("error opening debug log file in %q: %v", *debugLog, err) } - e = log.MultiEmitter{e, log.GoogleEmitter{&log.Writer{Next: f}}} + e = log.MultiEmitter{e, newEmitter(*debugLogFormat, f)} } log.SetTarget(e) @@ -236,6 +230,19 @@ func main() { os.Exit(128) } +func newEmitter(format string, logFile io.Writer) log.Emitter { + switch format { + case "text": + return &log.GoogleEmitter{&log.Writer{Next: logFile}} + case "json": + return &log.JSONEmitter{log.Writer{Next: logFile}} + case "json-k8s": + return &log.K8sJSONEmitter{log.Writer{Next: logFile}} + } + cmd.Fatalf("invalid log format %q, must be 'text', 'json', or 'json-k8s'", format) + panic("unreachable") +} + func init() { // Set default root dir to something (hopefully) user-writeable. *rootDir = "/var/run/runsc"