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  }