~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/logfwd/record.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}