Having a map per log entry seemed heavier than necessary. These
attributes end up being sorted and serialized, so storing them in a map
doesn't add anything (there's no random access element). In SwarmKit,
they originate as a slice, so there's an unnecessary conversion to a map
and back.
This also fixes the sort comparator, which used to inefficiently split
the string on each comparison.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
| ... | ... |
@@ -5,7 +5,6 @@ import ( |
| 5 | 5 |
"io" |
| 6 | 6 |
"net/url" |
| 7 | 7 |
"sort" |
| 8 |
- "strings" |
|
| 9 | 8 |
|
| 10 | 9 |
"golang.org/x/net/context" |
| 11 | 10 |
|
| ... | ... |
@@ -53,7 +52,8 @@ func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMe |
| 53 | 53 |
} |
| 54 | 54 |
logLine := msg.Line |
| 55 | 55 |
if config.Details {
|
| 56 |
- logLine = append([]byte(stringAttrs(msg.Attrs)+" "), logLine...) |
|
| 56 |
+ logLine = append(attrsByteSlice(msg.Attrs), ' ') |
|
| 57 |
+ logLine = append(logLine, msg.Line...) |
|
| 57 | 58 |
} |
| 58 | 59 |
if config.Timestamps {
|
| 59 | 60 |
// TODO(dperny) the format is defined in |
| ... | ... |
@@ -71,24 +71,26 @@ func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMe |
| 71 | 71 |
} |
| 72 | 72 |
} |
| 73 | 73 |
|
| 74 |
-type byKey []string |
|
| 74 |
+type byKey []backend.LogAttr |
|
| 75 | 75 |
|
| 76 |
-func (s byKey) Len() int { return len(s) }
|
|
| 77 |
-func (s byKey) Less(i, j int) bool {
|
|
| 78 |
- keyI := strings.Split(s[i], "=") |
|
| 79 |
- keyJ := strings.Split(s[j], "=") |
|
| 80 |
- return keyI[0] < keyJ[0] |
|
| 81 |
-} |
|
| 82 |
-func (s byKey) Swap(i, j int) {
|
|
| 83 |
- s[i], s[j] = s[j], s[i] |
|
| 84 |
-} |
|
| 76 |
+func (b byKey) Len() int { return len(b) }
|
|
| 77 |
+func (b byKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
|
|
| 78 |
+func (b byKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
| 85 | 79 |
|
| 86 |
-func stringAttrs(a backend.LogAttributes) string {
|
|
| 87 |
- var ss byKey |
|
| 88 |
- for k, v := range a {
|
|
| 89 |
- k, v := url.QueryEscape(k), url.QueryEscape(v) |
|
| 90 |
- ss = append(ss, k+"="+v) |
|
| 80 |
+func attrsByteSlice(a []backend.LogAttr) []byte {
|
|
| 81 |
+ // Note this sorts "a" in-place. That is fine here - nothing else is |
|
| 82 |
+ // going to use Attrs or care about the order. |
|
| 83 |
+ sort.Sort(byKey(a)) |
|
| 84 |
+ |
|
| 85 |
+ var ret []byte |
|
| 86 |
+ for i, pair := range a {
|
|
| 87 |
+ k, v := url.QueryEscape(pair.Key), url.QueryEscape(pair.Value) |
|
| 88 |
+ ret = append(ret, []byte(k)...) |
|
| 89 |
+ ret = append(ret, '=') |
|
| 90 |
+ ret = append(ret, []byte(v)...) |
|
| 91 |
+ if i != len(a)-1 {
|
|
| 92 |
+ ret = append(ret, ',') |
|
| 93 |
+ } |
|
| 91 | 94 |
} |
| 92 |
- sort.Sort(ss) |
|
| 93 |
- return strings.Join(ss, ",") |
|
| 95 |
+ return ret |
|
| 94 | 96 |
} |
| ... | ... |
@@ -35,7 +35,7 @@ type LogMessage struct {
|
| 35 | 35 |
Line []byte |
| 36 | 36 |
Source string |
| 37 | 37 |
Timestamp time.Time |
| 38 |
- Attrs LogAttributes |
|
| 38 |
+ Attrs []LogAttr |
|
| 39 | 39 |
Partial bool |
| 40 | 40 |
|
| 41 | 41 |
// Err is an error associated with a message. Completeness of a message |
| ... | ... |
@@ -44,9 +44,11 @@ type LogMessage struct {
|
| 44 | 44 |
Err error |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
-// LogAttributes is used to hold the extra attributes available in the log message |
|
| 48 |
-// Primarily used for converting the map type to string and sorting. |
|
| 49 |
-type LogAttributes map[string]string |
|
| 47 |
+// LogAttr is used to hold the extra attributes available in the log message. |
|
| 48 |
+type LogAttr struct {
|
|
| 49 |
+ Key string |
|
| 50 |
+ Value string |
|
| 51 |
+} |
|
| 50 | 52 |
|
| 51 | 53 |
// LogSelector is a list of services and tasks that should be returned as part |
| 52 | 54 |
// of a log stream. It is similar to swarmapi.LogSelector, with the difference |
| ... | ... |
@@ -524,10 +524,12 @@ func (r *controller) Logs(ctx context.Context, publisher exec.LogPublisher, opti |
| 524 | 524 |
} |
| 525 | 525 |
|
| 526 | 526 |
// parse the details out of the Attrs map |
| 527 |
- attrs := []api.LogAttr{}
|
|
| 528 |
- for k, v := range msg.Attrs {
|
|
| 529 |
- attr := api.LogAttr{Key: k, Value: v}
|
|
| 530 |
- attrs = append(attrs, attr) |
|
| 527 |
+ var attrs []api.LogAttr |
|
| 528 |
+ if len(msg.Attrs) != 0 {
|
|
| 529 |
+ attrs = make([]api.LogAttr, 0, len(msg.Attrs)) |
|
| 530 |
+ for _, attr := range msg.Attrs {
|
|
| 531 |
+ attrs = append(attrs, api.LogAttr{Key: attr.Key, Value: attr.Value})
|
|
| 532 |
+ } |
|
| 531 | 533 |
} |
| 532 | 534 |
|
| 533 | 535 |
if err := publisher.Publish(ctx, api.LogMessage{
|
| ... | ... |
@@ -458,22 +458,33 @@ func (c *Cluster) ServiceLogs(ctx context.Context, selector *backend.LogSelector |
| 458 | 458 |
for _, msg := range subscribeMsg.Messages {
|
| 459 | 459 |
// make a new message |
| 460 | 460 |
m := new(backend.LogMessage) |
| 461 |
- m.Attrs = make(backend.LogAttributes) |
|
| 461 |
+ m.Attrs = make([]backend.LogAttr, 0, len(msg.Attrs)+3) |
|
| 462 | 462 |
// add the timestamp, adding the error if it fails |
| 463 | 463 |
m.Timestamp, err = gogotypes.TimestampFromProto(msg.Timestamp) |
| 464 | 464 |
if err != nil {
|
| 465 | 465 |
m.Err = err |
| 466 | 466 |
} |
| 467 |
+ |
|
| 468 |
+ nodeKey := contextPrefix + ".node.id" |
|
| 469 |
+ serviceKey := contextPrefix + ".service.id" |
|
| 470 |
+ taskKey := contextPrefix + ".task.id" |
|
| 471 |
+ |
|
| 467 | 472 |
// copy over all of the details |
| 468 | 473 |
for _, d := range msg.Attrs {
|
| 469 |
- m.Attrs[d.Key] = d.Value |
|
| 474 |
+ switch d.Key {
|
|
| 475 |
+ case nodeKey, serviceKey, taskKey: |
|
| 476 |
+ // we have the final say over context details (in case there |
|
| 477 |
+ // is a conflict (if the user added a detail with a context's |
|
| 478 |
+ // key for some reason)) |
|
| 479 |
+ default: |
|
| 480 |
+ m.Attrs = append(m.Attrs, backend.LogAttr{Key: d.Key, Value: d.Value})
|
|
| 481 |
+ } |
|
| 470 | 482 |
} |
| 471 |
- // we have the final say over context details (in case there |
|
| 472 |
- // is a conflict (if the user added a detail with a context's |
|
| 473 |
- // key for some reason)) |
|
| 474 |
- m.Attrs[contextPrefix+".node.id"] = msg.Context.NodeID |
|
| 475 |
- m.Attrs[contextPrefix+".service.id"] = msg.Context.ServiceID |
|
| 476 |
- m.Attrs[contextPrefix+".task.id"] = msg.Context.TaskID |
|
| 483 |
+ m.Attrs = append(m.Attrs, |
|
| 484 |
+ backend.LogAttr{Key: nodeKey, Value: msg.Context.NodeID},
|
|
| 485 |
+ backend.LogAttr{Key: serviceKey, Value: msg.Context.ServiceID},
|
|
| 486 |
+ backend.LogAttr{Key: taskKey, Value: msg.Context.TaskID},
|
|
| 487 |
+ ) |
|
| 477 | 488 |
|
| 478 | 489 |
switch msg.Stream {
|
| 479 | 490 |
case swarmapi.LogStreamStdout: |
| ... | ... |
@@ -157,6 +157,7 @@ import ( |
| 157 | 157 |
|
| 158 | 158 |
"github.com/Sirupsen/logrus" |
| 159 | 159 |
"github.com/coreos/go-systemd/journal" |
| 160 |
+ "github.com/docker/docker/api/types/backend" |
|
| 160 | 161 |
"github.com/docker/docker/daemon/logger" |
| 161 | 162 |
) |
| 162 | 163 |
|
| ... | ... |
@@ -213,14 +214,11 @@ drain: |
| 213 | 213 |
source = "stdout" |
| 214 | 214 |
} |
| 215 | 215 |
// Retrieve the values of any variables we're adding to the journal. |
| 216 |
- attrs := make(map[string]string) |
|
| 216 |
+ var attrs []backend.LogAttr |
|
| 217 | 217 |
C.sd_journal_restart_data(j) |
| 218 | 218 |
for C.get_attribute_field(j, &data, &length) > C.int(0) {
|
| 219 | 219 |
kv := strings.SplitN(C.GoStringN(data, C.int(length)), "=", 2) |
| 220 |
- attrs[kv[0]] = kv[1] |
|
| 221 |
- } |
|
| 222 |
- if len(attrs) == 0 {
|
|
| 223 |
- attrs = nil |
|
| 220 |
+ attrs = append(attrs, backend.LogAttr{Key: kv[0], Value: kv[1]})
|
|
| 224 | 221 |
} |
| 225 | 222 |
// Send the log message. |
| 226 | 223 |
logWatcher.Msg <- &logger.Message{
|
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
"golang.org/x/net/context" |
| 13 | 13 |
|
| 14 | 14 |
"github.com/Sirupsen/logrus" |
| 15 |
+ "github.com/docker/docker/api/types/backend" |
|
| 15 | 16 |
"github.com/docker/docker/daemon/logger" |
| 16 | 17 |
"github.com/docker/docker/daemon/logger/jsonfilelog/multireader" |
| 17 | 18 |
"github.com/docker/docker/pkg/filenotify" |
| ... | ... |
@@ -27,11 +28,18 @@ func decodeLogLine(dec *json.Decoder, l *jsonlog.JSONLog) (*logger.Message, erro |
| 27 | 27 |
if err := dec.Decode(l); err != nil {
|
| 28 | 28 |
return nil, err |
| 29 | 29 |
} |
| 30 |
+ var attrs []backend.LogAttr |
|
| 31 |
+ if len(l.Attrs) != 0 {
|
|
| 32 |
+ attrs = make([]backend.LogAttr, 0, len(l.Attrs)) |
|
| 33 |
+ for k, v := range l.Attrs {
|
|
| 34 |
+ attrs = append(attrs, backend.LogAttr{Key: k, Value: v})
|
|
| 35 |
+ } |
|
| 36 |
+ } |
|
| 30 | 37 |
msg := &logger.Message{
|
| 31 | 38 |
Source: l.Stream, |
| 32 | 39 |
Timestamp: l.Created, |
| 33 | 40 |
Line: []byte(l.Log), |
| 34 |
- Attrs: l.Attrs, |
|
| 41 |
+ Attrs: attrs, |
|
| 35 | 42 |
} |
| 36 | 43 |
return msg, nil |
| 37 | 44 |
} |
| ... | ... |
@@ -68,11 +68,6 @@ func (m *Message) AsLogMessage() *backend.LogMessage {
|
| 68 | 68 |
return (*backend.LogMessage)(m) |
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
-// LogAttributes is used to hold the extra attributes available in the log message |
|
| 72 |
-// Primarily used for converting the map type to string and sorting. |
|
| 73 |
-// Imported here so it can be used internally with less refactoring |
|
| 74 |
-type LogAttributes backend.LogAttributes |
|
| 75 |
- |
|
| 76 | 71 |
// Logger is the interface for docker logging drivers. |
| 77 | 72 |
type Logger interface {
|
| 78 | 73 |
Log(*Message) error |
| ... | ... |
@@ -1,5 +1,9 @@ |
| 1 | 1 |
package logger |
| 2 | 2 |
|
| 3 |
+import ( |
|
| 4 |
+ "github.com/docker/docker/api/types/backend" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 3 | 7 |
func (m *Message) copy() *Message {
|
| 4 | 8 |
msg := &Message{
|
| 5 | 9 |
Source: m.Source, |
| ... | ... |
@@ -8,10 +12,8 @@ func (m *Message) copy() *Message {
|
| 8 | 8 |
} |
| 9 | 9 |
|
| 10 | 10 |
if m.Attrs != nil {
|
| 11 |
- msg.Attrs = make(map[string]string, len(m.Attrs)) |
|
| 12 |
- for k, v := range m.Attrs {
|
|
| 13 |
- msg.Attrs[k] = v |
|
| 14 |
- } |
|
| 11 |
+ msg.Attrs = make([]backend.LogAttr, len(m.Attrs)) |
|
| 12 |
+ copy(msg.Attrs, m.Attrs) |
|
| 15 | 13 |
} |
| 16 | 14 |
|
| 17 | 15 |
msg.Line = append(make([]byte, 0, len(m.Line)), m.Line...) |