1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/loggo"
16
// Record holds all the information for a single log record.
18
// ID identifies the record and its position in a sequence
22
// Origin describes what created the record.
25
// Timestamp is when the record was created.
28
// Level is the basic logging level of the record.
31
// Location describes where the record was created.
32
Location SourceLocation
34
// Message is the record's body. It may be empty.
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")
44
if rec.Timestamp.IsZero() {
45
return errors.NewNotValid(nil, "empty Timestamp")
48
// rec.Level may be anything, so we don't check it.
50
if err := rec.Location.Validate(); err != nil {
51
return errors.Annotate(err, "invalid Location")
54
// rec.Message may be anything, so we don't check it.
59
// SourceLocation identifies the line of source code that originated
61
type SourceLocation struct {
62
// Module is the source "module" (e.g. package) where the record
63
// originated. This is optional.
66
// Filename is the base name of the source file. This is required
67
// only if Line is greater than 0.
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.
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)
83
return SourceLocation{}, errors.Annotate(err, "failed to parse sourceLine")
85
loc := SourceLocation{
93
func parseSourceLine(sourceLine string) (filename string, line int, err error) {
94
filename, sep, lineNoStr := rPartition(sourceLine, ":")
96
return filename, -1, nil
99
return "", -1, errors.New(`missing line number after ":"`)
101
lineNo, err := strconv.Atoi(lineNoStr)
103
return "", -1, errors.Annotate(err, "line number must be non-negative integer")
106
return "", -1, errors.New("line number must be non-negative integer")
108
return filename, lineNo, nil
111
func rPartition(str, sep string) (remainder, used, part string) {
112
pos := strings.LastIndex(str, sep)
116
return str[:pos], sep, str[pos+1:]
119
// String returns a string representation of the location.
120
func (loc SourceLocation) String() string {
124
if loc.Line == 0 && loc.Filename == "" {
127
return fmt.Sprintf("%s:%d", loc.Filename, loc.Line)
130
var zero SourceLocation
132
// Validate ensures that the location is correct.
133
func (loc SourceLocation) Validate() error {
138
// Module may be anything, so there's nothing to check there.
140
// Filename may be set with no line number set, but not the other
142
if loc.Line >= 0 && loc.Filename == "" {
143
return errors.NewNotValid(nil, "Line set but Filename empty")