Skip to content

Commit 9e8d0f1

Browse files
committed
Finish rewrite
- Finished io.Writer logger and test logger - Added stderrlog and testlog packages.
1 parent fc2c4a0 commit 9e8d0f1

File tree

18 files changed

+804
-123
lines changed

18 files changed

+804
-123
lines changed

console.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (y *consoleMarshaller) marshal(v fieldValue) {
6464
y.line()
6565
}
6666

67-
y.s(quoteMapKey(f.name) + ":")
67+
y.s(quote(f.name) + ":")
6868

6969
y.marshalSub(f.value, true)
7070
}
@@ -121,9 +121,10 @@ func (y *consoleMarshaller) marshalSub(v fieldValue, isParentMap bool) {
121121
}
122122
}
123123

124-
// quoteMapKey quotes a string so that it is suitable
125-
// as a key for a map.
126-
func quoteMapKey(key string) string {
124+
// quotes quotes a string so that it is suitable
125+
// as a key for a map or in general some output that
126+
// cannot span multiple lines or have weird characters.
127+
func quote(key string) string {
127128
// strconv.Quote does not quote an empty string so we need this.
128129
if key == "" {
129130
return `""`

context.go

Lines changed: 0 additions & 49 deletions
This file was deleted.

entry.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package slog
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"go.coder.com/slog/internal/skipctx"
7+
"go.opencensus.io/trace"
8+
"path/filepath"
9+
"runtime"
10+
"time"
11+
)
12+
13+
type entry struct {
14+
time time.Time
15+
16+
level level
17+
msg string
18+
19+
component string
20+
21+
fn string
22+
file string
23+
line int
24+
25+
spanCtx trace.SpanContext
26+
27+
fields fieldMap
28+
}
29+
30+
func (ent entry) pinnedFields() string {
31+
pinned := fieldMap{}
32+
33+
if ent.spanCtx != (trace.SpanContext{}) {
34+
pinned = pinned.append("trace", fieldString(ent.spanCtx.TraceID.String()))
35+
pinned = pinned.append("span", fieldString(ent.spanCtx.SpanID.String()))
36+
}
37+
38+
return marshalFields(pinned)
39+
}
40+
41+
func (ent entry) stringFields() string {
42+
pinned := ent.pinnedFields()
43+
fields := marshalFields(ent.fields)
44+
45+
if pinned == "" {
46+
return fields
47+
}
48+
49+
if fields == "" {
50+
return pinned
51+
}
52+
53+
return pinned + "\n" + fields
54+
}
55+
56+
// Same as time.StampMilli but the days in the month padded by zeros.
57+
const timestampMilli = "Jan 02 15:04:05.000"
58+
59+
func (ent entry) String() string {
60+
var ents string
61+
if ent.file != "" {
62+
ents += fmt.Sprintf("%v:%v: ", filepath.Base(ent.file), ent.line)
63+
}
64+
ents += fmt.Sprintf("%v [%v]", ent.time.Format(timestampMilli), ent.level)
65+
66+
if ent.component != "" {
67+
ents += fmt.Sprintf(" (%v)", quote(ent.component))
68+
}
69+
70+
ents += fmt.Sprintf(": %v", quote(ent.msg))
71+
72+
fields := ent.stringFields()
73+
if fields != "" {
74+
// We never return with a trailing newline because Go's testing framework adds one
75+
// automatically and if we include one, then we'll get two newlines.
76+
// We also do not indent the fields as go's test does that automatically
77+
// for extra lines in a log so if we did it here, the fields would be indented
78+
// twice in test logs. So the Stderr logger indents all the fields itself.
79+
ents += "\n" + fields
80+
}
81+
82+
return ents
83+
}
84+
85+
type entryConfig struct {
86+
level level
87+
msg string
88+
fields []interface{}
89+
skip int
90+
}
91+
92+
func (l parsedFields) entry(ctx context.Context, config entryConfig) entry {
93+
l = l.withContext(ctx)
94+
l = l.withFields(config.fields)
95+
96+
ent := entry{
97+
time: time.Now(),
98+
level: config.level,
99+
component: l.component,
100+
msg: config.msg,
101+
spanCtx: trace.FromContext(ctx).SpanContext(),
102+
fields: l.fields,
103+
}
104+
105+
file, line, fn, ok := location(config.skip + 1 + skipctx.From(ctx))
106+
if ok {
107+
ent.file = file
108+
ent.line = line
109+
ent.fn = fn
110+
}
111+
return ent
112+
}
113+
114+
func location(skip int) (file string, line int, fn string, ok bool) {
115+
pc, file, line, ok := runtime.Caller(skip + 1)
116+
if !ok {
117+
return "", 0, "", false
118+
}
119+
f := runtime.FuncForPC(pc)
120+
return file, line, f.Name(), true
121+
}

examples_test.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
package slog_test
22

3-
// func Example_stderr() {
4-
// ctx := context.Background()
5-
// stderrlog.Info(ctx, "my message here",
6-
// "field_name", "something or the other",
7-
// "some_map", map[string]string{
8-
// "nested_fields": "wowow",
9-
// },
10-
// "some slice", []interface{}{
11-
// 1,
12-
// "foof",
13-
// "bar",
14-
// true,
15-
// },
16-
// log.Component, "test",
17-
// )
18-
// }
19-
//
3+
import (
4+
"context"
5+
"go.coder.com/slog"
6+
"go.coder.com/slog/stderrlog"
7+
)
8+
9+
func Example_stderr() {
10+
ctx := context.Background()
11+
stderrlog.Info(ctx, "my message here",
12+
"field_name", "something or the other",
13+
"some_map", map[string]string{
14+
"nested_fields": "wowow",
15+
},
16+
"some slice", []interface{}{
17+
1,
18+
"foof",
19+
"bar",
20+
true,
21+
},
22+
slog.Component("test"),
23+
)
24+
}
25+
2026
// func Example_test() {
2127
// // Nil here but would be provided by the testing framework.
2228
// var t *testing.T

fields.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package slog
2+
3+
import (
4+
"context"
5+
"go.opencensus.io/trace"
6+
"golang.org/x/xerrors"
7+
"reflect"
8+
)
9+
10+
type level string
11+
12+
const (
13+
levelDebug level = "DEBUG"
14+
levelInfo level = "INFO"
15+
levelWarn level = "WARN"
16+
levelError level = "ERROR"
17+
levelCritical level = "CRITICAL"
18+
levelFatal level = "FATAL"
19+
)
20+
21+
type parsedFields struct {
22+
component string
23+
spanCtx trace.SpanContext
24+
25+
fields fieldMap
26+
}
27+
28+
func parseFields(fields []interface{}) parsedFields {
29+
var l parsedFields
30+
31+
for i := 0; i < len(fields); i++ {
32+
f := fields[i]
33+
switch f := f.(type) {
34+
case componentField:
35+
l = l.appendComponent(string(f))
36+
continue
37+
case Field:
38+
l = l.appendField(f.LogKey(), f)
39+
continue
40+
case string:
41+
i++
42+
if i < len(fields) {
43+
v := fields[i]
44+
l = l.appendField(f, v)
45+
continue
46+
}
47+
48+
// Missing value for key.
49+
err := xerrors.Errorf("missing log value for key %v", f)
50+
l = l.appendField("missing_value_error", err)
51+
default:
52+
// Unexpected key type.
53+
err := xerrors.Errorf("unexpected log key of type %T: %#v", f, f)
54+
l = l.appendField("bad_key_error", err)
55+
// Skip the next value under the assumption that it is a value.
56+
i++
57+
}
58+
}
59+
60+
return l
61+
}
62+
63+
func (l parsedFields) appendField(k string, v interface{}) parsedFields {
64+
l.fields = l.fields.append(k, reflectFieldValue(reflect.ValueOf(v)))
65+
return l
66+
}
67+
68+
func (l parsedFields) withFields(f []interface{}) parsedFields {
69+
return l.with(parseFields(f))
70+
}
71+
72+
func (l parsedFields) with(l2 parsedFields) parsedFields {
73+
l = l.appendComponent(l2.component)
74+
if l2.spanCtx != (trace.SpanContext{}) {
75+
l.spanCtx = l2.spanCtx
76+
}
77+
78+
l.fields = l.fields.appendFields(l2.fields)
79+
return l
80+
}
81+
82+
func (l parsedFields) appendComponent(name string) parsedFields {
83+
if l.component == "" {
84+
l.component = name
85+
} else if name != "" {
86+
l.component += "." + name
87+
}
88+
return l
89+
}
90+
91+
func (l parsedFields) withContext(ctx context.Context) parsedFields {
92+
l2 := fromContext(ctx)
93+
if len(l2.fields) == 0 {
94+
return l
95+
}
96+
97+
return l.with(l2)
98+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/fatih/camelcase v1.0.0
77
github.com/golang/protobuf v1.3.2
88
github.com/google/go-cmp v0.3.0
9-
go.opencensus.io v0.22.1 // indirect
9+
go.opencensus.io v0.22.1
1010
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
1111
google.golang.org/genproto v0.0.0-20190905072037-92dd089d5514 // indirect
1212
)

internal/skipctx/skipctx.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Package skipctx contains helpers to put the frame skip level
2+
// into the context. Used by stderrlog and testlog helpers to
3+
// skip one more frame level.
4+
package skipctx
5+
6+
import (
7+
"context"
8+
)
9+
10+
type skipKey struct{}
11+
12+
func With(ctx context.Context, skip int) context.Context {
13+
skip += From(ctx)
14+
return context.WithValue(ctx, skipKey{}, skip)
15+
}
16+
17+
func From(ctx context.Context) int {
18+
l, _ := ctx.Value(skipKey{}).(int)
19+
return l
20+
}

0 commit comments

Comments
 (0)