github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/common/logs.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "context" 8 "fmt" 9 "net/url" 10 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 15 "github.com/juju/juju/api/base" 16 "github.com/juju/juju/rpc/params" 17 ) 18 19 // DebugLogParams holds parameters for WatchDebugLog that control the 20 // filtering of the log messages. If the structure is zero initialized, the 21 // entire log file is sent back starting from the end, and until the user 22 // closes the connection. 23 type DebugLogParams struct { 24 // IncludeEntity lists entity tags to include in the response. Tags may 25 // include '*' wildcards e.g.: unit-mysql-*, machine-2. If 26 // none are set, then all lines are considered included. 27 IncludeEntity []string 28 // IncludeModule lists logging modules to include in the response. If none 29 // are set all modules are considered included. If a module is specified, 30 // all the submodules also match. 31 IncludeModule []string 32 // IncludeLabel lists logging labels to include in the response. If none 33 // are set all labels are considered included. 34 IncludeLabel []string 35 // ExcludeEntity lists entity tags to exclude from the response. As with 36 // IncludeEntity the values may include '*' wildcards. 37 ExcludeEntity []string 38 // ExcludeModule lists logging modules to exclude from the response. If a 39 // module is specified, all the submodules are also excluded. 40 ExcludeModule []string 41 // ExcludeLabel lists logging labels to exclude from the response. 42 ExcludeLabel []string 43 44 // Limit defines the maximum number of lines to return. Once this many 45 // have been sent, the socket is closed. If zero, all filtered lines are 46 // sent down the connection until the client closes the connection. 47 Limit uint 48 // Backlog tells the server to try to go back this many lines before 49 // starting filtering. If backlog is zero and replay is false, then there 50 // may be an initial delay until the next matching log message is written. 51 Backlog uint 52 // Level specifies the minimum logging level to be sent back in the response. 53 Level loggo.Level 54 // Replay tells the server to start at the start of the log file rather 55 // than the end. If replay is true, backlog is ignored. 56 Replay bool 57 // NoTail tells the server to only return the logs it has now, and not 58 // to wait for new logs to arrive. 59 NoTail bool 60 // StartTime should be a time in the past - only records with a 61 // log time on or after StartTime will be returned. 62 StartTime time.Time 63 } 64 65 func (args DebugLogParams) URLQuery() url.Values { 66 attrs := url.Values{ 67 "includeEntity": args.IncludeEntity, 68 "includeModule": args.IncludeModule, 69 "includeLabel": args.IncludeLabel, 70 "excludeEntity": args.ExcludeEntity, 71 "excludeModule": args.ExcludeModule, 72 "excludeLabel": args.ExcludeLabel, 73 } 74 if args.Replay { 75 attrs.Set("replay", fmt.Sprint(args.Replay)) 76 } 77 if args.NoTail { 78 attrs.Set("noTail", fmt.Sprint(args.NoTail)) 79 } 80 if args.Limit > 0 { 81 attrs.Set("maxLines", fmt.Sprint(args.Limit)) 82 } 83 if args.Backlog > 0 { 84 attrs.Set("backlog", fmt.Sprint(args.Backlog)) 85 } 86 if args.Level != loggo.UNSPECIFIED { 87 attrs.Set("level", fmt.Sprint(args.Level)) 88 } 89 if !args.StartTime.IsZero() { 90 attrs.Set("startTime", args.StartTime.Format(time.RFC3339Nano)) 91 } 92 return attrs 93 } 94 95 // LogMessage is a structured logging entry. 96 type LogMessage struct { 97 Entity string 98 Timestamp time.Time 99 Severity string 100 Module string 101 Location string 102 Message string 103 Labels []string 104 } 105 106 // StreamDebugLog requests the specified debug log records from the 107 // server and returns a channel of the messages that come back. 108 func StreamDebugLog(ctx context.Context, source base.StreamConnector, args DebugLogParams) (<-chan LogMessage, error) { 109 // Prepare URL query attributes. 110 attrs := args.URLQuery() 111 112 connection, err := source.ConnectStream("/log", attrs) 113 if err != nil { 114 return nil, errors.Trace(err) 115 } 116 117 messages := make(chan LogMessage) 118 go func() { 119 defer close(messages) 120 121 for { 122 // If the context is done or cancelled, then we can check to ensure 123 // that the goroutine is cleaned up. 124 if ctx.Err() != nil { 125 return 126 } 127 128 var msg params.LogMessage 129 err := connection.ReadJSON(&msg) 130 if err != nil { 131 return 132 } 133 messages <- LogMessage{ 134 Entity: msg.Entity, 135 Timestamp: msg.Timestamp, 136 Severity: msg.Severity, 137 Module: msg.Module, 138 Location: msg.Location, 139 Message: msg.Message, 140 Labels: msg.Labels, 141 } 142 } 143 }() 144 145 return messages, nil 146 }