Skip to content

Commit 7dc2c25

Browse files
authored
feat: use agentv2 API for build logs (#78)
1 parent 0e485f9 commit 7dc2c25

File tree

17 files changed

+712
-1153
lines changed

17 files changed

+712
-1153
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ jobs:
3737
# Install Go!
3838
- uses: actions/setup-go@v3
3939
with:
40-
go-version: "~1.20"
40+
go-version: "~1.22"
4141

4242
# Check for Go linting errors!
4343
- name: Lint Go
44-
uses: golangci/golangci-lint-action@v3.3.1
44+
uses: golangci/golangci-lint-action@v6.0.1
4545
with:
46-
version: v1.51.0
46+
version: v1.59.1
4747
args: "--out-${NO_FUTURE}format colored-line-number"
4848

4949
- name: Lint shell scripts
@@ -98,7 +98,7 @@ jobs:
9898

9999
- uses: actions/setup-go@v3
100100
with:
101-
go-version: "~1.20"
101+
go-version: "~1.22"
102102

103103
# Sadly the new "set output" syntax (of writing env vars to
104104
# $GITHUB_OUTPUT) does not work on both powershell and bash so we use the
@@ -136,7 +136,7 @@ jobs:
136136

137137
- uses: actions/setup-go@v3
138138
with:
139-
go-version: "1.20.5"
139+
go-version: "~1.22"
140140

141141
# Sadly the new "set output" syntax (of writing env vars to
142142
# $GITHUB_OUTPUT) does not work on both powershell and bash so we use the
@@ -170,7 +170,7 @@ jobs:
170170
- uses: actions/checkout@v3
171171
- uses: actions/setup-go@v3
172172
with:
173-
go-version: "1.20.5"
173+
go-version: "~1.22"
174174

175175
- name: Go Cache Paths
176176
id: go-cache-paths
@@ -223,7 +223,7 @@ jobs:
223223
- name: Setup Go
224224
uses: actions/setup-go@v3
225225
with:
226-
go-version: "~1.20"
226+
go-version: "~1.22"
227227

228228
- name: Go Cache Paths
229229
id: go-cache-paths
@@ -254,7 +254,7 @@ jobs:
254254

255255
- uses: actions/setup-go@v3
256256
with:
257-
go-version: "1.20.5"
257+
go-version: "~1.22"
258258

259259
- name: build image
260260
run: make -j build/image/envbox

.golangci.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ linters-settings:
5454
# - importShadow
5555
- indexAlloc
5656
- initClause
57-
- ioutilDeprecated
5857
- mapKey
5958
- methodExprCall
6059
# - nestingReduce

background/process.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ func scanIntoLog(ctx context.Context, log slog.Logger, scanner *bufio.Scanner, b
233233
logFn = log.Info
234234
)
235235

236+
//nolint:gocritic
236237
if strings.Contains(line, "level=debug") {
237238
logFn = log.Debug
238239
} else if strings.Contains(line, "level=info") {

buildlog/coder.go

Lines changed: 101 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ package buildlog
33
import (
44
"context"
55
"fmt"
6+
"io"
7+
"net/url"
68
"time"
79

10+
"github.com/google/uuid"
11+
"golang.org/x/xerrors"
12+
"storj.io/drpc"
13+
814
"cdr.dev/slog"
9-
"github.com/coder/coder/codersdk/agentsdk"
15+
"github.com/coder/coder/v2/agent/proto"
16+
"github.com/coder/coder/v2/codersdk"
17+
"github.com/coder/coder/v2/codersdk/agentsdk"
18+
"github.com/coder/retry"
1019
)
1120

1221
const (
@@ -22,30 +31,94 @@ type StartupLog struct {
2231
}
2332

2433
type CoderClient interface {
25-
PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error
34+
Send(level codersdk.LogLevel, log string) error
35+
io.Closer
2636
}
2737

28-
type CoderLogger struct {
29-
ctx context.Context
30-
cancel context.CancelFunc
31-
client CoderClient
32-
logger slog.Logger
33-
logChan chan string
34-
err error
38+
type coderClient struct {
39+
ctx context.Context
40+
cancel context.CancelFunc
41+
source uuid.UUID
42+
ls *agentsdk.LogSender
43+
sl agentsdk.ScriptLogger
44+
log slog.Logger
3545
}
3646

37-
func OpenCoderLogger(ctx context.Context, client CoderClient, log slog.Logger) Logger {
38-
ctx, cancel := context.WithCancel(ctx)
47+
func (c *coderClient) Send(level codersdk.LogLevel, log string) error {
48+
err := c.sl.Send(c.ctx, agentsdk.Log{
49+
CreatedAt: time.Now(),
50+
Output: log,
51+
Level: level,
52+
})
53+
if err != nil {
54+
return xerrors.Errorf("send build log: %w", err)
55+
}
56+
return nil
57+
}
3958

40-
coder := &CoderLogger{
41-
ctx: ctx,
42-
cancel: cancel,
43-
client: client,
44-
logger: log,
45-
logChan: make(chan string),
59+
func (c *coderClient) Close() error {
60+
defer c.cancel()
61+
c.ls.Flush(c.source)
62+
err := c.ls.WaitUntilEmpty(c.ctx)
63+
if err != nil {
64+
return xerrors.Errorf("wait until empty: %w", err)
4665
}
66+
return nil
67+
}
4768

48-
go coder.processLogs()
69+
func OpenCoderClient(ctx context.Context, accessURL *url.URL, logger slog.Logger, token string) (CoderClient, error) {
70+
client := agentsdk.New(accessURL)
71+
client.SetSessionToken(token)
72+
73+
cctx, cancel := context.WithCancel(ctx)
74+
uid := uuid.New()
75+
ls := agentsdk.NewLogSender(logger)
76+
sl := ls.GetScriptLogger(uid)
77+
78+
var conn drpc.Conn
79+
var err error
80+
for r := retry.New(10*time.Millisecond, time.Second); r.Wait(ctx); {
81+
conn, err = client.ConnectRPC(ctx)
82+
if err != nil {
83+
logger.Error(ctx, "connect err", slog.Error(err))
84+
continue
85+
}
86+
break
87+
}
88+
if conn == nil {
89+
cancel()
90+
return nil, xerrors.Errorf("connect rpc: %w", err)
91+
}
92+
93+
arpc := proto.NewDRPCAgentClient(conn)
94+
go func() {
95+
err := ls.SendLoop(ctx, arpc)
96+
if err != nil {
97+
logger.Error(ctx, "send loop", slog.Error(err))
98+
}
99+
}()
100+
101+
return &coderClient{
102+
ctx: cctx,
103+
cancel: cancel,
104+
source: uid,
105+
ls: ls,
106+
sl: sl,
107+
log: logger,
108+
}, nil
109+
}
110+
111+
type CoderLogger struct {
112+
ctx context.Context
113+
client CoderClient
114+
logger slog.Logger
115+
}
116+
117+
func OpenCoderLogger(client CoderClient, log slog.Logger) Logger {
118+
coder := &CoderLogger{
119+
client: client,
120+
logger: log,
121+
}
49122

50123
return coder
51124
}
@@ -55,84 +128,31 @@ func (c *CoderLogger) Infof(format string, a ...any) {
55128
}
56129

57130
func (c *CoderLogger) Info(output string) {
58-
c.log(output)
131+
c.log(codersdk.LogLevelInfo, output)
59132
}
60133

61134
func (c *CoderLogger) Errorf(format string, a ...any) {
62135
c.Error(fmt.Sprintf(format, a...))
63136
}
64137

65138
func (c *CoderLogger) Error(output string) {
66-
c.log("ERROR: " + output)
139+
c.log(codersdk.LogLevelError, output)
67140
}
68141

69-
func (c *CoderLogger) log(output string) {
70-
if c.err != nil {
71-
return
142+
func (c *CoderLogger) log(level codersdk.LogLevel, output string) {
143+
if err := c.client.Send(level, output); err != nil {
144+
c.logger.Error(c.ctx, "send build log",
145+
slog.F("log", output),
146+
slog.Error(err),
147+
)
72148
}
73-
c.logChan <- output
74149
}
75150

76151
func (c *CoderLogger) Write(p []byte) (int, error) {
77152
c.Info(string(p))
78153
return len(p), nil
79154
}
80155

81-
func (c *CoderLogger) Close() {
82-
c.cancel()
83-
}
84-
85-
func (c *CoderLogger) processLogs() {
86-
for {
87-
var (
88-
line string
89-
logs = make([]agentsdk.StartupLog, 0, CoderLoggerMaxLogs)
90-
)
91-
92-
select {
93-
case line = <-c.logChan:
94-
lines := cutString(line, MaxCoderLogSize)
95-
96-
for _, output := range lines {
97-
logs = append(logs, agentsdk.StartupLog{
98-
CreatedAt: time.Now(),
99-
Output: output,
100-
})
101-
}
102-
103-
case <-c.ctx.Done():
104-
close(c.logChan)
105-
return
106-
}
107-
108-
// Send the logs in a goroutine so that we can avoid blocking
109-
// too long on the channel.
110-
cpLogs := logs
111-
go func(startupLogs []agentsdk.StartupLog) {
112-
err := c.client.PatchStartupLogs(c.ctx, agentsdk.PatchStartupLogs{
113-
Logs: startupLogs,
114-
})
115-
if err != nil {
116-
c.logger.Error(c.ctx, "send startup logs", slog.Error(err))
117-
}
118-
}(cpLogs)
119-
}
120-
}
121-
122-
// cutString cuts a string up into smaller strings that have a len no greater
123-
// than the provided max size.
124-
// If the string is less than the max size the return slice has one
125-
// element with a value of the provided string.
126-
func cutString(s string, maxSize int) []string {
127-
if len(s) <= maxSize {
128-
return []string{s}
129-
}
130-
131-
toks := []string{}
132-
for len(s) > maxSize {
133-
toks = append(toks, s[:maxSize])
134-
s = s[maxSize:]
135-
}
136-
137-
return append(toks, s)
156+
func (c *CoderLogger) Close() error {
157+
return c.client.Close()
138158
}

0 commit comments

Comments
 (0)