You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

log.go 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "bufio"
  6. "context"
  7. "fmt"
  8. "io"
  9. "os"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/models/dbfs"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/storage"
  15. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  16. "google.golang.org/protobuf/types/known/timestamppb"
  17. )
  18. const (
  19. MaxLineSize = 64 * 1024
  20. DBFSPrefix = "actions_log/"
  21. timeFormat = "2006-01-02T15:04:05.0000000Z07:00"
  22. defaultBufSize = MaxLineSize
  23. )
  24. func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
  25. name := DBFSPrefix + filename
  26. f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE)
  27. if err != nil {
  28. return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
  29. }
  30. defer f.Close()
  31. if _, err := f.Seek(offset, io.SeekStart); err != nil {
  32. return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
  33. }
  34. writer := bufio.NewWriterSize(f, defaultBufSize)
  35. ns := make([]int, 0, len(rows))
  36. for _, row := range rows {
  37. n, err := writer.WriteString(FormatLog(row.Time.AsTime(), row.Content) + "\n")
  38. if err != nil {
  39. return nil, err
  40. }
  41. ns = append(ns, n)
  42. }
  43. if err := writer.Flush(); err != nil {
  44. return nil, err
  45. }
  46. return ns, nil
  47. }
  48. func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) {
  49. f, err := openLogs(ctx, inStorage, filename)
  50. if err != nil {
  51. return nil, err
  52. }
  53. defer f.Close()
  54. if _, err := f.Seek(offset, io.SeekStart); err != nil {
  55. return nil, fmt.Errorf("file seek: %w", err)
  56. }
  57. scanner := bufio.NewScanner(f)
  58. maxLineSize := len(timeFormat) + MaxLineSize + 1
  59. scanner.Buffer(make([]byte, maxLineSize), maxLineSize)
  60. var rows []*runnerv1.LogRow
  61. for scanner.Scan() && (int64(len(rows)) < limit || limit < 0) {
  62. t, c, err := ParseLog(scanner.Text())
  63. if err != nil {
  64. return nil, fmt.Errorf("parse log %q: %w", scanner.Text(), err)
  65. }
  66. rows = append(rows, &runnerv1.LogRow{
  67. Time: timestamppb.New(t),
  68. Content: c,
  69. })
  70. }
  71. if err := scanner.Err(); err != nil {
  72. return nil, fmt.Errorf("scan: %w", err)
  73. }
  74. return rows, nil
  75. }
  76. func TransferLogs(ctx context.Context, filename string) (func(), error) {
  77. name := DBFSPrefix + filename
  78. remove := func() {
  79. if err := dbfs.Remove(ctx, name); err != nil {
  80. log.Warn("dbfs remove %q: %v", name, err)
  81. }
  82. }
  83. f, err := dbfs.Open(ctx, name)
  84. if err != nil {
  85. return nil, fmt.Errorf("dbfs open %q: %w", name, err)
  86. }
  87. defer f.Close()
  88. if _, err := storage.Actions.Save(filename, f, -1); err != nil {
  89. return nil, fmt.Errorf("storage save %q: %w", filename, err)
  90. }
  91. return remove, nil
  92. }
  93. func RemoveLogs(ctx context.Context, inStorage bool, filename string) error {
  94. if !inStorage {
  95. name := DBFSPrefix + filename
  96. err := dbfs.Remove(ctx, name)
  97. if err != nil {
  98. return fmt.Errorf("dbfs remove %q: %w", name, err)
  99. }
  100. return nil
  101. }
  102. err := storage.Actions.Delete(filename)
  103. if err != nil {
  104. return fmt.Errorf("storage delete %q: %w", filename, err)
  105. }
  106. return nil
  107. }
  108. func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) {
  109. if !inStorage {
  110. name := DBFSPrefix + filename
  111. f, err := dbfs.Open(ctx, name)
  112. if err != nil {
  113. return nil, fmt.Errorf("dbfs open %q: %w", name, err)
  114. }
  115. return f, nil
  116. }
  117. f, err := storage.Actions.Open(filename)
  118. if err != nil {
  119. return nil, fmt.Errorf("storage open %q: %w", filename, err)
  120. }
  121. return f, nil
  122. }
  123. func FormatLog(timestamp time.Time, content string) string {
  124. // Content shouldn't contain new line, it will break log indexes, other control chars are safe.
  125. content = strings.ReplaceAll(content, "\n", `\n`)
  126. if len(content) > MaxLineSize {
  127. content = content[:MaxLineSize]
  128. }
  129. return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), content)
  130. }
  131. func ParseLog(in string) (time.Time, string, error) {
  132. index := strings.IndexRune(in, ' ')
  133. if index < 0 {
  134. return time.Time{}, "", fmt.Errorf("invalid log: %q", in)
  135. }
  136. timestamp, err := time.Parse(timeFormat, in[:index])
  137. if err != nil {
  138. return time.Time{}, "", err
  139. }
  140. return timestamp, in[index+1:], nil
  141. }