Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to store logs in files #3568

Merged
merged 22 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/server/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,17 @@ var flags = append([]cli.Flag{
Usage: "Disable version check in admin web ui.",
Name: "skip-version-check",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_LOG_STORE"},
Name: "log-store",
Usage: "log store to use ('database' or 'file')",
Value: "database",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_LOG_STORE_FILE_BASE"},
Name: "log-store-file-base",
Usage: "base directory for file log storage",
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
},
//
// backend options for pipeline compiler
//
Expand Down
6 changes: 6 additions & 0 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ func setupEvilGlobals(c *cli.Context, s store.Store) error {
}
server.Config.Services.Manager = serviceManager

println(c.String("log-store"))
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
server.Config.Services.LogStore, err = setupLogStore(c, s)
if err != nil {
return fmt.Errorf("could not setup log store manager: %w", err)
anbraten marked this conversation as resolved.
Show resolved Hide resolved
}

// authentication
server.Config.Pipeline.AuthenticatePublicRepos = c.Bool("authenticate-public-repos")

Expand Down
11 changes: 11 additions & 0 deletions cmd/server/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/cache"
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
logService "go.woodpecker-ci.org/woodpecker/v2/server/services/log"
"go.woodpecker-ci.org/woodpecker/v2/server/services/log/file"
"go.woodpecker-ci.org/woodpecker/v2/server/store"
"go.woodpecker-ci.org/woodpecker/v2/server/store/datastore"
)
Expand Down Expand Up @@ -154,3 +156,12 @@ func setupMetrics(g *errgroup.Group, _store store.Store) {
}
})
}

func setupLogStore(c *cli.Context, s store.Store) (logService.Service, error) {
switch c.String("log-store") {
case "file":
return file.NewLogStore(c.String("log-store-file-base"))
anbraten marked this conversation as resolved.
Show resolved Hide resolved
default:
return s, nil
}
}
12 changes: 12 additions & 0 deletions docs/docs/30-administration/10-server-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,18 @@ Enable the Swagger UI for API documentation.

Disable version check in admin web UI.

### `WOODPECKER_LOG_STORE`

> Default: `database`

Where to store logs. Possible values: `database` or `file`.

### `WOODPECKER_LOG_STORE_FILE_BASE`

> Default empty

Directory to store logs if [`WOODPECKER_LOG_STORE`](#woodpecker_log_store) is `file`.
anbraten marked this conversation as resolved.
Show resolved Hide resolved

---

### `WOODPECKER_GITHUB_...`
Expand Down
4 changes: 2 additions & 2 deletions server/api/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func GetStepLogs(c *gin.Context) {
return
}

logs, err := _store.LogFind(step)
logs, err := server.Config.Services.LogStore.LogFind(step)
if err != nil {
handleDBError(c, err)
return
Expand Down Expand Up @@ -622,7 +622,7 @@ func DeletePipelineLogs(c *gin.Context) {
}

for _, step := range steps {
if lErr := _store.LogDelete(step); err != nil {
if lErr := server.Config.Services.LogStore.LogDelete(step); err != nil {
err = errors.Join(err, lErr)
}
}
Expand Down
2 changes: 2 additions & 0 deletions server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/pubsub"
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
"go.woodpecker-ci.org/woodpecker/v2/server/services"
"go.woodpecker-ci.org/woodpecker/v2/server/services/log"
"go.woodpecker-ci.org/woodpecker/v2/server/services/permissions"
)

Expand All @@ -34,6 +35,7 @@ var Config = struct {
Logs logging.Log
Membership cache.MembershipService
Manager services.Manager
LogStore log.Service
}
Server struct {
Key string
Expand Down
4 changes: 2 additions & 2 deletions server/grpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ func (s *RPC) Log(c context.Context, _logEntry *rpc.LogEntry) error {
log.Error().Err(err).Msgf("rpc server could not write to logger")
}
}()
// make line persistent in database
return s.store.LogAppend(logEntry)
// store line
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
return server.Config.Services.LogStore.LogAppend(logEntry)
}

func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int32) (int64, error) {
Expand Down
79 changes: 79 additions & 0 deletions server/services/log/file/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package file

import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"

"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/services/log"
)

type logStore struct {
base string
}

func NewLogStore(base string) (log.Service, error) {
if base == "" {
return nil, fmt.Errorf("file storage base path is required")
}
if _, err := os.Stat(base); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(base, 0600)
if err != nil {
return nil, err
}
}
return logStore{base: base}, nil
}

func (l logStore) filePath(id int64) string {
return filepath.Join(l.base, strconv.Itoa(int(id))+".json")
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
anbraten marked this conversation as resolved.
Show resolved Hide resolved
}

func (l logStore) LogFind(step *model.Step) ([]*model.LogEntry, error) {
file, err := os.ReadFile(l.filePath(step.ID))
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}

var entries []*model.LogEntry
for _, j := range bytes.Split(file, []byte("\n")) {
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
if len(bytes.TrimSpace(j)) == 0 {
continue
}
entry := &model.LogEntry{}
err = json.Unmarshal(j, entry)
if err != nil {
return nil, err
}
entries = append(entries, entry)
}

return entries, nil
}

func (l logStore) LogAppend(logEntry *model.LogEntry) error {
file, err := os.OpenFile(l.filePath(logEntry.StepID), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
jsonData, err := json.Marshal(logEntry)
if err != nil {
return err
}
_, err = file.Write(append(jsonData, byte('\n')))
if err != nil {
return err
}
return file.Close()
}

func (l logStore) LogDelete(step *model.Step) error {
return os.Remove(l.filePath(step.ID))
}
9 changes: 9 additions & 0 deletions server/services/log/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package log

import "go.woodpecker-ci.org/woodpecker/v2/server/model"

type Service interface {
LogFind(step *model.Step) ([]*model.LogEntry, error)
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
LogAppend(logEntry *model.LogEntry) error
LogDelete(step *model.Step) error
}