chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:33 +08:00
commit e071084ebe
982 changed files with 160368 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
// Package handler implements service debug handler embedded in go-micro services
package handler
import (
"context"
"errors"
"io"
"time"
"go-micro.dev/v6/client"
"go-micro.dev/v6/debug/log"
proto "go-micro.dev/v6/debug/proto"
"go-micro.dev/v6/debug/stats"
"go-micro.dev/v6/debug/trace"
)
// NewHandler returns an instance of the Debug Handler.
func NewHandler(c client.Client) *Debug {
return &Debug{
log: log.DefaultLog,
stats: stats.DefaultStats,
trace: trace.DefaultTracer,
}
}
var _ proto.DebugHandler = (*Debug)(nil)
type Debug struct {
// must honor the debug handler
proto.DebugHandler
// the logger for retrieving logs
log log.Log
// the stats collector
stats stats.Stats
// the tracer
trace trace.Tracer
}
func (d *Debug) Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error {
rsp.Status = "ok"
return nil
}
func (d *Debug) MessageBus(ctx context.Context, stream proto.Debug_MessageBusStream) error {
for {
_, err := stream.Recv()
if errors.Is(err, io.EOF) {
return nil
} else if err != nil {
return err
}
rsp := proto.BusMsg{
Msg: "Request received!",
}
if err := stream.Send(&rsp); err != nil {
return err
}
}
}
func (d *Debug) Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.StatsResponse) error {
stats, err := d.stats.Read()
if err != nil {
return err
}
if len(stats) == 0 {
return nil
}
// write the response values
rsp.Timestamp = uint64(stats[0].Timestamp)
rsp.Started = uint64(stats[0].Started)
rsp.Uptime = uint64(stats[0].Uptime)
rsp.Memory = stats[0].Memory
rsp.Gc = stats[0].GC
rsp.Threads = stats[0].Threads
rsp.Requests = stats[0].Requests
rsp.Errors = stats[0].Errors
return nil
}
func (d *Debug) Trace(ctx context.Context, req *proto.TraceRequest, rsp *proto.TraceResponse) error {
traces, err := d.trace.Read(trace.ReadTrace(req.Id))
if err != nil {
return err
}
for _, t := range traces {
var typ proto.SpanType
switch t.Type {
case trace.SpanTypeRequestInbound:
typ = proto.SpanType_INBOUND
case trace.SpanTypeRequestOutbound:
typ = proto.SpanType_OUTBOUND
}
rsp.Spans = append(rsp.Spans, &proto.Span{
Trace: t.Trace,
Id: t.Id,
Parent: t.Parent,
Name: t.Name,
Started: uint64(t.Started.UnixNano()),
Duration: uint64(t.Duration.Nanoseconds()),
Type: typ,
Metadata: t.Metadata,
})
}
return nil
}
func (d *Debug) Log(ctx context.Context, req *proto.LogRequest, stream proto.Debug_LogStream) error {
var options []log.ReadOption
since := time.Unix(req.Since, 0)
if !since.IsZero() {
options = append(options, log.Since(since))
}
count := int(req.Count)
if count > 0 {
options = append(options, log.Count(count))
}
if req.Stream {
// TODO: we need to figure out how to close the log stream
// It seems like when a client disconnects,
// the connection stays open until some timeout expires
// or something like that; that means the map of streams
// might end up leaking memory if not cleaned up properly
lgStream, err := d.log.Stream()
if err != nil {
return err
}
defer func() { _ = lgStream.Stop() }()
for record := range lgStream.Chan() {
// copy metadata
metadata := make(map[string]string)
for k, v := range record.Metadata {
metadata[k] = v
}
// send record
if err := stream.Send(&proto.Record{
Timestamp: record.Timestamp.Unix(),
Message: record.Message.(string),
Metadata: metadata,
}); err != nil {
return err
}
}
// done streaming, return
return nil
}
// get the log records
records, err := d.log.Read(options...)
if err != nil {
return err
}
// send all the logs downstream
for _, record := range records {
// copy metadata
metadata := make(map[string]string)
for k, v := range record.Metadata {
metadata[k] = v
}
// send record
if err := stream.Send(&proto.Record{
Timestamp: record.Timestamp.Unix(),
Message: record.Message.(string),
Metadata: metadata,
}); err != nil {
return err
}
}
return nil
}
+58
View File
@@ -0,0 +1,58 @@
// Package log provides debug logging
package log
import (
"encoding/json"
"fmt"
"time"
)
var (
// Default buffer size if any.
DefaultSize = 1024
// DefaultLog logger.
DefaultLog = NewLog()
// Default formatter.
DefaultFormat = TextFormat
)
// Log is debug log interface for reading and writing logs.
type Log interface {
// Read reads log entries from the logger
Read(...ReadOption) ([]Record, error)
// Write writes records to log
Write(Record) error
// Stream log records
Stream() (Stream, error)
}
// Record is log record entry.
type Record struct {
// Timestamp of logged event
Timestamp time.Time `json:"timestamp"`
// Metadata to enrich log record
Metadata map[string]string `json:"metadata"`
// Value contains log entry
Message interface{} `json:"message"`
}
// Stream returns a log stream.
type Stream interface {
Chan() <-chan Record
Stop() error
}
// Format is a function which formats the output.
type FormatFunc func(Record) string
// TextFormat returns text format.
func TextFormat(r Record) string {
t := r.Timestamp.Format("2006-01-02 15:04:05")
return fmt.Sprintf("%s %v", t, r.Message)
}
// JSONFormat is a json Format func.
func JSONFormat(r Record) string {
b, _ := json.Marshal(r)
return string(b)
}
+119
View File
@@ -0,0 +1,119 @@
// Package memory provides an in memory log buffer
package memory
import (
"fmt"
"go-micro.dev/v6/debug/log"
"go-micro.dev/v6/internal/util/ring"
)
var (
// DefaultSize of the logger buffer.
DefaultSize = 1024
)
// memoryLog is default micro log.
type memoryLog struct {
*ring.Buffer
}
// NewLog returns default Logger with.
func NewLog(opts ...log.Option) log.Log {
// get default options
options := log.DefaultOptions()
// apply requested options
for _, o := range opts {
o(&options)
}
return &memoryLog{
Buffer: ring.New(options.Size),
}
}
// Write writes logs into logger.
func (l *memoryLog) Write(r log.Record) error {
l.Put(fmt.Sprint(r.Message))
return nil
}
// Read reads logs and returns them.
func (l *memoryLog) Read(opts ...log.ReadOption) ([]log.Record, error) {
options := log.ReadOptions{}
// initialize the read options
for _, o := range opts {
o(&options)
}
var entries []*ring.Entry
// if Since options ha sbeen specified we honor it
if !options.Since.IsZero() {
entries = l.Since(options.Since)
}
// only if we specified valid count constraint
// do we end up doing some serious if-else kung-fu
// if since constraint has been provided
// we return *count* number of logs since the given timestamp;
// otherwise we return last count number of logs
if options.Count > 0 {
switch len(entries) > 0 {
case true:
// if we request fewer logs than what since constraint gives us
if options.Count < len(entries) {
entries = entries[0:options.Count]
}
default:
entries = l.Get(options.Count)
}
}
records := make([]log.Record, 0, len(entries))
for _, entry := range entries {
record := log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
}
records = append(records, record)
}
return records, nil
}
// Stream returns channel for reading log records
// along with a stop channel, close it when done.
func (l *memoryLog) Stream() (log.Stream, error) {
// get stream channel from ring buffer
stream, stop := l.Buffer.Stream()
// make a buffered channel
records := make(chan log.Record, 128)
// get last 10 records
last10 := l.Get(10)
// stream the log records
go func() {
// first send last 10 records
for _, entry := range last10 {
records <- log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
Metadata: make(map[string]string),
}
}
// now stream continuously
for entry := range stream {
records <- log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
Metadata: make(map[string]string),
}
}
}()
return &logStream{
stream: records,
stop: stop,
}, nil
}
+32
View File
@@ -0,0 +1,32 @@
package memory
import (
"reflect"
"testing"
"go-micro.dev/v6/debug/log"
)
func TestLogger(t *testing.T) {
// set size to some value
size := 100
// override the global logger
lg := NewLog(log.Size(size))
// make sure we have the right size of the logger ring buffer
if lg.(*memoryLog).Size() != size {
t.Errorf("expected buffer size: %d, got: %d", size, lg.(*memoryLog).Size())
}
// Log some cruft
lg.Write(log.Record{Message: "foobar"})
lg.Write(log.Record{Message: "foo bar"})
// Check if the logs are stored in the logger ring buffer
expected := []string{"foobar", "foo bar"}
entries, _ := lg.Read(log.Count(len(expected)))
for i, entry := range entries {
if !reflect.DeepEqual(entry.Message, expected[i]) {
t.Errorf("expected %s, got %s", expected[i], entry.Message)
}
}
}
+24
View File
@@ -0,0 +1,24 @@
package memory
import (
"go-micro.dev/v6/debug/log"
)
type logStream struct {
stream <-chan log.Record
stop chan bool
}
func (l *logStream) Chan() <-chan log.Record {
return l.stream
}
func (l *logStream) Stop() error {
select {
case <-l.stop:
return nil
default:
close(l.stop)
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package noop
import (
"go-micro.dev/v6/debug/log"
)
type noop struct{}
func (n *noop) Read(...log.ReadOption) ([]log.Record, error) {
return nil, nil
}
func (n *noop) Write(log.Record) error {
return nil
}
func (n *noop) Stream() (log.Stream, error) {
return nil, nil
}
func NewLog(opts ...log.Option) log.Log {
return new(noop)
}
+70
View File
@@ -0,0 +1,70 @@
package log
import "time"
// Option used by the logger.
type Option func(*Options)
// Options are logger options.
type Options struct {
// Format specifies the output format
Format FormatFunc
// Name of the log
Name string
// Size is the size of ring buffer
Size int
}
// Name of the log.
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Size sets the size of the ring buffer.
func Size(s int) Option {
return func(o *Options) {
o.Size = s
}
}
func Format(f FormatFunc) Option {
return func(o *Options) {
o.Format = f
}
}
// DefaultOptions returns default options.
func DefaultOptions() Options {
return Options{
Size: DefaultSize,
}
}
// ReadOptions for querying the logs.
type ReadOptions struct {
// Since what time in past to return the logs
Since time.Time
// Count specifies number of logs to return
Count int
// Stream requests continuous log stream
Stream bool
}
// ReadOption used for reading the logs.
type ReadOption func(*ReadOptions)
// Since sets the time since which to return the log records.
func Since(s time.Time) ReadOption {
return func(o *ReadOptions) {
o.Since = s
}
}
// Count sets the number of log records to return.
func Count(c int) ReadOption {
return func(o *ReadOptions) {
o.Count = c
}
}
+80
View File
@@ -0,0 +1,80 @@
package log
import (
"sync"
"github.com/google/uuid"
"go-micro.dev/v6/internal/util/ring"
)
// Should stream from OS.
type osLog struct {
format FormatFunc
buffer *ring.Buffer
subs map[string]*osStream
sync.RWMutex
}
type osStream struct {
stream chan Record
}
// Read reads log entries from the logger.
func (o *osLog) Read(...ReadOption) ([]Record, error) {
var records []Record
// read the last 100 records
for _, v := range o.buffer.Get(100) {
records = append(records, v.Value.(Record))
}
return records, nil
}
// Write writes records to log.
func (o *osLog) Write(r Record) error {
o.buffer.Put(r)
return nil
}
// Stream log records.
func (o *osLog) Stream() (Stream, error) {
o.Lock()
defer o.Unlock()
// create stream
st := &osStream{
stream: make(chan Record, 128),
}
// save stream
o.subs[uuid.New().String()] = st
return st, nil
}
func (o *osStream) Chan() <-chan Record {
return o.stream
}
func (o *osStream) Stop() error {
return nil
}
func NewLog(opts ...Option) Log {
options := Options{
Format: DefaultFormat,
}
for _, o := range opts {
o(&options)
}
l := &osLog{
format: options.Format,
buffer: ring.New(1024),
subs: make(map[string]*osStream),
}
return l
}
+78
View File
@@ -0,0 +1,78 @@
// Package http enables the http profiler
package http
import (
"context"
"net/http"
"net/http/pprof"
"sync"
"go-micro.dev/v6/debug/profile"
)
type httpProfile struct {
server *http.Server
sync.Mutex
running bool
}
var (
DefaultAddress = ":6060"
)
// Start the profiler.
func (h *httpProfile) Start() error {
h.Lock()
defer h.Unlock()
if h.running {
return nil
}
go func() {
if err := h.server.ListenAndServe(); err != nil {
h.Lock()
h.running = false
h.Unlock()
}
}()
h.running = true
return nil
}
// Stop the profiler.
func (h *httpProfile) Stop() error {
h.Lock()
defer h.Unlock()
if !h.running {
return nil
}
h.running = false
return h.server.Shutdown(context.TODO())
}
func (h *httpProfile) String() string {
return "http"
}
func NewProfile(opts ...profile.Option) profile.Profile {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return &httpProfile{
server: &http.Server{
Addr: DefaultAddress,
Handler: mux,
},
}
}
+98
View File
@@ -0,0 +1,98 @@
// Package pprof provides a pprof profiler
package pprof
import (
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"sync"
"go-micro.dev/v6/debug/profile"
)
type profiler struct {
// where the cpu profile is written
cpuFile *os.File
// where the mem profile is written
memFile *os.File
opts profile.Options
sync.Mutex
running bool
}
func (p *profiler) Start() error {
p.Lock()
defer p.Unlock()
if p.running {
return nil
}
cpuFile := filepath.Join(os.TempDir(), "cpu.pprof")
memFile := filepath.Join(os.TempDir(), "mem.pprof")
if len(p.opts.Name) > 0 {
cpuFile = filepath.Join(os.TempDir(), p.opts.Name+".cpu.pprof")
memFile = filepath.Join(os.TempDir(), p.opts.Name+".mem.pprof")
}
f1, err := os.Create(cpuFile)
if err != nil {
return err
}
f2, err := os.Create(memFile)
if err != nil {
return err
}
// start cpu profiling
if err := pprof.StartCPUProfile(f1); err != nil {
return err
}
// set cpu file
p.cpuFile = f1
// set mem file
p.memFile = f2
p.running = true
return nil
}
func (p *profiler) Stop() error {
p.Lock()
defer p.Unlock()
if !p.running {
return nil
}
pprof.StopCPUProfile()
p.cpuFile.Close()
runtime.GC()
_ = pprof.WriteHeapProfile(p.memFile)
p.memFile.Close()
p.running = false
p.cpuFile = nil
p.memFile = nil
return nil
}
func (p *profiler) String() string {
return "pprof"
}
func NewProfile(opts ...profile.Option) profile.Profile {
var options profile.Options
for _, o := range opts {
o(&options)
}
p := new(profiler)
p.opts = options
return p
}
+43
View File
@@ -0,0 +1,43 @@
// Package profile is for profilers
package profile
type Profile interface {
// Start the profiler
Start() error
// Stop the profiler
Stop() error
// Name of the profiler
String() string
}
var (
DefaultProfile Profile = new(noop)
)
type noop struct{}
func (p *noop) Start() error {
return nil
}
func (p *noop) Stop() error {
return nil
}
func (p *noop) String() string {
return "noop"
}
type Options struct {
// Name to use for the profile
Name string
}
type Option func(o *Options)
// Name of the profile.
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
File diff suppressed because it is too large Load Diff
+314
View File
@@ -0,0 +1,314 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/debug.proto
package debug
import (
fmt "fmt"
proto "google.golang.org/protobuf/proto"
math "math"
)
import (
context "context"
client "go-micro.dev/v6/client"
server "go-micro.dev/v6/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Debug service
type DebugService interface {
Log(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogService, error)
Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error)
Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error)
Trace(ctx context.Context, in *TraceRequest, opts ...client.CallOption) (*TraceResponse, error)
MessageBus(ctx context.Context, opts ...client.CallOption) (Debug_MessageBusService, error)
}
type debugService struct {
c client.Client
name string
}
func NewDebugService(name string, c client.Client) DebugService {
return &debugService{
c: c,
name: name,
}
}
func (c *debugService) Log(ctx context.Context, in *LogRequest, opts ...client.CallOption) (Debug_LogService, error) {
req := c.c.NewRequest(c.name, "Debug.Log", &LogRequest{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
}
if err := stream.Send(in); err != nil {
return nil, err
}
return &debugServiceLog{stream}, nil
}
type Debug_LogService interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
CloseSend() error
Close() error
Recv() (*Record, error)
}
type debugServiceLog struct {
stream client.Stream
}
func (x *debugServiceLog) CloseSend() error {
return x.stream.CloseSend()
}
func (x *debugServiceLog) Close() error {
return x.stream.Close()
}
func (x *debugServiceLog) Context() context.Context {
return x.stream.Context()
}
func (x *debugServiceLog) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugServiceLog) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugServiceLog) Recv() (*Record, error) {
m := new(Record)
err := x.stream.Recv(m)
if err != nil {
return nil, err
}
return m, nil
}
func (c *debugService) Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error) {
req := c.c.NewRequest(c.name, "Debug.Health", in)
out := new(HealthResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *debugService) Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error) {
req := c.c.NewRequest(c.name, "Debug.Stats", in)
out := new(StatsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *debugService) Trace(ctx context.Context, in *TraceRequest, opts ...client.CallOption) (*TraceResponse, error) {
req := c.c.NewRequest(c.name, "Debug.Trace", in)
out := new(TraceResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *debugService) MessageBus(ctx context.Context, opts ...client.CallOption) (Debug_MessageBusService, error) {
req := c.c.NewRequest(c.name, "Debug.MessageBus", &BusMsg{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
}
return &debugServiceMessageBus{stream}, nil
}
type Debug_MessageBusService interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
CloseSend() error
Close() error
Send(*BusMsg) error
Recv() (*BusMsg, error)
}
type debugServiceMessageBus struct {
stream client.Stream
}
func (x *debugServiceMessageBus) CloseSend() error {
return x.stream.CloseSend()
}
func (x *debugServiceMessageBus) Close() error {
return x.stream.Close()
}
func (x *debugServiceMessageBus) Context() context.Context {
return x.stream.Context()
}
func (x *debugServiceMessageBus) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugServiceMessageBus) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugServiceMessageBus) Send(m *BusMsg) error {
return x.stream.Send(m)
}
func (x *debugServiceMessageBus) Recv() (*BusMsg, error) {
m := new(BusMsg)
err := x.stream.Recv(m)
if err != nil {
return nil, err
}
return m, nil
}
// Server API for Debug service
type DebugHandler interface {
Log(context.Context, *LogRequest, Debug_LogStream) error
Health(context.Context, *HealthRequest, *HealthResponse) error
Stats(context.Context, *StatsRequest, *StatsResponse) error
Trace(context.Context, *TraceRequest, *TraceResponse) error
MessageBus(context.Context, Debug_MessageBusStream) error
}
func RegisterDebugHandler(s server.Server, hdlr DebugHandler, opts ...server.HandlerOption) error {
type debug interface {
Log(ctx context.Context, stream server.Stream) error
Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error
Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error
Trace(ctx context.Context, in *TraceRequest, out *TraceResponse) error
MessageBus(ctx context.Context, stream server.Stream) error
}
type Debug struct {
debug
}
h := &debugHandler{hdlr}
return s.Handle(s.NewHandler(&Debug{h}, opts...))
}
type debugHandler struct {
DebugHandler
}
func (h *debugHandler) Log(ctx context.Context, stream server.Stream) error {
m := new(LogRequest)
if err := stream.Recv(m); err != nil {
return err
}
return h.DebugHandler.Log(ctx, m, &debugLogStream{stream})
}
type Debug_LogStream interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*Record) error
}
type debugLogStream struct {
stream server.Stream
}
func (x *debugLogStream) Close() error {
return x.stream.Close()
}
func (x *debugLogStream) Context() context.Context {
return x.stream.Context()
}
func (x *debugLogStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugLogStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugLogStream) Send(m *Record) error {
return x.stream.Send(m)
}
func (h *debugHandler) Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error {
return h.DebugHandler.Health(ctx, in, out)
}
func (h *debugHandler) Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error {
return h.DebugHandler.Stats(ctx, in, out)
}
func (h *debugHandler) Trace(ctx context.Context, in *TraceRequest, out *TraceResponse) error {
return h.DebugHandler.Trace(ctx, in, out)
}
func (h *debugHandler) MessageBus(ctx context.Context, stream server.Stream) error {
return h.DebugHandler.MessageBus(ctx, &debugMessageBusStream{stream})
}
type Debug_MessageBusStream interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*BusMsg) error
Recv() (*BusMsg, error)
}
type debugMessageBusStream struct {
stream server.Stream
}
func (x *debugMessageBusStream) Close() error {
return x.stream.Close()
}
func (x *debugMessageBusStream) Context() context.Context {
return x.stream.Context()
}
func (x *debugMessageBusStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *debugMessageBusStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *debugMessageBusStream) Send(m *BusMsg) error {
return x.stream.Send(m)
}
func (x *debugMessageBusStream) Recv() (*BusMsg, error) {
m := new(BusMsg)
if err := x.stream.Recv(m); err != nil {
return nil, err
}
return m, nil
}
+107
View File
@@ -0,0 +1,107 @@
syntax = "proto3";
package debug;
option go_package = "./proto;debug";
// Compile this proto by running the following command in the debug directory:
// protoc --proto_path=. --micro_out=. --go_out=:. proto/debug.proto
service Debug {
rpc Log(LogRequest) returns (stream Record) {};
rpc Health(HealthRequest) returns (HealthResponse) {};
rpc Stats(StatsRequest) returns (StatsResponse) {};
rpc Trace(TraceRequest) returns (TraceResponse) {};
rpc MessageBus(stream BusMsg) returns (stream BusMsg) {};
}
message BusMsg { string msg = 1; }
message HealthRequest {
// optional service name
string service = 1;
}
message HealthResponse {
// default: ok
string status = 1;
}
message StatsRequest {
// optional service name
string service = 1;
}
message StatsResponse {
// timestamp of recording
uint64 timestamp = 1;
// unix timestamp
uint64 started = 2;
// in seconds
uint64 uptime = 3;
// in bytes
uint64 memory = 4;
// num threads
uint64 threads = 5;
// total gc in nanoseconds
uint64 gc = 6;
// total number of requests
uint64 requests = 7;
// total number of errors
uint64 errors = 8;
}
// LogRequest requests service logs
message LogRequest {
// service to request logs for
string service = 1;
// stream records continuously
bool stream = 2;
// count of records to request
int64 count = 3;
// relative time in seconds
// before the current time
// from which to show logs
int64 since = 4;
}
// Record is service log record
// Also used as default basic message type to test requests.
message Record {
// timestamp of log record
int64 timestamp = 1;
// record metadata
map<string, string> metadata = 2;
// message
string message = 3;
}
message TraceRequest {
// trace id to retrieve
string id = 1;
}
message TraceResponse { repeated Span spans = 1; }
enum SpanType {
INBOUND = 0;
OUTBOUND = 1;
}
message Span {
// the trace id
string trace = 1;
// id of the span
string id = 2;
// parent span
string parent = 3;
// name of the resource
string name = 4;
// time of start in nanoseconds
uint64 started = 5;
// duration of the execution in nanoseconds
uint64 duration = 6;
// associated metadata
map<string, string> metadata = 7;
SpanType type = 8;
}
+89
View File
@@ -0,0 +1,89 @@
package stats
import (
"runtime"
"sync"
"time"
"go-micro.dev/v6/internal/util/ring"
)
type stats struct {
// used to store past stats
buffer *ring.Buffer
sync.RWMutex
started int64
requests uint64
errors uint64
}
func (s *stats) snapshot() *Stat {
s.RLock()
defer s.RUnlock()
var mstat runtime.MemStats
runtime.ReadMemStats(&mstat)
now := time.Now().Unix()
return &Stat{
Timestamp: now,
Started: s.started,
Uptime: now - s.started,
Memory: mstat.Alloc,
GC: mstat.PauseTotalNs,
Threads: uint64(runtime.NumGoroutine()),
Requests: s.requests,
Errors: s.errors,
}
}
func (s *stats) Read() ([]*Stat, error) {
// TODO adjustable size and optional read values
buf := s.buffer.Get(60)
var stats []*Stat
// get a value from the buffer if it exists
for _, b := range buf {
stat, ok := b.Value.(*Stat)
if !ok {
continue
}
stats = append(stats, stat)
}
// get a snapshot
stats = append(stats, s.snapshot())
return stats, nil
}
func (s *stats) Write(stat *Stat) error {
s.buffer.Put(stat)
return nil
}
func (s *stats) Record(err error) error {
s.Lock()
defer s.Unlock()
// increment the total request count
s.requests++
// increment the error count
if err != nil {
s.errors++
}
return nil
}
// NewStats returns a new in memory stats buffer
// TODO add options.
func NewStats() Stats {
return &stats{
started: time.Now().Unix(),
buffer: ring.New(60),
}
}
+36
View File
@@ -0,0 +1,36 @@
// Package stats provides runtime stats
package stats
// Stats provides stats interface.
type Stats interface {
// Read stat snapshot
Read() ([]*Stat, error)
// Write a stat snapshot
Write(*Stat) error
// Record a request
Record(error) error
}
// A runtime stat.
type Stat struct {
// Timestamp of recording
Timestamp int64
// Start time as unix timestamp
Started int64
// Uptime in seconds
Uptime int64
// Memory usage in bytes
Memory uint64
// Threads aka go routines
Threads uint64
// Garbage collection in nanoseconds
GC uint64
// Total requests
Requests uint64
// Total errors
Errors uint64
}
var (
DefaultStats = NewStats()
)
+89
View File
@@ -0,0 +1,89 @@
package trace
import (
"context"
"time"
"github.com/google/uuid"
"go-micro.dev/v6/internal/util/ring"
)
type memTracer struct {
// ring buffer of traces
buffer *ring.Buffer
opts Options
}
func (t *memTracer) Read(opts ...ReadOption) ([]*Span, error) {
var options ReadOptions
for _, o := range opts {
o(&options)
}
sp := t.buffer.Get(t.buffer.Size())
spans := make([]*Span, 0, len(sp))
for _, span := range sp {
val := span.Value.(*Span)
// skip if trace id is specified and doesn't match
if len(options.Trace) > 0 && val.Trace != options.Trace {
continue
}
spans = append(spans, val)
}
return spans, nil
}
func (t *memTracer) Start(ctx context.Context, name string) (context.Context, *Span) {
span := &Span{
Name: name,
Trace: uuid.New().String(),
Id: uuid.New().String(),
Started: time.Now(),
Metadata: make(map[string]string),
}
// return span if no context
if ctx == nil {
return ToContext(context.Background(), span.Trace, span.Id), span
}
traceID, parentSpanID, ok := FromContext(ctx)
// If the trace can not be found in the header,
// that means this is where the trace is created.
if !ok {
return ToContext(ctx, span.Trace, span.Id), span
}
// set trace id
span.Trace = traceID
// set parent
span.Parent = parentSpanID
// return the span
return ToContext(ctx, span.Trace, span.Id), span
}
func (t *memTracer) Finish(s *Span) error {
// set finished time
s.Duration = time.Since(s.Started)
// save the span
t.buffer.Put(s)
return nil
}
func NewTracer(opts ...Option) Tracer {
var options Options
for _, o := range opts {
o(&options)
}
return &memTracer{
opts: options,
// the last 256 requests
buffer: ring.New(256),
}
}
+1
View File
@@ -0,0 +1 @@
package trace
+34
View File
@@ -0,0 +1,34 @@
package trace
type Options struct {
// Size is the size of ring buffer
Size int
}
type Option func(o *Options)
type ReadOptions struct {
// Trace id
Trace string
}
type ReadOption func(o *ReadOptions)
// Read the given trace.
func ReadTrace(t string) ReadOption {
return func(o *ReadOptions) {
o.Trace = t
}
}
const (
// DefaultSize of the buffer.
DefaultSize = 64
)
// DefaultOptions returns default options.
func DefaultOptions() Options {
return Options{
Size: DefaultSize,
}
}
+82
View File
@@ -0,0 +1,82 @@
// Package trace provides an interface for distributed tracing
package trace
import (
"context"
"time"
"go-micro.dev/v6/metadata"
"go-micro.dev/v6/transport/headers"
)
var (
// DefaultTracer is the default tracer.
DefaultTracer = NewTracer()
)
// Tracer is an interface for distributed tracing.
type Tracer interface {
// Start a trace
Start(ctx context.Context, name string) (context.Context, *Span)
// Finish the trace
Finish(*Span) error
// Read the traces
Read(...ReadOption) ([]*Span, error)
}
// SpanType describe the nature of the trace span.
type SpanType int
const (
// SpanTypeRequestInbound is a span created when serving a request.
SpanTypeRequestInbound SpanType = iota
// SpanTypeRequestOutbound is a span created when making a service call.
SpanTypeRequestOutbound
)
// Span is used to record an entry.
type Span struct {
// Start time
Started time.Time
// associated data
Metadata map[string]string
// Id of the trace
Trace string
// name of the span
Name string
// id of the span
Id string
// parent span id
Parent string
// Duration in nano seconds
Duration time.Duration
// Type
Type SpanType
}
// FromContext returns a span from context.
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
traceID, traceOk := metadata.Get(ctx, headers.TraceIDKey)
microID, microOk := metadata.Get(ctx, headers.ID)
if !traceOk && !microOk {
isFound = false
return
}
if !traceOk {
traceID = microID
}
parentSpanID, ok := metadata.Get(ctx, headers.SpanID)
return traceID, parentSpanID, ok
}
// ToContext saves the trace and span ids in the context.
func ToContext(ctx context.Context, traceID, parentSpanID string) context.Context {
return metadata.MergeContext(ctx, map[string]string{
headers.TraceIDKey: traceID,
headers.SpanID: parentSpanID,
}, true)
}