xref: /aosp_15_r20/build/soong/ui/status/log.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2018 Google Inc. All rights reserved.
2//
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//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package status
16
17import (
18	"compress/gzip"
19	"errors"
20	"fmt"
21	"io"
22	"io/ioutil"
23	"os"
24	"strings"
25	"sync"
26	"time"
27
28	"google.golang.org/protobuf/proto"
29
30	"android/soong/ui/logger"
31	soong_build_error_proto "android/soong/ui/status/build_error_proto"
32	soong_build_progress_proto "android/soong/ui/status/build_progress_proto"
33)
34
35type verboseLog struct {
36	w    *gzip.Writer
37	lock *sync.Mutex
38	data chan []string
39	stop chan bool
40}
41
42func NewVerboseLog(log logger.Logger, filename string) StatusOutput {
43	if !strings.HasSuffix(filename, ".gz") {
44		filename += ".gz"
45	}
46
47	f, err := logger.CreateFileWithRotation(filename, 5)
48	if err != nil {
49		log.Println("Failed to create verbose log file:", err)
50		return nil
51	}
52
53	w := gzip.NewWriter(f)
54
55	l := &verboseLog{
56		w:    w,
57		lock: &sync.Mutex{},
58		data: make(chan []string),
59		stop: make(chan bool),
60	}
61	l.startWriter()
62	return l
63}
64
65func (v *verboseLog) startWriter() {
66	go func() {
67		tick := time.Tick(time.Second)
68		for {
69			select {
70			case <-v.stop:
71				close(v.data)
72				v.w.Close()
73				return
74			case <-tick:
75				v.w.Flush()
76			case dataList := <-v.data:
77				for _, data := range dataList {
78					fmt.Fprint(v.w, data)
79				}
80			}
81		}
82	}()
83}
84
85func (v *verboseLog) stopWriter() {
86	v.stop <- true
87}
88
89func (v *verboseLog) queueWrite(s ...string) {
90	v.data <- s
91}
92
93func (v *verboseLog) StartAction(action *Action, counts Counts) {}
94
95func (v *verboseLog) FinishAction(result ActionResult, counts Counts) {
96	cmd := result.Command
97	if cmd == "" {
98		cmd = result.Description
99	}
100
101	v.queueWrite(fmt.Sprintf("[%d/%d] ", counts.FinishedActions, counts.TotalActions), cmd, "\n")
102
103	if result.Error != nil {
104		v.queueWrite("FAILED: ", strings.Join(result.Outputs, " "), "\n")
105	}
106
107	if result.Output != "" {
108		v.queueWrite(result.Output, "\n")
109	}
110}
111
112func (v *verboseLog) Flush() {
113	v.stopWriter()
114}
115
116func (v *verboseLog) Message(level MsgLevel, message string) {
117	v.queueWrite(level.Prefix(), message, "\n")
118}
119
120func (v *verboseLog) Write(p []byte) (int, error) {
121	v.queueWrite(string(p))
122	return len(p), nil
123}
124
125type errorLog struct {
126	w     io.WriteCloser
127	empty bool
128}
129
130func NewErrorLog(log logger.Logger, filename string) StatusOutput {
131	f, err := logger.CreateFileWithRotation(filename, 5)
132	if err != nil {
133		log.Println("Failed to create error log file:", err)
134		return nil
135	}
136
137	return &errorLog{
138		w:     f,
139		empty: true,
140	}
141}
142
143func (e *errorLog) StartAction(action *Action, counts Counts) {}
144
145func (e *errorLog) FinishAction(result ActionResult, counts Counts) {
146	if result.Error == nil {
147		return
148	}
149
150	if !e.empty {
151		fmt.Fprintf(e.w, "\n\n")
152	}
153	e.empty = false
154
155	fmt.Fprintf(e.w, "FAILED: %s\n", result.Description)
156
157	if len(result.Outputs) > 0 {
158		fmt.Fprintf(e.w, "Outputs: %s\n", strings.Join(result.Outputs, " "))
159	}
160
161	fmt.Fprintf(e.w, "Error: %s\n", result.Error)
162	if result.Command != "" {
163		fmt.Fprintf(e.w, "Command: %s\n", result.Command)
164	}
165	fmt.Fprintf(e.w, "Output:\n%s\n", result.Output)
166}
167
168func (e *errorLog) Flush() {
169	e.w.Close()
170}
171
172func (e *errorLog) Message(level MsgLevel, message string) {
173	if level < ErrorLvl {
174		return
175	}
176
177	if !e.empty {
178		fmt.Fprintf(e.w, "\n\n")
179	}
180	e.empty = false
181
182	fmt.Fprintf(e.w, "error: %s\n", message)
183}
184
185func (e *errorLog) Write(p []byte) (int, error) {
186	fmt.Fprint(e.w, string(p))
187	return len(p), nil
188}
189
190type errorProtoLog struct {
191	errorProto soong_build_error_proto.BuildError
192	filename   string
193	log        logger.Logger
194}
195
196func NewProtoErrorLog(log logger.Logger, filename string) StatusOutput {
197	os.Remove(filename)
198	return &errorProtoLog{
199		errorProto: soong_build_error_proto.BuildError{},
200		filename:   filename,
201		log:        log,
202	}
203}
204
205func (e *errorProtoLog) StartAction(action *Action, counts Counts) {}
206
207func (e *errorProtoLog) FinishAction(result ActionResult, counts Counts) {
208	if result.Error == nil {
209		return
210	}
211
212	e.errorProto.ActionErrors = append(e.errorProto.ActionErrors, &soong_build_error_proto.BuildActionError{
213		Description: proto.String(result.Description),
214		Command:     proto.String(result.Command),
215		Output:      proto.String(result.Output),
216		Artifacts:   result.Outputs,
217		Error:       proto.String(result.Error.Error()),
218	})
219
220	err := writeToFile(&e.errorProto, e.filename)
221	if err != nil {
222		e.log.Printf("Failed to write file %s: %v\n", e.filename, err)
223	}
224}
225
226func (e *errorProtoLog) Flush() {
227	//Not required.
228}
229
230func (e *errorProtoLog) Message(level MsgLevel, message string) {
231	if level > ErrorLvl {
232		e.errorProto.ErrorMessages = append(e.errorProto.ErrorMessages, message)
233	}
234}
235
236func (e *errorProtoLog) Write(p []byte) (int, error) {
237	return 0, errors.New("not supported")
238}
239
240type buildProgressLog struct {
241	filename      string
242	log           logger.Logger
243	failedActions uint64
244}
245
246func NewBuildProgressLog(log logger.Logger, filename string) StatusOutput {
247	return &buildProgressLog{
248		filename:      filename,
249		log:           log,
250		failedActions: 0,
251	}
252}
253
254func (b *buildProgressLog) StartAction(action *Action, counts Counts) {
255	b.updateCounters(counts)
256}
257
258func (b *buildProgressLog) FinishAction(result ActionResult, counts Counts) {
259	if result.Error != nil {
260		b.failedActions++
261	}
262	b.updateCounters(counts)
263}
264
265func (b *buildProgressLog) Flush() {
266	//Not required.
267}
268
269func (b *buildProgressLog) Message(level MsgLevel, message string) {
270	// Not required.
271}
272
273func (b *buildProgressLog) Write(p []byte) (int, error) {
274	return 0, errors.New("not supported")
275}
276
277func (b *buildProgressLog) updateCounters(counts Counts) {
278	err := writeToFile(
279		&soong_build_progress_proto.BuildProgress{
280			CurrentActions:  proto.Uint64(uint64(counts.RunningActions)),
281			FinishedActions: proto.Uint64(uint64(counts.FinishedActions)),
282			TotalActions:    proto.Uint64(uint64(counts.TotalActions)),
283			FailedActions:   proto.Uint64(b.failedActions),
284		},
285		b.filename,
286	)
287	if err != nil {
288		b.log.Printf("Failed to write file %s: %v\n", b.filename, err)
289	}
290}
291
292func writeToFile(pb proto.Message, outputPath string) (err error) {
293	data, err := proto.Marshal(pb)
294	if err != nil {
295		return err
296	}
297
298	tempPath := outputPath + ".tmp"
299	err = ioutil.WriteFile(tempPath, []byte(data), 0644)
300	if err != nil {
301		return err
302	}
303
304	err = os.Rename(tempPath, outputPath)
305	if err != nil {
306		return err
307	}
308
309	return nil
310}
311