Initial QSfera import
This commit is contained in:
+58
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go-micro.dev/v4/util/ring"
|
||||
)
|
||||
|
||||
// Should stream from OS.
|
||||
type osLog struct {
|
||||
format FormatFunc
|
||||
buffer *ring.Buffer
|
||||
subs map[string]*osStream
|
||||
|
||||
sync.RWMutex
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Package http enables the http profiler
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"sync"
|
||||
|
||||
"go-micro.dev/v4/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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Package pprof provides a pprof profiler
|
||||
package pprof
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
|
||||
"go-micro.dev/v4/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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go-micro.dev/v4/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),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package trace
|
||||
|
||||
import "context"
|
||||
|
||||
type noop struct{}
|
||||
|
||||
func (n *noop) Init(...Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *noop) Finish(*Span) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noop) Read(...ReadOption) ([]*Span, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Package trace provides an interface for distributed tracing
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go-micro.dev/v4/metadata"
|
||||
"go-micro.dev/v4/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)
|
||||
}
|
||||
Reference in New Issue
Block a user