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>
| ... | ... |
@@ -25,6 +25,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
| 25 | 25 |
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
| 26 | 26 |
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
| 27 | 27 |
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
| 28 |
+ details := cmd.Bool([]string{"-details"}, false, "Show extra details provided to logs")
|
|
| 28 | 29 |
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
| 29 | 30 |
cmd.Require(flag.Exact, 1) |
| 30 | 31 |
|
| ... | ... |
@@ -48,6 +49,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
| 48 | 48 |
Timestamps: *times, |
| 49 | 49 |
Follow: *follow, |
| 50 | 50 |
Tail: *tail, |
| 51 |
+ Details: *details, |
|
| 51 | 52 |
} |
| 52 | 53 |
responseBody, err := cli.client.ContainerLogs(context.Background(), name, options) |
| 53 | 54 |
if err != nil {
|
| ... | ... |
@@ -9,6 +9,8 @@ package logger |
| 9 | 9 |
|
| 10 | 10 |
import ( |
| 11 | 11 |
"errors" |
| 12 |
+ "sort" |
|
| 13 |
+ "strings" |
|
| 12 | 14 |
"time" |
| 13 | 15 |
|
| 14 | 16 |
"github.com/docker/docker/pkg/jsonlog" |
| ... | ... |
@@ -29,6 +31,31 @@ type Message struct {
|
| 29 | 29 |
Line []byte |
| 30 | 30 |
Source string |
| 31 | 31 |
Timestamp time.Time |
| 32 |
+ Attrs LogAttributes |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// LogAttributes is used to hold the extra attributes available in the log message |
|
| 36 |
+// Primarily used for converting the map type to string and sorting. |
|
| 37 |
+type LogAttributes map[string]string |
|
| 38 |
+type byKey []string |
|
| 39 |
+ |
|
| 40 |
+func (s byKey) Len() int { return len(s) }
|
|
| 41 |
+func (s byKey) Less(i, j int) bool {
|
|
| 42 |
+ keyI := strings.Split(s[i], "=") |
|
| 43 |
+ keyJ := strings.Split(s[j], "=") |
|
| 44 |
+ return keyI[0] < keyJ[0] |
|
| 45 |
+} |
|
| 46 |
+func (s byKey) Swap(i, j int) {
|
|
| 47 |
+ s[i], s[j] = s[j], s[i] |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func (a LogAttributes) String() string {
|
|
| 51 |
+ var ss byKey |
|
| 52 |
+ for k, v := range a {
|
|
| 53 |
+ ss = append(ss, k+"="+v) |
|
| 54 |
+ } |
|
| 55 |
+ sort.Sort(ss) |
|
| 56 |
+ return strings.Join(ss, ",") |
|
| 32 | 57 |
} |
| 33 | 58 |
|
| 34 | 59 |
// Logger is the interface for docker logging drivers. |
| ... | ... |
@@ -90,6 +90,9 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c |
| 90 | 90 |
return nil |
| 91 | 91 |
} |
| 92 | 92 |
logLine := msg.Line |
| 93 |
+ if config.Details {
|
|
| 94 |
+ logLine = append([]byte(msg.Attrs.String()+" "), logLine...) |
|
| 95 |
+ } |
|
| 93 | 96 |
if config.Timestamps {
|
| 94 | 97 |
logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) |
| 95 | 98 |
} |
| ... | ... |
@@ -136,6 +136,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 136 | 136 |
* `POST /auth` now returns an `IdentityToken` when supported by a registry. |
| 137 | 137 |
* `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`. |
| 138 | 138 |
* `GET /volumes` now supports more filters, new added filters are `name` and `driver`. |
| 139 |
+* `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. |
|
| 139 | 140 |
|
| 140 | 141 |
### v1.22 API changes |
| 141 | 142 |
|
| ... | ... |
@@ -770,6 +770,7 @@ Get `stdout` and `stderr` logs from the container ``id`` |
| 770 | 770 |
|
| 771 | 771 |
Query Parameters: |
| 772 | 772 |
|
| 773 |
+- **details** - 1/True/true or 0/False/flase, Show extra details provided to logs. Default `false`. |
|
| 773 | 774 |
- **follow** – 1/True/true or 0/False/false, return stream. Default `false`. |
| 774 | 775 |
- **stdout** – 1/True/true or 0/False/false, show `stdout` log. Default `false`. |
| 775 | 776 |
- **stderr** – 1/True/true or 0/False/false, show `stderr` log. Default `false`. |
| ... | ... |
@@ -14,6 +14,7 @@ parent = "smn_cli" |
| 14 | 14 |
|
| 15 | 15 |
Fetch the logs of a container |
| 16 | 16 |
|
| 17 |
+ --details Show extra details provided to logs |
|
| 17 | 18 |
-f, --follow Follow log output |
| 18 | 19 |
--help Print usage |
| 19 | 20 |
--since="" Show logs since timestamp |
| ... | ... |
@@ -36,6 +37,10 @@ The `docker logs --timestamps` command will add an [RFC3339Nano timestamp](https |
| 36 | 36 |
log entry. To ensure that the timestamps are aligned the |
| 37 | 37 |
nano-second part of the timestamp will be padded with zero when necessary. |
| 38 | 38 |
|
| 39 |
+The `docker logs --details` command will add on extra attributes, such as |
|
| 40 |
+environment variables and labels, provided to `--log-opt` when creating the |
|
| 41 |
+container. |
|
| 42 |
+ |
|
| 39 | 43 |
The `--since` option shows only the container logs generated after |
| 40 | 44 |
a given date. You can specify the date as an RFC 3339 date, a UNIX |
| 41 | 45 |
timestamp, or a Go duration string (e.g. `1m30s`, `3h`). Besides RFC3339 date |
| ... | ... |
@@ -307,3 +307,16 @@ func (s *DockerSuite) TestLogsCLIContainerNotFound(c *check.C) {
|
| 307 | 307 |
message := fmt.Sprintf("Error: No such container: %s\n", name)
|
| 308 | 308 |
c.Assert(out, checker.Equals, message) |
| 309 | 309 |
} |
| 310 |
+ |
|
| 311 |
+func (s *DockerSuite) TestLogsWithDetails(c *check.C) {
|
|
| 312 |
+ dockerCmd(c, "run", "--name=test", "--label", "foo=bar", "-e", "baz=qux", "--log-opt", "labels=foo", "--log-opt", "env=baz", "busybox", "echo", "hello") |
|
| 313 |
+ out, _ := dockerCmd(c, "logs", "--details", "--timestamps", "test") |
|
| 314 |
+ |
|
| 315 |
+ logFields := strings.Fields(strings.TrimSpace(out)) |
|
| 316 |
+ c.Assert(len(logFields), checker.Equals, 3, check.Commentf(out)) |
|
| 317 |
+ |
|
| 318 |
+ details := strings.Split(logFields[1], ",") |
|
| 319 |
+ c.Assert(details, checker.HasLen, 2) |
|
| 320 |
+ c.Assert(details[0], checker.Equals, "baz=qux") |
|
| 321 |
+ c.Assert(details[1], checker.Equals, "foo=bar") |
|
| 322 |
+} |
| ... | ... |
@@ -30,6 +30,9 @@ logging drivers. |
| 30 | 30 |
**--help** |
| 31 | 31 |
Print usage statement |
| 32 | 32 |
|
| 33 |
+**--details**=*true*|*false* |
|
| 34 |
+ Show extra details provided to logs |
|
| 35 |
+ |
|
| 33 | 36 |
**-f**, **--follow**=*true*|*false* |
| 34 | 37 |
Follow log output. The default is *false*. |
| 35 | 38 |
|
| ... | ... |
@@ -55,6 +58,10 @@ epoch or Unix time), and the optional .nanoseconds field is a fraction of a |
| 55 | 55 |
second no more than nine digits long. You can combine the `--since` option with |
| 56 | 56 |
either or both of the `--follow` or `--tail` options. |
| 57 | 57 |
|
| 58 |
+The `docker logs --details` command will add on extra attributes, such as |
|
| 59 |
+environment variables and labels, provided to `--log-opt` when creating the |
|
| 60 |
+container. |
|
| 61 |
+ |
|
| 58 | 62 |
# HISTORY |
| 59 | 63 |
April 2014, Originally compiled by William Henry (whenry at redhat dot com) |
| 60 | 64 |
based on docker.com source material and internal work. |
| ... | ... |
@@ -15,6 +15,8 @@ type JSONLog struct {
|
| 15 | 15 |
Stream string `json:"stream,omitempty"` |
| 16 | 16 |
// Created is the created timestamp of log |
| 17 | 17 |
Created time.Time `json:"time"` |
| 18 |
+ // Attrs is the list of extra attributes provided by the user |
|
| 19 |
+ Attrs map[string]string `json:"attrs,omitempty"` |
|
| 18 | 20 |
} |
| 19 | 21 |
|
| 20 | 22 |
// Format returns the log formatted according to format |
| ... | ... |
@@ -6,18 +6,18 @@ import ( |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 | 8 |
func TestJSONLogMarshalJSON(t *testing.T) {
|
| 9 |
- logs := map[JSONLog]string{
|
|
| 10 |
- JSONLog{Log: `"A log line with \\"`}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":\".{20,}\"}$`,
|
|
| 11 |
- JSONLog{Log: "A log line"}: `^{\"log\":\"A log line\",\"time\":\".{20,}\"}$`,
|
|
| 12 |
- JSONLog{Log: "A log line with \r"}: `^{\"log\":\"A log line with \\r\",\"time\":\".{20,}\"}$`,
|
|
| 13 |
- JSONLog{Log: "A log line with & < >"}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":\".{20,}\"}$`,
|
|
| 14 |
- JSONLog{Log: "A log line with utf8 : 🚀 ψ ω β"}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":\".{20,}\"}$`,
|
|
| 15 |
- JSONLog{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":\".{20,}\"}$`,
|
|
| 16 |
- JSONLog{}: `^{\"time\":\".{20,}\"}$`,
|
|
| 9 |
+ logs := map[*JSONLog]string{
|
|
| 10 |
+ &JSONLog{Log: `"A log line with \\"`}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":\".{20,}\"}$`,
|
|
| 11 |
+ &JSONLog{Log: "A log line"}: `^{\"log\":\"A log line\",\"time\":\".{20,}\"}$`,
|
|
| 12 |
+ &JSONLog{Log: "A log line with \r"}: `^{\"log\":\"A log line with \\r\",\"time\":\".{20,}\"}$`,
|
|
| 13 |
+ &JSONLog{Log: "A log line with & < >"}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":\".{20,}\"}$`,
|
|
| 14 |
+ &JSONLog{Log: "A log line with utf8 : 🚀 ψ ω β"}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":\".{20,}\"}$`,
|
|
| 15 |
+ &JSONLog{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":\".{20,}\"}$`,
|
|
| 16 |
+ &JSONLog{}: `^{\"time\":\".{20,}\"}$`,
|
|
| 17 | 17 |
// These ones are a little weird |
| 18 |
- JSONLog{Log: "\u2028 \u2029"}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":\".{20,}\"}$`,
|
|
| 19 |
- JSONLog{Log: string([]byte{0xaF})}: `^{\"log\":\"\\ufffd\",\"time\":\".{20,}\"}$`,
|
|
| 20 |
- JSONLog{Log: string([]byte{0x7F})}: `^{\"log\":\"\x7f\",\"time\":\".{20,}\"}$`,
|
|
| 18 |
+ &JSONLog{Log: "\u2028 \u2029"}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":\".{20,}\"}$`,
|
|
| 19 |
+ &JSONLog{Log: string([]byte{0xaF})}: `^{\"log\":\"\\ufffd\",\"time\":\".{20,}\"}$`,
|
|
| 20 |
+ &JSONLog{Log: string([]byte{0x7F})}: `^{\"log\":\"\x7f\",\"time\":\".{20,}\"}$`,
|
|
| 21 | 21 |
} |
| 22 | 22 |
for jsonLog, expression := range logs {
|
| 23 | 23 |
data, err := jsonLog.MarshalJSON() |