Compare commits

..

15 Commits

Author SHA1 Message Date
fcc4faff8a fix godoc link
All checks were successful
coverage / build (push) Successful in 56s
test / test (push) Successful in 3m25s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2025-01-04 18:57:02 +03:00
5df8f83f45 badges (#392)
Some checks failed
coverage / build (push) Successful in 57s
test / test (push) Has been cancelled
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
Co-authored-by: vtolstov <vtolstov@users.noreply.github.com>
Reviewed-on: #392
Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org>
Co-committed-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2025-01-04 18:53:57 +03:00
vtolstov
27fa6e9173 Apply Code Coverage Badge 2024-12-28 22:58:19 +00:00
bd55a35dc3 logger/slog: add delayed buffer test
All checks were successful
test / test (push) Successful in 3m33s
coverage / build (push) Successful in 8m22s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-29 01:57:41 +03:00
653bd386cc util/buffer: add DelayedBuffer
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-29 01:57:41 +03:00
vtolstov
558c6f4d7c Apply Code Coverage Badge 2024-12-28 11:56:07 +00:00
d7dd6fbeb2 register/memory: fix build
All checks were successful
test / test (push) Successful in 3m35s
coverage / build (push) Successful in 8m22s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-28 14:55:20 +03:00
a00cf2c8d9 register: watcher fixes
Some checks failed
coverage / build (push) Failing after 55s
test / test (push) Successful in 3m39s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-28 14:51:10 +03:00
vtolstov
a3e8ab2492 Apply Code Coverage Badge 2024-12-27 20:57:08 +00:00
06da500ef4 register: cleanup
All checks were successful
test / test (push) Successful in 3m33s
coverage / build (push) Successful in 9m11s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-27 23:56:27 +03:00
277f04ba19 register: add Codec option
All checks were successful
coverage / build (push) Successful in 1m3s
test / test (push) Successful in 3m31s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-27 19:33:50 +03:00
vtolstov
470263ff5f Apply Code Coverage Badge 2024-12-27 16:14:00 +00:00
b8232e02be register: add ListName option
All checks were successful
test / test (push) Successful in 3m50s
coverage / build (push) Successful in 8m15s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-27 19:12:57 +03:00
vtolstov
f8c5e10c1d Apply Code Coverage Badge 2024-12-26 22:21:35 +00:00
397e71f815 Merge pull request 'register: improvements' (#390) from register into v3
All checks were successful
test / test (push) Successful in 3m50s
coverage / build (push) Successful in 13m40s
Reviewed-on: #390
2024-12-27 01:20:49 +03:00
10 changed files with 177 additions and 72 deletions

View File

@@ -26,24 +26,24 @@ jobs:
- name: test coverage
run: |
go test -v -cover ./... -coverprofile coverage.out -coverpkg ./...
go test -v -cover ./... -covermode=count -coverprofile coverage.out -coverpkg ./...
go tool cover -func coverage.out -o coverage.out
- name: coverage badge
uses: tj-actions/coverage-badge-go@v1
uses: tj-actions/coverage-badge-go@v2
with:
green: 80
filename: coverage.out
- uses: stefanzweifel/git-auto-commit-action@v4
id: auto-commit-action
name: autocommit
with:
commit_message: Apply Code Coverage Badge
skip_fetch: true
skip_checkout: true
file_pattern: ./README.md
- name: Push Changes
- name: push
if: steps.auto-commit-action.outputs.changes_detected == 'true'
uses: ad-m/github-push-action@master
with:

View File

@@ -1,5 +1,9 @@
# Micro
![Coverage](https://img.shields.io/badge/Coverage-45.1%25-yellow)
[![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/go.unistack.org/micro/v3?tab=overview)
[![Status](https://git.unistack.org/unistack-org/micro/actions/workflows/job_tests.yml/badge.svg?branch=v3)](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush)
[![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3)
![Coverage](https://img.shields.io/badge/Coverage-44.6%25-yellow)
Micro is a standard library for microservices.

View File

@@ -14,6 +14,7 @@ import (
"github.com/google/uuid"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/util/buffer"
)
// always first to have proper check
@@ -30,11 +31,30 @@ func TestStacktrace(t *testing.T) {
l.Error(ctx, "msg1", errors.New("err"))
if !bytes.Contains(buf.Bytes(), []byte(`slog_test.go:31`)) {
if !bytes.Contains(buf.Bytes(), []byte(`slog_test.go:32`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestDelayedBuffer(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
dbuf := buffer.NewDelayedBuffer(100, 100*time.Millisecond, buf)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(dbuf),
WithHandlerFunc(slog.NewTextHandler),
logger.WithAddStacktrace(true),
)
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
t.Fatal(err)
}
l.Error(ctx, "msg1", errors.New("err"))
time.Sleep(120 * time.Millisecond)
if !bytes.Contains(buf.Bytes(), []byte(`key1=val1`)) {
t.Fatalf("logger delayed buffer not works, buf contains: %s", buf.Bytes())
}
}
func TestTime(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)

View File

@@ -149,7 +149,7 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new service: %s, version: %s", s.Name, s.Version))
}
m.records[options.Namespace] = srvs
go m.sendEvent(&register.Result{Action: "create", Service: s})
go m.sendEvent(&register.Result{Action: register.EventCreate, Service: s})
}
var addedNodes bool
@@ -185,7 +185,7 @@ func (m *memory) Register(_ context.Context, s *register.Service, opts ...regist
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new node to service: %s, version: %s", s.Name, s.Version))
}
go m.sendEvent(&register.Result{Action: "update", Service: s})
go m.sendEvent(&register.Result{Action: register.EventUpdate, Service: s})
} else {
// refresh TTL and timestamp
for _, n := range s.Nodes {
@@ -238,7 +238,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// is cleanup
if len(version.Nodes) > 0 {
m.records[options.Namespace][s.Name][s.Version] = version
go m.sendEvent(&register.Result{Action: "update", Service: s})
go m.sendEvent(&register.Result{Action: register.EventUpdate, Service: s})
return nil
}
@@ -246,7 +246,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// register and exit
if len(versions) == 1 {
delete(m.records[options.Namespace], s.Name)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
go m.sendEvent(&register.Result{Action: register.EventDelete, Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s", s.Name))
@@ -256,7 +256,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
// there are other versions of the service running, so only remove this version of it
delete(m.records[options.Namespace][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
go m.sendEvent(&register.Result{Action: register.EventDelete, Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s, version: %s", s.Name, s.Version))
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls"
"time"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/tracer"
@@ -26,6 +27,8 @@ type Options struct {
Name string
// Addrs specifies register addrs
Addrs []string
// Codec used to marshal/unmarshal data in register
Codec codec.Codec
// Timeout specifies timeout
Timeout time.Duration
}
@@ -37,6 +40,7 @@ func NewOptions(opts ...Option) Options {
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Context: context.Background(),
Codec: codec.NewCodec(),
}
for _, o := range opts {
o(&options)
@@ -134,8 +138,6 @@ type ListOptions struct {
Context context.Context
// Namespace to scope the request to
Namespace string
// Name filter services by name
Name string
}
// NewListOptions returns list options filled by opts
@@ -297,16 +299,15 @@ func ListNamespace(d string) ListOption {
}
}
// ListName sets the name for list method to filter needed services
func ListName(n string) ListOption {
return func(o *ListOptions) {
o.Name = n
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}

View File

@@ -15,31 +15,31 @@ type Watcher interface {
// the watcher. Actions can be create, update, delete
type Result struct {
// Service holds register service
Service *Service
Service *Service `json:"service,omitempty"`
// Action holds the action
Action string
Action EventType `json:"action,omitempty"`
}
// EventType defines register event type
type EventType int
const (
// Create is emitted when a new service is registered
Create EventType = iota
// Delete is emitted when an existing service is deregistered
Delete
// Update is emitted when an existing service is updated
Update
// EventCreate is emitted when a new service is registered
EventCreate EventType = iota
// EventDelete is emitted when an existing service is deregistered
EventDelete
// EventUpdate is emitted when an existing service is updated
EventUpdate
)
// String returns human readable event type
func (t EventType) String() string {
switch t {
case Create:
case EventCreate:
return "create"
case Delete:
case EventDelete:
return "delete"
case Update:
case EventUpdate:
return "update"
default:
return "unknown"
@@ -49,11 +49,11 @@ func (t EventType) String() string {
// Event is register event
type Event struct {
// Timestamp is event timestamp
Timestamp time.Time
Timestamp time.Time `json:"timestamp,omitempty"`
// Service is register service
Service *Service
Service *Service `json:"service,omitempty"`
// ID is register id
ID string
ID string `json:"id,omitempty"`
// Type defines type of event
Type EventType
Type EventType `json:"type,omitempty"`
}

View File

@@ -1,27 +0,0 @@
package buf
import (
"bytes"
"io"
)
var _ io.Closer = &Buffer{}
// Buffer bytes.Buffer wrapper to satisfie io.Closer interface
type Buffer struct {
*bytes.Buffer
}
// Close reset buffer contents
func (b *Buffer) Close() error {
b.Buffer.Reset()
return nil
}
// New creates new buffer that satisfies Closer interface
func New(b *bytes.Buffer) *Buffer {
if b == nil {
b = bytes.NewBuffer(nil)
}
return &Buffer{b}
}

85
util/buffer/buffer.go Normal file
View File

@@ -0,0 +1,85 @@
package buffer
import (
"io"
"sync"
"time"
)
var _ io.WriteCloser = (*DelayedBuffer)(nil)
// DelayedBuffer is the buffer that holds items until either the buffer filled or a specified time limit is reached
type DelayedBuffer struct {
mu sync.Mutex
maxWait time.Duration
flushTime time.Time
buffer chan []byte
ticker *time.Ticker
w io.Writer
err error
}
func NewDelayedBuffer(size int, maxWait time.Duration, w io.Writer) *DelayedBuffer {
b := &DelayedBuffer{
buffer: make(chan []byte, size),
ticker: time.NewTicker(maxWait),
w: w,
flushTime: time.Now(),
maxWait: maxWait,
}
b.loop()
return b
}
func (b *DelayedBuffer) loop() {
go func() {
for range b.ticker.C {
b.mu.Lock()
if time.Since(b.flushTime) > b.maxWait {
b.flush()
}
b.mu.Unlock()
}
}()
}
func (b *DelayedBuffer) flush() {
bufLen := len(b.buffer)
if bufLen > 0 {
tmp := make([][]byte, bufLen)
for i := 0; i < bufLen; i++ {
tmp[i] = <-b.buffer
}
for _, t := range tmp {
_, b.err = b.w.Write(t)
}
b.flushTime = time.Now()
}
}
func (b *DelayedBuffer) Put(items ...[]byte) {
b.mu.Lock()
for _, item := range items {
select {
case b.buffer <- item:
default:
b.flush()
b.buffer <- item
}
}
b.mu.Unlock()
}
func (b *DelayedBuffer) Close() error {
b.mu.Lock()
b.flush()
close(b.buffer)
b.ticker.Stop()
b.mu.Unlock()
return b.err
}
func (b *DelayedBuffer) Write(data []byte) (int, error) {
b.Put(data)
return len(data), b.err
}

View File

@@ -0,0 +1,22 @@
package buffer
import (
"bytes"
"testing"
"time"
)
func TestTimedBuffer(t *testing.T) {
buf := bytes.NewBuffer(nil)
b := NewDelayedBuffer(100, 300*time.Millisecond, buf)
for i := 0; i < 100; i++ {
_, _ = b.Write([]byte(`test`))
}
if buf.Len() != 0 {
t.Fatal("delayed write not worked")
}
time.Sleep(400 * time.Millisecond)
if buf.Len() == 0 {
t.Fatal("delayed write not worked")
}
}

View File

@@ -35,11 +35,11 @@ func TestUnmarshalYAML(t *testing.T) {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
err = yaml.Unmarshal([]byte(`{"ttl":"1y"}`), v)
err = yaml.Unmarshal([]byte(`{"ttl":"1d"}`), v)
if err != nil {
t.Fatal(err)
} else if *(v.TTL) != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
} else if *(v.TTL) != 86400000000000 {
t.Fatalf("invalid duration %v != 86400000000000", *v.TTL)
}
}
@@ -68,11 +68,11 @@ func TestUnmarshalJSON(t *testing.T) {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
err = json.Unmarshal([]byte(`{"ttl":"1y"}`), v)
err = json.Unmarshal([]byte(`{"ttl":"1d"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
} else if v.TTL != 86400000000000 {
t.Fatalf("invalid duration %v != 86400000000000", v.TTL)
}
}
@@ -87,11 +87,11 @@ func TestParseDuration(t *testing.T) {
if td.String() != "340h0m0s" {
t.Fatalf("ParseDuration 14d != 340h0m0s : %s", td.String())
}
td, err = ParseDuration("1y")
td, err = ParseDuration("1d")
if err != nil {
t.Fatalf("ParseDuration error: %v", err)
}
if td.String() != "8784h0m0s" {
t.Fatalf("ParseDuration 1y != 8784h0m0s : %s", td.String())
if td.String() != "24h0m0s" {
t.Fatalf("ParseDuration 1d != 24h0m0s : %s", td.String())
}
}