go-micro.dev/v5@v5.12.0/debug/handler/debug.go (about)

     1  // Package handler implements service debug handler embedded in go-micro services
     2  package handler
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"io"
     8  	"time"
     9  
    10  	"go-micro.dev/v5/client"
    11  	"go-micro.dev/v5/debug/log"
    12  	proto "go-micro.dev/v5/debug/proto"
    13  	"go-micro.dev/v5/debug/stats"
    14  	"go-micro.dev/v5/debug/trace"
    15  )
    16  
    17  // NewHandler returns an instance of the Debug Handler.
    18  func NewHandler(c client.Client) *Debug {
    19  	return &Debug{
    20  		log:   log.DefaultLog,
    21  		stats: stats.DefaultStats,
    22  		trace: trace.DefaultTracer,
    23  	}
    24  }
    25  
    26  var _ proto.DebugHandler = (*Debug)(nil)
    27  
    28  type Debug struct {
    29  	// must honor the debug handler
    30  	proto.DebugHandler
    31  	// the logger for retrieving logs
    32  	log log.Log
    33  	// the stats collector
    34  	stats stats.Stats
    35  	// the tracer
    36  	trace trace.Tracer
    37  }
    38  
    39  func (d *Debug) Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error {
    40  	rsp.Status = "ok"
    41  	return nil
    42  }
    43  
    44  func (d *Debug) MessageBus(ctx context.Context, stream proto.Debug_MessageBusStream) error {
    45  	for {
    46  		_, err := stream.Recv()
    47  		if errors.Is(err, io.EOF) {
    48  			return nil
    49  		} else if err != nil {
    50  			return err
    51  		}
    52  
    53  		rsp := proto.BusMsg{
    54  			Msg: "Request received!",
    55  		}
    56  
    57  		if err := stream.Send(&rsp); err != nil {
    58  			return err
    59  		}
    60  	}
    61  }
    62  
    63  func (d *Debug) Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.StatsResponse) error {
    64  	stats, err := d.stats.Read()
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	if len(stats) == 0 {
    70  		return nil
    71  	}
    72  
    73  	// write the response values
    74  	rsp.Timestamp = uint64(stats[0].Timestamp)
    75  	rsp.Started = uint64(stats[0].Started)
    76  	rsp.Uptime = uint64(stats[0].Uptime)
    77  	rsp.Memory = stats[0].Memory
    78  	rsp.Gc = stats[0].GC
    79  	rsp.Threads = stats[0].Threads
    80  	rsp.Requests = stats[0].Requests
    81  	rsp.Errors = stats[0].Errors
    82  
    83  	return nil
    84  }
    85  
    86  func (d *Debug) Trace(ctx context.Context, req *proto.TraceRequest, rsp *proto.TraceResponse) error {
    87  	traces, err := d.trace.Read(trace.ReadTrace(req.Id))
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	for _, t := range traces {
    93  		var typ proto.SpanType
    94  
    95  		switch t.Type {
    96  		case trace.SpanTypeRequestInbound:
    97  			typ = proto.SpanType_INBOUND
    98  		case trace.SpanTypeRequestOutbound:
    99  			typ = proto.SpanType_OUTBOUND
   100  		}
   101  
   102  		rsp.Spans = append(rsp.Spans, &proto.Span{
   103  			Trace:    t.Trace,
   104  			Id:       t.Id,
   105  			Parent:   t.Parent,
   106  			Name:     t.Name,
   107  			Started:  uint64(t.Started.UnixNano()),
   108  			Duration: uint64(t.Duration.Nanoseconds()),
   109  			Type:     typ,
   110  			Metadata: t.Metadata,
   111  		})
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (d *Debug) Log(ctx context.Context, req *proto.LogRequest, stream proto.Debug_LogStream) error {
   118  
   119  	var options []log.ReadOption
   120  
   121  	since := time.Unix(req.Since, 0)
   122  	if !since.IsZero() {
   123  		options = append(options, log.Since(since))
   124  	}
   125  
   126  	count := int(req.Count)
   127  	if count > 0 {
   128  		options = append(options, log.Count(count))
   129  	}
   130  
   131  	if req.Stream {
   132  		// TODO: we need to figure out how to close the log stream
   133  		// It seems like when a client disconnects,
   134  		// the connection stays open until some timeout expires
   135  		// or something like that; that means the map of streams
   136  		// might end up leaking memory if not cleaned up properly
   137  		lgStream, err := d.log.Stream()
   138  		if err != nil {
   139  			return err
   140  		}
   141  		defer lgStream.Stop()
   142  
   143  		for record := range lgStream.Chan() {
   144  			// copy metadata
   145  			metadata := make(map[string]string)
   146  			for k, v := range record.Metadata {
   147  				metadata[k] = v
   148  			}
   149  			// send record
   150  			if err := stream.Send(&proto.Record{
   151  				Timestamp: record.Timestamp.Unix(),
   152  				Message:   record.Message.(string),
   153  				Metadata:  metadata,
   154  			}); err != nil {
   155  				return err
   156  			}
   157  		}
   158  
   159  		// done streaming, return
   160  		return nil
   161  	}
   162  
   163  	// get the log records
   164  	records, err := d.log.Read(options...)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	// send all the logs downstream
   170  	for _, record := range records {
   171  		// copy metadata
   172  		metadata := make(map[string]string)
   173  		for k, v := range record.Metadata {
   174  			metadata[k] = v
   175  		}
   176  		// send record
   177  		if err := stream.Send(&proto.Record{
   178  			Timestamp: record.Timestamp.Unix(),
   179  			Message:   record.Message.(string),
   180  			Metadata:  metadata,
   181  		}); err != nil {
   182  			return err
   183  		}
   184  	}
   185  
   186  	return nil
   187  }