Add support for reading logs extra attrs

The jsonlog logger currently allows specifying envs and labels that
should be propagated to the log message, however there has been no way
to read that back.

This adds a new API option to enable inserting these attrs back to the
log reader.

With timestamps, this looks like so:
```
92016-04-08T15:28:09.835913720Z foo=bar,hello=world hello
```

The extra attrs are comma separated before the log message but after
timestamps.

Without timestaps it looks like so:
```
foo=bar,hello=world hello
```

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
Brian Goff 2016-04-08 12:15:08 -04:00
parent 973d6f0820
commit bd9d14a07b
12 changed files with 74 additions and 11 deletions

View File

@ -25,6 +25,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
details := cmd.Bool([]string{"-details"}, false, "Show extra details provided to logs")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
cmd.Require(flag.Exact, 1)
@ -48,6 +49,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
Timestamps: *times,
Follow: *follow,
Tail: *tail,
Details: *details,
}
responseBody, err := cli.client.ContainerLogs(context.Background(), name, options)
if err != nil {

View File

@ -99,6 +99,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
Tail: r.Form.Get("tail"),
ShowStdout: stdout,
ShowStderr: stderr,
Details: httputils.BoolValue(r, "details"),
},
OutStream: w,
}

View File

@ -27,6 +27,7 @@ func decodeLogLine(dec *json.Decoder, l *jsonlog.JSONLog) (*logger.Message, erro
Source: l.Stream,
Timestamp: l.Created,
Line: []byte(l.Log),
Attrs: l.Attrs,
}
return msg, nil
}

View File

@ -9,6 +9,8 @@ package logger
import (
"errors"
"sort"
"strings"
"time"
"github.com/docker/docker/pkg/jsonlog"
@ -29,6 +31,31 @@ type Message struct {
Line []byte
Source string
Timestamp time.Time
Attrs LogAttributes
}
// LogAttributes is used to hold the extra attributes available in the log message
// Primarily used for converting the map type to string and sorting.
type LogAttributes map[string]string
type byKey []string
func (s byKey) Len() int { return len(s) }
func (s byKey) Less(i, j int) bool {
keyI := strings.Split(s[i], "=")
keyJ := strings.Split(s[j], "=")
return keyI[0] < keyJ[0]
}
func (s byKey) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (a LogAttributes) String() string {
var ss byKey
for k, v := range a {
ss = append(ss, k+"="+v)
}
sort.Sort(ss)
return strings.Join(ss, ",")
}
// Logger is the interface for docker logging drivers.

View File

@ -90,6 +90,9 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
return nil
}
logLine := msg.Line
if config.Details {
logLine = append([]byte(msg.Attrs.String()+" "), logLine...)
}
if config.Timestamps {
logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...)
}

View File

