blob: 3f1b866c24cb6fa2cc513d1c6554960afc0abde5 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package logdog provide an interface for writing LogDog logs.
//
// Since LogDog output is a single stream, this package is not
// goroutine safe.
//
// Also, because it is a single stream, printing from
// any Logger or Step that is not the most recently created, or
// creating a second step at the same nesting level without closing
// the previous one is undefined behavior.
//
// As a rule of thumb, do not call any methods on a Logger or Step
// after calling the Step method, until the returned Step is Closed.
// This is not enforced because it would require adding errors
// everywhere and complicating the API, or adding panics which is
// undesirable (logging errors are not severe enough to be fatal).
package logdog
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"lucifer/annotations"
)
// Logger defines the methods for writing LogDog logs. The
// implementation may format the output differently if the output
// stream is not being processed for LogDog (e.g., if the output is
// just a text stream).
//
// The provided interface is consistent with top level LogDog output
// not associated with a step. Since it is not associated with a
// step, you cannot attach step links or indicate step failure.
type Logger interface {
// RawWriter returns the raw backing Writer. This can be used
// for dumping copious log data from sources like a
// subprocess.
RawWriter() io.Writer
// Basic log printing methods.
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
// LogDog specific methods.
Step(string) Step
LabeledLog(string, string)
}
// Step defines the methods for writing LogDog logs while a step is
// active. This provides a superset of the methods available globally
// via Logger.
//
// Step also implements Logger, which means you could pass a Step to a
// hypothetical function that takes a root Logger, and the function
// which would normally log steps under the root would instead log
// substeps under the given Step. However, you would generally not
// assign a Step to a Logger variable.
type Step interface {
Logger
// Close writes out the end of the LogDog step. Calling any
// method on a step after it is closed is undefined, including
// calling Close again. This method always returns nil (but
// implements io.Closer).
Close() error
Failure()
Exception()
AddLink(label, url string)
}
var programName = filepath.Base(os.Args[0])
// realLogger is the real LogDog implementation of Logger.
type realLogger struct {
w io.Writer
printer
}
// NewLogger returns a Logger that writes to the given writer, which
// should process LogDog annotations.
func NewLogger(w io.Writer) Logger {
rl := realLogger{
w: w,
}
prefix := fmt.Sprintf("%s: ", programName)
rl.logger = log.New(w, prefix, log.LstdFlags)
return &rl
}
// RawWriter implements the Logger interface.
func (rl *realLogger) RawWriter() io.Writer {
return rl.w
}
// Step implements the Logger interface.
func (rl *realLogger) Step(name string) Step {
annotations.SeedStep(rl.w, name)
annotations.StepCursor(rl.w, name)
annotations.StepStarted(rl.w)
return &realStep{
realLogger: rl,
name: name,
}
}
// LabeledLog implements the Logger interface.
func (rl *realLogger) LabeledLog(label, line string) {
annotations.StepLogLine(rl.w, label, line)
}
type realStep struct {
*realLogger
name string
level int
parentName string
}
// Close implements the Step interface.
func (rs *realStep) Close() error {
annotations.StepClosed(rs.w)
if rs.level > 0 {
annotations.StepCursor(rs.w, rs.parentName)
}
return nil
}
// Failure implements the Step interface.
func (rs *realStep) Failure() {
annotations.StepFailure(rs.w)
}
// Exception implements the Step interface.
func (rs *realStep) Exception() {
annotations.StepException(rs.w)
}
// AddLink implements the Step interface.
func (rs *realStep) AddLink(label, url string) {
annotations.StepLink(rs.w, label, url)
}
// Step implements the Step interface.
func (rs *realStep) Step(name string) Step {
annotations.SeedStep(rs.w, name)
annotations.StepCursor(rs.w, name)
annotations.StepNestLevel(rs.w, rs.level+1)
annotations.StepStarted(rs.w)
return &realStep{
realLogger: rs.realLogger,
name: name,
level: rs.level + 1,
parentName: rs.name,
}
}