github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 }