github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/logfwd/record.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package logfwd
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  )
    15  
    16  // Record holds all the information for a single log record.
    17  type Record struct {
    18  	// ID identifies the record and its position in a sequence
    19  	// of records.
    20  	ID int64
    21  
    22  	// Origin describes what created the record.
    23  	Origin Origin
    24  
    25  	// Timestamp is when the record was created.
    26  	Timestamp time.Time
    27  
    28  	// Level is the basic logging level of the record.
    29  	Level loggo.Level
    30  
    31  	// Location describes where the record was created.
    32  	Location SourceLocation
    33  
    34  	// Message is the record's body. It may be empty.
    35  	Message string
    36  }
    37  
    38  // Validate ensures that the record is correct.
    39  func (rec Record) Validate() error {
    40  	if err := rec.Origin.Validate(); err != nil {
    41  		return errors.Annotate(err, "invalid Origin")
    42  	}
    43  
    44  	if rec.Timestamp.IsZero() {
    45  		return errors.NewNotValid(nil, "empty Timestamp")
    46  	}
    47  
    48  	// rec.Level may be anything, so we don't check it.
    49  
    50  	if err := rec.Location.Validate(); err != nil {
    51  		return errors.Annotate(err, "invalid Location")
    52  	}
    53  
    54  	// rec.Message may be anything, so we don't check it.
    55  
    56  	return nil
    57  }
    58  
    59  // SourceLocation identifies the line of source code that originated
    60  // a log record.
    61  type SourceLocation struct {
    62  	// Module is the source "module" (e.g. package) where the record
    63  	// originated. This is optional.
    64  	Module string
    65  
    66  	// Filename is the base name of the source file. This is required
    67  	// only if Line is greater than 0.
    68  	Filename string
    69  
    70  	// Line is the line number in the source. It is optional. A negative
    71  	// value means "not set". So does 0 if Filename is not set. If Line
    72  	// is greater than 0 then Filename must be set.
    73  	Line int
    74  }
    75  
    76  // ParseLocation converts the given info into a SourceLocation. The
    77  // expected format is "FILENAME" or "FILENAME:LINE". If the first format
    78  // is used then Line is set to -1. If provided, LINE must be a
    79  // non-negative integer.
    80  func ParseLocation(module, sourceLine string) (SourceLocation, error) {
    81  	filename, lineNo, err := parseSourceLine(sourceLine)
    82  	if err != nil {
    83  		return SourceLocation{}, errors.Annotate(err, "failed to parse sourceLine")
    84  	}
    85  	loc := SourceLocation{
    86  		Module:   module,
    87  		Filename: filename,
    88  		Line:     lineNo,
    89  	}
    90  	return loc, nil
    91  }
    92  
    93  func parseSourceLine(sourceLine string) (filename string, line int, err error) {
    94  	filename, sep, lineNoStr := rPartition(sourceLine, ":")
    95  	if sep == "" {
    96  		return filename, -1, nil
    97  	}
    98  	if lineNoStr == "" {
    99  		return "", -1, errors.New(`missing line number after ":"`)
   100  	}
   101  	lineNo, err := strconv.Atoi(lineNoStr)
   102  	if err != nil {
   103  		return "", -1, errors.Annotate(err, "line number must be non-negative integer")
   104  	}
   105  	if lineNo < 0 {
   106  		return "", -1, errors.New("line number must be non-negative integer")
   107  	}
   108  	return filename, lineNo, nil
   109  }
   110  
   111  func rPartition(str, sep string) (remainder, used, part string) {
   112  	pos := strings.LastIndex(str, sep)
   113  	if pos < 0 {
   114  		return str, "", ""
   115  	}
   116  	return str[:pos], sep, str[pos+1:]
   117  }
   118  
   119  // String returns a string representation of the location.
   120  func (loc SourceLocation) String() string {
   121  	if loc.Line < 0 {
   122  		return loc.Filename
   123  	}
   124  	if loc.Line == 0 && loc.Filename == "" {
   125  		return ""
   126  	}
   127  	return fmt.Sprintf("%s:%d", loc.Filename, loc.Line)
   128  }
   129  
   130  var zero SourceLocation
   131  
   132  // Validate ensures that the location is correct.
   133  func (loc SourceLocation) Validate() error {
   134  	if loc == zero {
   135  		return nil
   136  	}
   137  
   138  	// Module may be anything, so there's nothing to check there.
   139  
   140  	// Filename may be set with no line number set, but not the other
   141  	// way around.
   142  	if loc.Line >= 0 && loc.Filename == "" {
   143  		return errors.NewNotValid(nil, "Line set but Filename empty")
   144  	}
   145  
   146  	return nil
   147  }