logs.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package formatter
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "strconv"
  19. "strings"
  20. "sync"
  21. "time"
  22. "github.com/docker/compose/v2/pkg/api"
  23. "github.com/docker/docker/pkg/jsonmessage"
  24. )
  25. // LogConsumer consume logs from services and format them
  26. type logConsumer struct {
  27. ctx context.Context
  28. presenters sync.Map // map[string]*presenter
  29. width int
  30. stdout io.Writer
  31. stderr io.Writer
  32. color bool
  33. prefix bool
  34. timestamp bool
  35. }
  36. // NewLogConsumer creates a new LogConsumer
  37. func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix, timestamp bool) api.LogConsumer {
  38. return &logConsumer{
  39. ctx: ctx,
  40. presenters: sync.Map{},
  41. width: 0,
  42. stdout: stdout,
  43. stderr: stderr,
  44. color: color,
  45. prefix: prefix,
  46. timestamp: timestamp,
  47. }
  48. }
  49. func (l *logConsumer) Register(name string) {
  50. l.register(name)
  51. }
  52. func (l *logConsumer) register(name string) *presenter {
  53. cf := monochrome
  54. if l.color {
  55. if name == api.WatchLogger {
  56. cf = makeColorFunc("92")
  57. } else {
  58. cf = nextColor()
  59. }
  60. }
  61. p := &presenter{
  62. colors: cf,
  63. name: name,
  64. }
  65. l.presenters.Store(name, p)
  66. if l.prefix {
  67. l.computeWidth()
  68. l.presenters.Range(func(key, value interface{}) bool {
  69. p := value.(*presenter)
  70. p.setPrefix(l.width)
  71. return true
  72. })
  73. }
  74. return p
  75. }
  76. func (l *logConsumer) getPresenter(container string) *presenter {
  77. p, ok := l.presenters.Load(container)
  78. if !ok { // should have been registered, but ¯\_(ツ)_/¯
  79. return l.register(container)
  80. }
  81. return p.(*presenter)
  82. }
  83. // Log formats a log message as received from name/container
  84. func (l *logConsumer) Log(container, message string) {
  85. l.write(l.stdout, container, message)
  86. }
  87. // Log formats a log message as received from name/container
  88. func (l *logConsumer) Err(container, message string) {
  89. l.write(l.stderr, container, message)
  90. }
  91. func (l *logConsumer) write(w io.Writer, container, message string) {
  92. if l.ctx.Err() != nil {
  93. return
  94. }
  95. p := l.getPresenter(container)
  96. timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
  97. for _, line := range strings.Split(message, "\n") {
  98. if l.timestamp {
  99. fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
  100. } else {
  101. fmt.Fprintf(w, "%s%s\n", p.prefix, line)
  102. }
  103. }
  104. }
  105. func (l *logConsumer) Status(container, msg string) {
  106. p := l.getPresenter(container)
  107. s := p.colors(fmt.Sprintf("%s %s\n", container, msg))
  108. l.stdout.Write([]byte(s)) //nolint:errcheck
  109. }
  110. func (l *logConsumer) computeWidth() {
  111. width := 0
  112. l.presenters.Range(func(key, value interface{}) bool {
  113. p := value.(*presenter)
  114. if len(p.name) > width {
  115. width = len(p.name)
  116. }
  117. return true
  118. })
  119. l.width = width + 1
  120. }
  121. type presenter struct {
  122. colors colorFunc
  123. name string
  124. prefix string
  125. }
  126. func (p *presenter) setPrefix(width int) {
  127. if p.name == api.WatchLogger {
  128. p.prefix = p.colors(strings.Repeat(" ", width) + " ⦿ ")
  129. return
  130. }
  131. p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
  132. }