@ -136,6 +136,7 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /auth` now returns an `IdentityToken` when supported by a registry.
* `POST /containers/create` with both `Hostname` and `Domainname` fields specified will result in the container's hostname being set to `Hostname`, rather than `Hostname.Domainname`.
* `GET /volumes` now supports more filters, new added filters are `name` and `driver`.
* `GET /containers/(id or name)/logs` now accepts a `details` query parameter to stream the extra attributes that were provided to the containers `LogOpts`, such as environment variables and labels, with the logs.
### v1.22 API changes

View File

@ -770,6 +770,7 @@ Get `stdout` and `stderr` logs from the container ``id``
Query Parameters:
- **details** - 1/True/true or 0/False/flase, Show extra details provided to logs. Default `false`.
- **follow** 1/True/true or 0/False/false, return stream. Default `false`.
- **stdout** 1/True/true or 0/False/false, show `stdout` log. Default `false`.
- **stderr** 1/True/true or 0/False/false, show `stderr` log. Default `false`.

View File

@ -14,6 +14,7 @@ parent = "smn_cli"
Fetch the logs of a container
--details Show extra details provided to logs
-f, --follow Follow log output
--help Print usage
--since="" Show logs since timestamp
@ -36,6 +37,10 @@ The `docker logs --timestamps` command will add an [RFC3339Nano timestamp](https
log entry. To ensure that the timestamps are aligned the
nano-second part of the timestamp will be padded with zero when necessary.
The `docker logs --details` command will add on extra attributes, such as
environment variables and labels, provided to `--log-opt` when creating the
container.
The `--since` option shows only the container logs generated after
a given date. You can specify the date as an RFC 3339 date, a UNIX
timestamp, or a Go duration string (e.g. `1m30s`, `3h`). Besides RFC3339 date

View File

@ -307,3 +307,16 @@ func (s *DockerSuite) TestLogsCLIContainerNotFound(c *check.C) {
message := fmt.Sprintf("Error: No such container: %s\n", name)
c.Assert(out, checker.Equals, message)
}
func (s *DockerSuite) TestLogsWithDetails(c *check.C) {
dockerCmd(c, "run", "--name=test", "--label", "foo=bar", "-e", "baz=qux", "--log-opt", "labels=foo", "--log-opt", "env=baz", "busybox", "echo", "hello")
out, _ := dockerCmd(c, "logs", "--details", "--timestamps", "test")
logFields := strings.Fields(strings.TrimSpace(out))
c.Assert(len(logFields), checker.Equals, 3, check.Commentf(out))
details := strings.Split(logFields[1], ",")
c.Assert(details, checker.HasLen, 2)
c.Assert(details[0], checker.Equals, "baz=qux")
c.Assert(details[1], checker.Equals, "foo=bar")
}

View File

@ -30,6 +30,9 @@ logging drivers.
**--help**
Print usage statement
**--details**=*true*|*false*
Show extra details provided to logs
**-f**, **--follow**=*true*|*false*
Follow log output. The default is *false*.
@ -55,6 +58,10 @@ epoch or Unix time), and the optional .nanoseconds field is a fraction of a
second no more than nine digits long. You can combine the `--since` option with
either or both of the `--follow` or `--tail` options.
The `docker logs --details` command will add on extra attributes, such as
environment variables and labels, provided to `--log-opt` when creating the
container.
# HISTORY
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
based on docker.com source material and internal work.

View File

@ -15,6 +15,8 @@ type JSONLog struct {
Stream string `json:"stream,omitempty"`
// Created is the created timestamp of log
Created time.Time `json:"time"`
// Attrs is the list of extra attributes provided by the user
Attrs map[string]string `json:"attrs,omitempty"`
}
// Format returns the log formatted according to format

View File

@ -6,18 +6,18 @@ import (
)
func TestJSONLogMarshalJSON(t *testing.T) {
logs := map[JSONLog]string{
JSONLog{Log: `"A log line with \\"`}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":\".{20,}\"}$`,
JSONLog{Log: "A log line"}: `^{\"log\":\"A log line\",\"time\":\".{20,}\"}$`,
JSONLog{Log: "A log line with \r"}: `^{\"log\":\"A log line with \\r\",\"time\":\".{20,}\"}$`,
JSONLog{Log: "A log line with & < >"}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":\".{20,}\"}$`,
JSONLog{Log: "A log line with utf8 : 🚀 ψ ω β"}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":\".{20,}\"}$`,
JSONLog{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":\".{20,}\"}$`,
JSONLog{}: `^{\"time\":\".{20,}\"}$`,
logs := map[*JSONLog]string{
&JSONLog{Log: `"A log line with \\"`}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: "A log line"}: `^{\"log\":\"A log line\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: "A log line with \r"}: `^{\"log\":\"A log line with \\r\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: "A log line with & < >"}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: "A log line with utf8 : 🚀 ψ ω β"}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":\".{20,}\"}$`,
&JSONLog{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":\".{20,}\"}$`,
&JSONLog{}: `^{\"time\":\".{20,}\"}$`,
// These ones are a little weird
JSONLog{Log: "\u2028 \u2029"}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":\".{20,}\"}$`,
JSONLog{Log: string([]byte{0xaF})}: `^{\"log\":\"\\ufffd\",\"time\":\".{20,}\"}$`,
JSONLog{Log: string([]byte{0x7F})}: `^{\"log\":\"\x7f\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: "\u2028 \u2029"}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: string([]byte{0xaF})}: `^{\"log\":\"\\ufffd\",\"time\":\".{20,}\"}$`,
&JSONLog{Log: string([]byte{0x7F})}: `^{\"log\":\"\x7f\",\"time\":\".{20,}\"}$`,
}
for jsonLog, expression := range logs {
data, err := jsonLog.MarshalJSON()